From 8b34ab4bfe1689db13c97518d9f12f9158516a76 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 26 Jul 2021 17:39:35 +0200 Subject: [PATCH 01/52] Add boilerplate class for monotonic path order It contains no real implementation yet. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 103 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 src/PathOrderMonotonic.h diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h new file mode 100644 index 0000000000..78748cb770 --- /dev/null +++ b/src/PathOrderMonotonic.h @@ -0,0 +1,103 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PATHORDERMONOTONIC_H +#define PATHORDERMONOTONIC_H + +namespace cura +{ + +/*! + * Class that orders paths monotonically. + * + * This is a utility class that changes the order in which things are printed, + * to ensure that they are printed in the same major direction. Printing + * adjacent lines in the same direction ensures that they layer on top of each + * other in the same way. That helps to make the entire surface look consistent. + * + * To use this class, first create an instance and provide some parameters as + * metadata. Then add polygons and polylines to the class. Then call the + * \ref optimize function to compute the order. Finally, print the polygons and + * polylines in the \ref paths field in the order in which they are given. + * + * In the output of this class, polylines and polygons are combined into a + * single list: \ref paths . Each path contains a pointer to the original + * polygon data, as well as whether that data represented a polygon or a + * polyline, which direction to print the path in, and where to start along the + * path. + * + * The monotonic order does not use the Z seam settings. It is meant to apply + * only to polylines. If given polygons, it will place the seam in the location + * closest to the source direction of the monotonicity vector. + */ +template +class PathOrderMonotonic +{ +public: + /*! + * Represents a path which has been optimised, the output of the ordering. + * + * This small data structure contains the vertex data of a path, where to + * start along the path and in which direction to print it, as well as + * whether the path should be closed (in the case of a polygon) or open (in + * case of a polyline). + * + * After the ordering has completed, the \ref paths vector will be filled + * with optimized paths. + */ + struct Path + { + /*! + * Construct a new planned path. + * + * The \ref converted field is not initialized yet. This can only be + * done after all of the input paths have been added, to prevent + * invalidating the pointers. + */ + Path(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false) + : vertices(vertices) + , start_vertex(start_vertex) + , is_closed(is_closed) + , backwards(backwards) + {} + + /*! + * The vertex data of the path. + */ + PathType vertices; + + /*! + * Vertex data, converted into a Polygon so that the orderer knows how + * to deal with this data. + */ + ConstPolygonPointer converted; + + /*! + * Which vertex along the path to start printing with. + * + * If this path represents a polyline, this will always be one of the + * endpoints of the path; either 0 or ``vertices->size() - 1``. + */ + size_t start_vertex; + + /*! + * Whether the path should be closed at the ends or not. + * + * If this path should be closed, it represents a polygon. If it should + * not be closed, it represents a polyline. + */ + bool is_closed; + + /*! + * Whether the path should be traversed in backwards direction. + * + * For a polyline it may be more efficient to print the path in + * backwards direction, if the last vertex is closer than the first. + */ + bool backwards; + }; +}; + +} + +#endif //PATHORDERMONOTONIC_H \ No newline at end of file From 5e3c37671c4667593357fe8aaa71875aa1efbc45 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 10:58:42 +0200 Subject: [PATCH 02/52] Move Path internal class to parent It'll need to be re-used for the PathOrderOptimizer as well once we merge all this to Arachne. Contributes to issue CURA-7928. --- src/PathOrder.h | 87 ++++++++++++++++++++++++++++++++++++++++ src/PathOrderMonotonic.h | 64 +---------------------------- 2 files changed, 88 insertions(+), 63 deletions(-) create mode 100644 src/PathOrder.h diff --git a/src/PathOrder.h b/src/PathOrder.h new file mode 100644 index 0000000000..7d3cd55163 --- /dev/null +++ b/src/PathOrder.h @@ -0,0 +1,87 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PATHORDER_H +#define PATHORDER_H + +#include "utils/polygonUtils.h" + +/*! + * Parent class of all path ordering techniques. + * + * This path ordering provides a virtual interface for the methods that path + * ordering techniques should implement in order to provide a consistent API to + * allow interchanging the techniques used to optimise the printing order. + * + * It also provides some base members that can be used by all path ordering + * techniques, to reduce code duplication. + */ +template +class PathOrder +{ +public: + /*! + * Represents a path which has been optimised, the output of the ordering. + * + * This small data structure contains the vertex data of a path, where to + * start along the path and in which direction to print it, as well as + * whether the path should be closed (in the case of a polygon) or open (in + * case of a polyline). + * + * After the ordering has completed, the \ref paths vector will be filled + * with optimized paths. + */ + struct Path + { + /*! + * Construct a new planned path. + * + * The \ref converted field is not initialized yet. This can only be + * done after all of the input paths have been added, to prevent + * invalidating the pointers. + */ + Path(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false) + : vertices(vertices) + , start_vertex(start_vertex) + , is_closed(is_closed) + , backwards(backwards) + {} + + /*! + * The vertex data of the path. + */ + PathType vertices; + + /*! + * Vertex data, converted into a Polygon so that the orderer knows how + * to deal with this data. + */ + ConstPolygonPointer converted; + + /*! + * Which vertex along the path to start printing with. + * + * If this path represents a polyline, this will always be one of the + * endpoints of the path; either 0 or ``vertices->size() - 1``. + */ + size_t start_vertex; + + /*! + * Whether the path should be closed at the ends or not. + * + * If this path should be closed, it represents a polygon. If it should + * not be closed, it represents a polyline. + */ + bool is_closed; + + /*! + * Whether the path should be traversed in backwards direction. + * + * For a polyline it may be more efficient to print the path in + * backwards direction, if the last vertex is closer than the first. + */ + bool backwards; + }; +}; + +#endif //PATHORDER_H \ No newline at end of file diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 78748cb770..6a3eb6bbbe 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -31,71 +31,9 @@ namespace cura * closest to the source direction of the monotonicity vector. */ template -class PathOrderMonotonic +class PathOrderMonotonic : public PathOrder { public: - /*! - * Represents a path which has been optimised, the output of the ordering. - * - * This small data structure contains the vertex data of a path, where to - * start along the path and in which direction to print it, as well as - * whether the path should be closed (in the case of a polygon) or open (in - * case of a polyline). - * - * After the ordering has completed, the \ref paths vector will be filled - * with optimized paths. - */ - struct Path - { - /*! - * Construct a new planned path. - * - * The \ref converted field is not initialized yet. This can only be - * done after all of the input paths have been added, to prevent - * invalidating the pointers. - */ - Path(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false) - : vertices(vertices) - , start_vertex(start_vertex) - , is_closed(is_closed) - , backwards(backwards) - {} - - /*! - * The vertex data of the path. - */ - PathType vertices; - - /*! - * Vertex data, converted into a Polygon so that the orderer knows how - * to deal with this data. - */ - ConstPolygonPointer converted; - - /*! - * Which vertex along the path to start printing with. - * - * If this path represents a polyline, this will always be one of the - * endpoints of the path; either 0 or ``vertices->size() - 1``. - */ - size_t start_vertex; - - /*! - * Whether the path should be closed at the ends or not. - * - * If this path should be closed, it represents a polygon. If it should - * not be closed, it represents a polyline. - */ - bool is_closed; - - /*! - * Whether the path should be traversed in backwards direction. - * - * For a polyline it may be more efficient to print the path in - * backwards direction, if the last vertex is closer than the first. - */ - bool backwards; - }; }; } From c50f54fb1e987aa476945e4a73536a05cd3cf32b Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 11:08:06 +0200 Subject: [PATCH 03/52] Add fields and basic methods to add paths to plan This is largely copied (copy-written) from the current implementation already in libArachne. These are fields that all orderings will need, as part of their input and output. Contributes to issue CURA-7928. --- src/PathOrder.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/PathOrder.h b/src/PathOrder.h index 7d3cd55163..b0dfe3a3aa 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -4,6 +4,7 @@ #ifndef PATHORDER_H #define PATHORDER_H +#include "settings/EnumSettings.h" //To get the seam settings. #include "utils/polygonUtils.h" /*! @@ -15,6 +16,9 @@ * * It also provides some base members that can be used by all path ordering * techniques, to reduce code duplication. + * \param PathType The type of paths to optimise. This class can reorder any + * type of paths as long as an overload of ``getVertexData`` exists to convert + * that type into a list of vertices. */ template class PathOrder @@ -82,6 +86,51 @@ class PathOrder */ bool backwards; }; + + /*! + * After reordering, this contains the path that need to be printed in the + * correct order. + * + * Each path contains the information necessary to print the paths: A + * pointer to the vertex data, whether to close the loop or not, the + * direction in which to print the path and where to start the path. + */ + std::vector paths; + + /*! + * The location where the nozzle is assumed to start from before printing + * these parts. + */ + Point start_point; + + /*! + * Seam settings. + */ + ZSeamConfig seam_config; + + /*! + * Add a new polygon to be planned. + * + * This will be interpreted as a closed polygon. + * \param polygon The polygon to plan. + */ + void addPolygon(const PathType& polygon) + { + constexpr bool is_closed = true; + paths.emplace_back(polygon, is_closed); + } + + /*! + * Add a new polyline to be planned. + * + * This polyline will be interpreted as an open polyline, not a polygon. + * \param polyline The polyline to plan. + */ + void addPolyline(const PathType& polyline) + { + constexpr bool is_closed = false; + paths.emplace_back(polyline, is_closed); + } }; #endif //PATHORDER_H \ No newline at end of file From d39596e76b07562ce5a777e62e4415a3520e75df Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 11:27:56 +0200 Subject: [PATCH 04/52] Add virtual function to optimise paths This is what the PathOrderMonotonic (and later PathOrderOptimizer) will need to implement. Contributes to issue CURA-7928. --- src/PathOrder.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/PathOrder.h b/src/PathOrder.h index b0dfe3a3aa..889385e552 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -131,6 +131,19 @@ class PathOrder constexpr bool is_closed = false; paths.emplace_back(polyline, is_closed); } + + /*! + * Call on this method to reorder all paths to an appropriate printing + * order. + * + * Every ordering technique will need to implement this to produce the + * correct order. + * + * The \ref paths vector will be edited by this function call to reorder it, + * to adjust the starting positions of those paths and perhaps to mark those + * paths as being printed backwards. + */ + virtual void optimize() = 0; }; #endif //PATHORDER_H \ No newline at end of file From 937b8689e8a767959ff39c2d9446d39648ddef16 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 11:40:01 +0200 Subject: [PATCH 05/52] Add implementations of getVertexData for various data types These are the data types we need to support ordering for, for now. Contributes to issue CURA-7928. --- src/PathOrder.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ src/PathOrder.h | 15 +++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 src/PathOrder.cpp diff --git a/src/PathOrder.cpp b/src/PathOrder.cpp new file mode 100644 index 0000000000..5974a17ca7 --- /dev/null +++ b/src/PathOrder.cpp @@ -0,0 +1,43 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PathOrder.h" //The definitions we're implementing here. +#include "sliceDataStorage.h" //For SliceLayerPart and related parts to reorder. + +//Since the PathOrder is a template class, we will only implement the template specializations in this file. +//The templated segments need to go into the header. + +namespace cura +{ + +template<> +ConstPolygonRef PathOrder::getVertexData(ConstPolygonRef path) +{ + return path; +} + +template<> +ConstPolygonRef PathOrder::getVertexData(PolygonRef path) +{ + return path; +} + +template<> +ConstPolygonRef PathOrder::getVertexData(const SkinPart* path) +{ + return path->outline.outerPolygon(); +} + +template<> +ConstPolygonRef PathOrder::getVertexData(const SliceLayerPart* path) +{ + return path->outline.outerPolygon(); +} + +template<> +ConstPolygonRef PathOrder::getVertexData(const SupportInfillPart* path) +{ + return path->outline.outerPolygon(); +} + +} \ No newline at end of file diff --git a/src/PathOrder.h b/src/PathOrder.h index 889385e552..ac6a1454e2 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -144,6 +144,21 @@ class PathOrder * paths as being printed backwards. */ virtual void optimize() = 0; + +protected: + /*! + * Get vertex data from the custom path type. + * + * This is a function that allows the reordering algorithm to work with any + * type of input data structure. It provides a translation from the input + * data structure that the user would like to have reordered to a data + * structure that the reordering algorithm can work with. It's unknown how + * the ``PathType`` object is structured or how to get the vertex data from + * it. This function tells the optimizer how, but it needs to be specialized + * for each different type that this class is used with. See the .cpp file + * for examples and where to add a new specialization. + */ + ConstPolygonRef getVertexData(const PathType& path); }; #endif //PATHORDER_H \ No newline at end of file From e2f37d6506451955dca813ecc7c481cc779edabf Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 12:00:45 +0200 Subject: [PATCH 06/52] Move ZSeamConfig helper struct to separate file As per our code standards, we'd like to have each public class and struct in a separate file. In this case that would've really helped us, since I want to be able to use that struct in a different class too... Contributes to issue CURA-7928. --- CMakeLists.txt | 1 + src/pathOrderOptimizer.h | 29 ++---------------- src/settings/ZSeamConfig.cpp | 21 +++++++++++++ src/settings/ZSeamConfig.h | 59 ++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 26 deletions(-) create mode 100644 src/settings/ZSeamConfig.cpp create mode 100644 src/settings/ZSeamConfig.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2de514d2d9..d2aabf71c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -196,6 +196,7 @@ set(engine_SRCS # Except main.cpp. src/settings/FlowTempGraph.cpp src/settings/PathConfigStorage.cpp src/settings/Settings.cpp + src/settings/ZSeamConfig.cpp src/utils/AABB.cpp src/utils/AABB3D.cpp diff --git a/src/pathOrderOptimizer.h b/src/pathOrderOptimizer.h index ea5b5ecfbf..945d675a05 100644 --- a/src/pathOrderOptimizer.h +++ b/src/pathOrderOptimizer.h @@ -1,4 +1,4 @@ -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef PATHOPTIMIZER_H @@ -6,35 +6,12 @@ #include #include "settings/EnumSettings.h" +#include "settings/ZSeamConfig.h" //To group Z seam requirements together. #include "utils/polygon.h" #include "utils/polygonUtils.h" -namespace cura { - -/*! - * Helper class that encapsulates the various criteria that define the location of the z-seam. - * Instances of this are passed to the PathOrderOptimizer to specify where the z-seam is to be located. - */ -class ZSeamConfig +namespace cura { -public: - EZSeamType type; - Point pos; //!< The position near where to create the z_seam (if \ref PathOrderOptimizer::type == 'back') - EZSeamCornerPrefType corner_pref; - // default constructor - ZSeamConfig() - : type(EZSeamType::SHORTEST) - , pos(Point(0, 0)) - , corner_pref(EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) - { - } - ZSeamConfig(EZSeamType type, Point pos, EZSeamCornerPrefType corner_pref) - : type(type) - , pos(pos) - , corner_pref(corner_pref) - { - } -}; /*! * Parts order optimization class. diff --git a/src/settings/ZSeamConfig.cpp b/src/settings/ZSeamConfig.cpp new file mode 100644 index 0000000000..3b6d458e7d --- /dev/null +++ b/src/settings/ZSeamConfig.cpp @@ -0,0 +1,21 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "ZSeamConfig.h" //The definitions we're implementing. + +namespace cura +{ + +ZSeamConfig::ZSeamConfig() + : type(EZSeamType::SHORTEST) + , pos(Point(0, 0)) + , corner_pref(EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) +{} + +ZSeamConfig::ZSeamConfig(const EZSeamType type, const Point pos, const EZSeamCornerPrefType corner_pref) + : type(type) + , pos(pos) + , corner_pref(corner_pref) +{} + +} \ No newline at end of file diff --git a/src/settings/ZSeamConfig.h b/src/settings/ZSeamConfig.h new file mode 100644 index 0000000000..1899d64ee0 --- /dev/null +++ b/src/settings/ZSeamConfig.h @@ -0,0 +1,59 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef ZSEAMCONFIG_H +#define ZSEAMCONFIG_H + +#include "EnumSettings.h" //For the seam type and corner preference settings. +#include "../utils/IntPoint.h" //For Point. + +namespace cura +{ + +/*! + * Helper class that encapsulates various criteria that define the location of + * the seam. + * + * Instances of this are passed to the order optimizer to specify where the seam + * is to be placed. + */ +struct ZSeamConfig +{ + /*! + * Strategy to place the seam (user-specified, shortest distance, sharpest + * corner, etc.) + */ + EZSeamType type; + + /*! + * When using a user-specified position for the seam, this is the position + * that the user specified. + */ + Point pos; + + /*! + * Corner preference type, applicable to various strategies to filter on + * which corners the seam is allowed to be located. + */ + EZSeamCornerPrefType corner_pref; + + /*! + * Default constructor for use when memory must be allocated before it gets + * filled (like with some data structures). + * + * This will select the "shortest" seam strategy. + */ + ZSeamConfig(); + + /*! + * Create a seam configuration with a custom configuration. + * \param type The strategy to place the seam. + * \param pos The position of a user-specified seam. + * \param corner_pref The corner preference, applicable to some strategies. + */ + ZSeamConfig(const EZSeamType type, const Point pos, const EZSeamCornerPrefType corner_pref); +}; + +} + +#endif //ZSEAMCONFIG_H \ No newline at end of file From 9336de159c35aab12713a99852b41feb7bbf8c4f Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 12:49:47 +0200 Subject: [PATCH 07/52] Compile getVertexData template specialisations Need to add them to CMakeLists to allow them to compile. We can't add them by reference because we need to copy over the data to the Path instance anyway. Contributes to issue CURA-7928. --- CMakeLists.txt | 1 + src/PathOrder.h | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2aabf71c1..e8313a58fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,6 +150,7 @@ set(engine_SRCS # Except main.cpp. src/MeshGroup.cpp src/Mold.cpp src/multiVolumes.cpp + src/PathOrder.cpp src/pathOrderOptimizer.cpp src/Preheat.cpp src/PrimeTower.cpp diff --git a/src/PathOrder.h b/src/PathOrder.h index ac6a1454e2..f3ac51017d 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -4,9 +4,12 @@ #ifndef PATHORDER_H #define PATHORDER_H -#include "settings/EnumSettings.h" //To get the seam settings. +#include "settings/ZSeamConfig.h" //To get the seam settings. #include "utils/polygonUtils.h" +namespace cura +{ + /*! * Parent class of all path ordering techniques. * @@ -158,7 +161,9 @@ class PathOrder * for each different type that this class is used with. See the .cpp file * for examples and where to add a new specialization. */ - ConstPolygonRef getVertexData(const PathType& path); + ConstPolygonRef getVertexData(const PathType path); }; +} + #endif //PATHORDER_H \ No newline at end of file From 9fae1bc8ef1178a22d55575ea07089893f6b723d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 13:23:03 +0200 Subject: [PATCH 08/52] Filter loops for monotonic order Loops are not reordered. Instead, place all loops at the start of the range, causing them to print first. Contributes to issue CURA-7928. --- src/PathOrder.h | 28 ++++++++++++++++++++++++++++ src/PathOrderMonotonic.h | 10 ++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/PathOrder.h b/src/PathOrder.h index f3ac51017d..030e46c862 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -162,6 +162,34 @@ class PathOrder * for examples and where to add a new specialization. */ ConstPolygonRef getVertexData(const PathType path); + + /*! + * In the current set of paths, detect all loops and mark them as such. + * + * This will go through all polylines in the paths. If the endpoints of + * these polylines are close enough together, it considers them a polygon. + * It will then mark these polylines as being polygons for the future. + */ + void detectLoops() + { + constexpr coord_t coincident_point_distance = 10; //If the endpoints are closer together than 10 units, it is considered pretty much a closed loop. + for(Path& path : paths) + { + if(path.is_closed) //Already a polygon. No need to detect loops. + { + continue; + } + if(path.converted->size() < 3) //Not enough vertices to really be a closed loop. + { + continue; + } + if(vSize2(path.converted.back() - path.converted->front()) < coincident_point_distance * coincident_point_distance) + { + //Endpoints are really close to one another. Consider it a closed loop. + path.is_closed = true; + } + } + } }; } diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 6a3eb6bbbe..84c5bccb25 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -34,6 +34,16 @@ template class PathOrderMonotonic : public PathOrder { public: + void optimize() + { + //First print all the looping polygons, if there are any. + detectLoops(); //Always filter out loops. We don't specifically want to print those in monotonic order. + const auto polylines_start = std::partition(paths.begin(), paths.end(), [](const Path& path) { + return path.is_closed; + }); + //Now we only need to reorder paths from polylines_start to the end. + //TODO. + } }; } From 1822257ed1083cff591caf1925363ffaf44e1a3d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 13:40:11 +0200 Subject: [PATCH 09/52] Accept monotonic direction in constructor of PathOrderMonotonic This will be the direction in which we'll try to sort the paths. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 84c5bccb25..3795dc7930 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -4,6 +4,10 @@ #ifndef PATHORDERMONOTONIC_H #define PATHORDERMONOTONIC_H +#include //For std::sin() and std::cos(). + +#include "PathOrder.h" + namespace cura { @@ -34,6 +38,10 @@ template class PathOrderMonotonic : public PathOrder { public: + PathOrderMonotonic(const AngleRadians monotonic_direction) + : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) + {} + void optimize() { //First print all the looping polygons, if there are any. @@ -44,6 +52,16 @@ class PathOrderMonotonic : public PathOrder //Now we only need to reorder paths from polylines_start to the end. //TODO. } + +protected: + /*! + * The direction in which to print montonically, encoded as vector of length + * 1000. + * + * The resulting ordering will cause clusters of paths to be sorted + * according to their projection on this vector. + */ + Point monotonic_vector; }; } From 015efae592a51cf1fb254766aa7dcd2ac27c3c2d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 13:57:21 +0200 Subject: [PATCH 10/52] Fix getting members of parent class Since this class is a template class, the code is compiled before some real types are known. The parent class is one of those parts that is still unknown since it is a dependent type. So to fix that, explicitly say that we're using these members from the parent class. Contributes to issue CURA-7928. --- src/PathOrder.h | 2 +- src/PathOrderMonotonic.h | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/PathOrder.h b/src/PathOrder.h index 030e46c862..ac9a4b3358 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -183,7 +183,7 @@ class PathOrder { continue; } - if(vSize2(path.converted.back() - path.converted->front()) < coincident_point_distance * coincident_point_distance) + if(vSize2(path.converted->back() - path.converted->front()) < coincident_point_distance * coincident_point_distance) { //Endpoints are really close to one another. Consider it a closed loop. path.is_closed = true; diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 3795dc7930..be77ce6887 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -37,6 +37,10 @@ namespace cura template class PathOrderMonotonic : public PathOrder { + using typename PathOrder::Path; + using PathOrder::paths; + using PathOrder::detectLoops; + public: PathOrderMonotonic(const AngleRadians monotonic_direction) : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) From 33397d22078a047b15158c1300a6c7122e74986c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 14:58:28 +0200 Subject: [PATCH 11/52] Don't use partition to split polylines and polygons The std::partition function doesn't work for this since it requires std::move to be implemented for the Path inner class. We can't implement std::move there because Path contains the PathType template argument, which can be ConstPolygonRef which is const, not moveable. Contributes to issue CURA-7928. --- src/PathOrder.h | 8 ++++---- src/PathOrderMonotonic.h | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/PathOrder.h b/src/PathOrder.h index ac9a4b3358..af18261dd7 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -48,10 +48,10 @@ class PathOrder * invalidating the pointers. */ Path(const PathType& vertices, const bool is_closed = false, const size_t start_vertex = 0, const bool backwards = false) - : vertices(vertices) - , start_vertex(start_vertex) - , is_closed(is_closed) - , backwards(backwards) + : vertices(vertices) + , start_vertex(start_vertex) + , is_closed(is_closed) + , backwards(backwards) {} /*! diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index be77ce6887..031118d347 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -37,24 +37,41 @@ namespace cura template class PathOrderMonotonic : public PathOrder { +public: using typename PathOrder::Path; using PathOrder::paths; using PathOrder::detectLoops; -public: PathOrderMonotonic(const AngleRadians monotonic_direction) : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) {} void optimize() { + std::vector reordered; //To store the result in. At the end, we'll std::swap with the real paths. + reordered.reserve(paths.size()); + //First print all the looping polygons, if there are any. detectLoops(); //Always filter out loops. We don't specifically want to print those in monotonic order. - const auto polylines_start = std::partition(paths.begin(), paths.end(), [](const Path& path) { - return path.is_closed; - }); + for(const Path& path : paths) + { + if(path.is_closed) + { + reordered.push_back(path); + } + } //Now we only need to reorder paths from polylines_start to the end. + //TODO. + for(const Path& path : paths) + { + if(!path.is_closed) + { + reordered.push_back(path); + } + } + + std::swap(reordered, paths); //Store the resulting list in the main paths. } protected: From 85915cd1ddce1465906b388a25d405c9a12f5da7 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 15:39:57 +0200 Subject: [PATCH 12/52] Store vertex data in paths before starting to optimise We'll need this in order to optimise. I could put this tiny loop into a function for slightly easier calling, but otherwise I don't really see an easy way to pull this out to the parent class so that the implementer doesn't have to remember to do this. Or... maybe putting it in the addPolyline and addPolygon function would work? Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 031118d347..17179254a8 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -41,6 +41,7 @@ class PathOrderMonotonic : public PathOrder using typename PathOrder::Path; using PathOrder::paths; using PathOrder::detectLoops; + using PathOrder::getVertexData; PathOrderMonotonic(const AngleRadians monotonic_direction) : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) @@ -48,6 +49,17 @@ class PathOrderMonotonic : public PathOrder void optimize() { + if(paths.empty()) + { + return; + } + + //Get the vertex data and store it in the paths. + for(Path& path : paths) + { + path.converted = getVertexData(path.vertices); + } + std::vector reordered; //To store the result in. At the end, we'll std::swap with the real paths. reordered.reserve(paths.size()); From 70b39272cbcf06bcbf79c7e4e7c5dff329fc189c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 16:07:21 +0200 Subject: [PATCH 13/52] Implement ordering paths based on projection on vector The basis for monotonic ordering is that lines get ordered monotonically in a particular direction. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 17179254a8..aa2d9ebbdb 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -64,23 +64,35 @@ class PathOrderMonotonic : public PathOrder reordered.reserve(paths.size()); //First print all the looping polygons, if there are any. + std::vector polylines; //Also find all polylines and store them in a vector that we can sort in-place without making copies all the time. detectLoops(); //Always filter out loops. We don't specifically want to print those in monotonic order. - for(const Path& path : paths) + for(Path& path : paths) { - if(path.is_closed) + if(path.is_closed || path.vertices.size() <= 1) { reordered.push_back(path); } + else + { + polylines.push_back(&path); + } } - //Now we only need to reorder paths from polylines_start to the end. - //TODO. - for(const Path& path : paths) + //Sort the polylines by their projection on the monotonic vector. + std::sort(polylines.begin(), polylines.end(), [this](Path* a, Path* b) { + const coord_t a_start_projection = dot(a->converted->front(), monotonic_vector); + const coord_t a_end_projection = dot(a->converted->back(), monotonic_vector); + const coord_t a_projection = std::min(a_start_projection, a_end_projection); //The projection of a path is the endpoint furthest back of the two endpoints. + + const coord_t b_start_projection = dot(b->converted->front(), monotonic_vector); + const coord_t b_end_projection = dot(b->converted->back(), monotonic_vector); + const coord_t b_projection = std::min(b_start_projection, b_end_projection); + + return a_projection < b_projection; + }); + for(Path* polyline : polylines) { - if(!path.is_closed) - { - reordered.push_back(path); - } + reordered.push_back(*polyline); } std::swap(reordered, paths); //Store the resulting list in the main paths. From 8e418ef215c218e0c28061a9f29cca26dfa838ee Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 16:58:30 +0200 Subject: [PATCH 14/52] Create sequences of monotonic parts These sequences are streaks where adjacent lines are overlapping. Currently this still has a problem: Some parts of sequences are printed multiple times. We'll need to filter those out. Shouldn't be too hard. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 60 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index aa2d9ebbdb..9176f69f8a 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -5,6 +5,8 @@ #define PATHORDERMONOTONIC_H #include //For std::sin() and std::cos(). +#include //To track starting points of monotonic sequences. +#include //To track monotonic sequences. #include "PathOrder.h" @@ -90,9 +92,65 @@ class PathOrderMonotonic : public PathOrder return a_projection < b_projection; }); + + //Find out which lines overlap with which adjacent lines, when projected perpendicularly to the monotonic vector. + //Create a DAG structure of overlapping lines. + const Point perpendicular = turn90CCW(monotonic_vector); + + std::unordered_set unconnected_polylines; //Polylines that haven't been overlapped yet by a previous line. + unconnected_polylines.insert(polylines.begin(), polylines.end()); + std::unordered_map connections; //For each polyline, which polyline it overlaps with, closest in the projected order. + + for(auto polyline_it = polylines.begin(); polyline_it != polylines.end(); polyline_it++) + { + coord_t my_start = dot((*polyline_it)->converted->front(), perpendicular); + coord_t my_end = dot((*polyline_it)->converted->back(), perpendicular); + if(my_start > my_end) + { + std::swap(my_start, my_end); + } + //Find the next polyline this line overlaps with. + //Lines are already sorted, so the first overlapping line we encounter is the next closest line to overlap with. + for(auto overlapping_line = polyline_it + 1; overlapping_line != polylines.end(); overlapping_line++) + { + //Does this one overlap? + coord_t their_start = dot((*overlapping_line)->converted->front(), perpendicular); + coord_t their_end = dot((*overlapping_line)->converted->back(), perpendicular); + if(their_start > their_end) + { + std::swap(their_start, their_end); + } + if( (my_start > their_start && my_start < their_end) //It overlaps if any endpoint is between the endpoints of the other line. + || (my_end > their_start && my_end < their_end) + || (their_start > my_start && their_start < my_end) + || (their_end > my_start && their_end < my_end)) + { + connections.emplace(*polyline_it, *overlapping_line); + const auto is_unconnected = unconnected_polylines.find(*overlapping_line); + if(is_unconnected != unconnected_polylines.end()) + { + unconnected_polylines.erase(is_unconnected); //The overlapping line is now connected. + } + break; + } + } + } + + //Now that we know which lines overlap with which other lines, iterate over them again to print connected lines in order. for(Path* polyline : polylines) { - reordered.push_back(*polyline); + if(unconnected_polylines.find(polyline) == unconnected_polylines.end()) //Polyline is reached through another line. + { + continue; + } + reordered.push_back(*polyline); //Add the start of the connected sequence. + auto connection = connections.find(polyline); + while(connection != connections.end()) + { + polyline = connection->second; + reordered.push_back(*polyline); + connection = connections.find(polyline); + } } std::swap(reordered, paths); //Store the resulting list in the main paths. From ca53d3becb0040e34a97454639b8bdf4a740b14e Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 27 Jul 2021 17:06:37 +0200 Subject: [PATCH 15/52] Don't re-print parts of sequences that have already been printed We track which paths have already been printed in a set. If it's already in the set, then don't print them again. Otherwise, print them and add them to the set. A set like this is a rather expensive method to add an extra boolean flag to the Path struct... But it does separate concerns better. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 9176f69f8a..f6ff5a13a4 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -137,18 +137,26 @@ class PathOrderMonotonic : public PathOrder } //Now that we know which lines overlap with which other lines, iterate over them again to print connected lines in order. + std::unordered_set completed_lines; + completed_lines.reserve(polylines.size()); for(Path* polyline : polylines) { if(unconnected_polylines.find(polyline) == unconnected_polylines.end()) //Polyline is reached through another line. { continue; } - reordered.push_back(*polyline); //Add the start of the connected sequence. + reordered.push_back(*polyline); //Plan the start of the connected sequence to be printed next! + completed_lines.insert(polyline); auto connection = connections.find(polyline); while(connection != connections.end()) { polyline = connection->second; - reordered.push_back(*polyline); + if(completed_lines.find(polyline) != completed_lines.end()) //Already printed the rest of this sequence. + { + break; + } + reordered.push_back(*polyline); //Plan in this line to be printed next! + completed_lines.insert(polyline); connection = connections.find(polyline); } } From 008ccd8dd9be506b9f41924cc67e7bc2d1176d5c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Jul 2021 15:20:17 +0200 Subject: [PATCH 16/52] Optimise closest start point for individual polylines The order of the polylines is monotonic, but the direction of them will be optimised such that they print with minimal travel moves. This won't work properly for actual polylines such as the zigzag pattern. For those we'll have to find an alternative solution. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 56 +++++++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index f6ff5a13a4..5858be6f88 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -41,34 +41,33 @@ class PathOrderMonotonic : public PathOrder { public: using typename PathOrder::Path; - using PathOrder::paths; - using PathOrder::detectLoops; - using PathOrder::getVertexData; - PathOrderMonotonic(const AngleRadians monotonic_direction) + PathOrderMonotonic(const AngleRadians monotonic_direction, const Point start_point) : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) - {} + { + this->start_point = start_point; + } void optimize() { - if(paths.empty()) + if(this->paths.empty()) { return; } //Get the vertex data and store it in the paths. - for(Path& path : paths) + for(Path& path : this->paths) { - path.converted = getVertexData(path.vertices); + path.converted = this->getVertexData(path.vertices); } std::vector reordered; //To store the result in. At the end, we'll std::swap with the real paths. - reordered.reserve(paths.size()); + reordered.reserve(this->paths.size()); //First print all the looping polygons, if there are any. std::vector polylines; //Also find all polylines and store them in a vector that we can sort in-place without making copies all the time. - detectLoops(); //Always filter out loops. We don't specifically want to print those in monotonic order. - for(Path& path : paths) + this->detectLoops(); //Always filter out loops. We don't specifically want to print those in monotonic order. + for(Path& path : this->paths) { if(path.is_closed || path.vertices.size() <= 1) { @@ -139,12 +138,14 @@ class PathOrderMonotonic : public PathOrder //Now that we know which lines overlap with which other lines, iterate over them again to print connected lines in order. std::unordered_set completed_lines; completed_lines.reserve(polylines.size()); + Point current_pos = this->start_point; for(Path* polyline : polylines) { if(unconnected_polylines.find(polyline) == unconnected_polylines.end()) //Polyline is reached through another line. { continue; } + optimizeClosestStartPoint(*polyline, current_pos); reordered.push_back(*polyline); //Plan the start of the connected sequence to be printed next! completed_lines.insert(polyline); auto connection = connections.find(polyline); @@ -155,13 +156,14 @@ class PathOrderMonotonic : public PathOrder { break; } + optimizeClosestStartPoint(*polyline, current_pos); reordered.push_back(*polyline); //Plan in this line to be printed next! completed_lines.insert(polyline); connection = connections.find(polyline); } } - std::swap(reordered, paths); //Store the resulting list in the main paths. + std::swap(reordered, this->paths); //Store the resulting list in the main paths. } protected: @@ -173,6 +175,36 @@ class PathOrderMonotonic : public PathOrder * according to their projection on this vector. */ Point monotonic_vector; + + /*! + * For a given path, make sure that it is configured correctly to start + * printing from the best endpoint. + * + * This changes the path's ``start_vertex`` and ``backwards`` fields, and + * also adjusts the \ref current_pos in-place. + * + * Will cause a crash if given a path with 0 vertices! + * \param path The path to adjust the start and direction parameters for. + * \param current_pos The last position of the nozzle before printing this + * path. + */ + void optimizeClosestStartPoint(Path& path, Point& current_pos) + { + const coord_t dist_start = vSize2(current_pos - path.converted->front()); + const coord_t dist_end = vSize2(current_pos - path.converted->back()); + if(dist_start < dist_end) + { + path.start_vertex = 0; + path.backwards = false; + current_pos = path.converted->back(); + } + else + { + path.start_vertex = path.converted->size() - 1; + path.backwards = true; + current_pos = path.converted->front(); + } + } }; } From c207af6906b3cfacfbf8823da02013a8840307c6 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Jul 2021 16:12:04 +0200 Subject: [PATCH 17/52] Prevent segments from being added to the back of other segments If a long segment is printed first, and then a different segment is added on later to the back of the long segment (combining two 'legs' into one segment), then there will be a border where the lines are not printed in monotonic order. To prevent this, we really separate all of the segments including the joints of such segments. Where they join will start a new segment as well. Then each of these segments is printed in monotonic order. That way, no line will be printed before an adjacent line. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 58 +++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 5858be6f88..1a78b58cdb 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -98,6 +98,8 @@ class PathOrderMonotonic : public PathOrder std::unordered_set unconnected_polylines; //Polylines that haven't been overlapped yet by a previous line. unconnected_polylines.insert(polylines.begin(), polylines.end()); + std::unordered_set starting_lines; //Starting points of a linearly connected segment. + starting_lines.insert(polylines.begin(), polylines.end()); std::unordered_map connections; //For each polyline, which polyline it overlaps with, closest in the projected order. for(auto polyline_it = polylines.begin(); polyline_it != polylines.end(); polyline_it++) @@ -124,42 +126,50 @@ class PathOrderMonotonic : public PathOrder || (their_start > my_start && their_start < my_end) || (their_end > my_start && their_end < my_end)) { - connections.emplace(*polyline_it, *overlapping_line); const auto is_unconnected = unconnected_polylines.find(*overlapping_line); - if(is_unconnected != unconnected_polylines.end()) + if(is_unconnected == unconnected_polylines.end()) //It was already connected to another line. { + starting_lines.insert(*overlapping_line); //The overlapping line is a junction where two segments come together. Make it possible to start from there. + } + else + { + connections.emplace(*polyline_it, *overlapping_line); unconnected_polylines.erase(is_unconnected); //The overlapping line is now connected. + starting_lines.erase(*overlapping_line); //If it was a starting line, it is no longer. It is now a later line in a sequence. } break; } } } - //Now that we know which lines overlap with which other lines, iterate over them again to print connected lines in order. - std::unordered_set completed_lines; - completed_lines.reserve(polylines.size()); + //Order the starting points of each segments monotonically. This is the order in which to print each segment. + std::vector starting_lines_monotonic; + starting_lines_monotonic.resize(starting_lines.size()); + std::partial_sort_copy(starting_lines.begin(), starting_lines.end(), starting_lines_monotonic.begin(), starting_lines_monotonic.end(), [this](Path* a, Path* b) { + const coord_t a_start_projection = dot(a->converted->front(), monotonic_vector); + const coord_t a_end_projection = dot(a->converted->back(), monotonic_vector); + const coord_t a_projection = std::min(a_start_projection, a_end_projection); //The projection of a path is the endpoint furthest back of the two endpoints. + + const coord_t b_start_projection = dot(b->converted->front(), monotonic_vector); + const coord_t b_end_projection = dot(b->converted->back(), monotonic_vector); + const coord_t b_projection = std::min(b_start_projection, b_end_projection); + + return a_projection < b_projection; + }); + + //Now that we have the segments of overlapping lines, and know in which order to print the segments, print segments in monotonic order. Point current_pos = this->start_point; - for(Path* polyline : polylines) + for(Path* line : starting_lines_monotonic) { - if(unconnected_polylines.find(polyline) == unconnected_polylines.end()) //Polyline is reached through another line. + optimizeClosestStartPoint(*line, current_pos); + reordered.push_back(*line); //Plan the start of the sequence to be printed next! + auto connection = connections.find(line); + while(connection != connections.end() && starting_lines.find(connection->second) == starting_lines.end()) //Stop if the sequence ends or if we hit another starting point. { - continue; - } - optimizeClosestStartPoint(*polyline, current_pos); - reordered.push_back(*polyline); //Plan the start of the connected sequence to be printed next! - completed_lines.insert(polyline); - auto connection = connections.find(polyline); - while(connection != connections.end()) - { - polyline = connection->second; - if(completed_lines.find(polyline) != completed_lines.end()) //Already printed the rest of this sequence. - { - break; - } - optimizeClosestStartPoint(*polyline, current_pos); - reordered.push_back(*polyline); //Plan in this line to be printed next! - completed_lines.insert(polyline); - connection = connections.find(polyline); + line = connection->second; + optimizeClosestStartPoint(*line, current_pos); + reordered.push_back(*line); //Plan this line in, to be printed next! + connection = connections.find(line); } } From bbe4ddcb8c0da2c8142aa9fd785337179211e8a3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 28 Jul 2021 17:15:01 +0200 Subject: [PATCH 18/52] Print skin lines monotonically instead of optimally if enabled There's now a new function addLinesMonotonic, to be interchanged with addLinesByOptimizer. The function will add lines printed monotonically instead of optimising the travel moves. Use that new function for skin lines, if the setting for that is enabled. Contributes to issue CURA-7928. --- src/FffGcodeWriter.cpp | 50 ++++++++++++++++++++++++----------- src/LayerPlan.cpp | 60 ++++++++++++++++++++++++++++++++++++++++++ src/LayerPlan.h | 2 ++ 3 files changed, 96 insertions(+), 16 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 30086cc783..3ecb00f823 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2509,26 +2509,44 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La gcode_layer.addPolygonsByOptimizer(skin_polygons, config); } - std::optional near_start_location; - const EFillMethod pattern = (gcode_layer.getLayerNr() == 0) ? - mesh.settings.get("top_bottom_pattern_0") : - mesh.settings.get("top_bottom_pattern"); - if (pattern == EFillMethod::LINES || pattern == EFillMethod::ZIG_ZAG) - { // update near_start_location to a location which tries to avoid seams in skin - near_start_location = getSeamAvoidingLocation(area, skin_angle, gcode_layer.getLastPlannedPositionOrStartingPosition()); - } - - constexpr bool enable_travel_optimization = false; - constexpr float flow = 1.0; - if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) + if(mesh.settings.get("skin_monotonic")) { - gcode_layer.addLinesByOptimizer(skin_lines, config, SpaceFillType::Lines, enable_travel_optimization, mesh.settings.get("infill_wipe_dist"), flow, near_start_location, fan_speed); + const AngleRadians monotonic_direction = (skin_angle + 90) / 180.0 * M_PI; + constexpr Ratio flow = 1.0_r; + if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) + { + gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, mesh.settings.get("infill_wipe_dist"), flow, fan_speed, monotonic_direction); + } + else + { + const SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; + constexpr coord_t wipe_dist = 0; + gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, wipe_dist, flow, fan_speed, monotonic_direction); + } } else { - SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; - constexpr coord_t wipe_dist = 0; - gcode_layer.addLinesByOptimizer(skin_lines, config, space_fill_type, enable_travel_optimization, wipe_dist, flow, near_start_location, fan_speed); + std::optional near_start_location; + const EFillMethod pattern = (gcode_layer.getLayerNr() == 0) ? + mesh.settings.get("top_bottom_pattern_0") : + mesh.settings.get("top_bottom_pattern"); + if (pattern == EFillMethod::LINES || pattern == EFillMethod::ZIG_ZAG) + { // update near_start_location to a location which tries to avoid seams in skin + near_start_location = getSeamAvoidingLocation(area, skin_angle, gcode_layer.getLastPlannedPositionOrStartingPosition()); + } + + constexpr bool enable_travel_optimization = false; + constexpr float flow = 1.0; + if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) + { + gcode_layer.addLinesByOptimizer(skin_lines, config, SpaceFillType::Lines, enable_travel_optimization, mesh.settings.get("infill_wipe_dist"), flow, near_start_location, fan_speed); + } + else + { + SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; + constexpr coord_t wipe_dist = 0; + gcode_layer.addLinesByOptimizer(skin_lines, config, space_fill_type, enable_travel_optimization, wipe_dist, flow, near_start_location, fan_speed); + } } } } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index e1962f1536..3ee8a85447 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -7,6 +7,7 @@ #include "ExtruderTrain.h" #include "LayerPlan.h" #include "MergeInfillLines.h" +#include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. #include "raft.h" // getTotalExtraLayers #include "Slice.h" #include "sliceDataStorage.h" @@ -1124,6 +1125,65 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, const GCodePathCon } } +void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction) +{ + const Point last_position = getLastPlannedPositionOrStartingPosition(); + PathOrderMonotonic order(monotonic_direction, last_position); + for(size_t line_idx = 0; line_idx < polygons.size(); ++line_idx) + { + order.addPolyline(polygons[line_idx]); + } + order.optimize(); + + for (unsigned int order_idx = 0; order_idx < order.paths.size(); order_idx++) + { + const PathOrder::Path& path = order.paths[order_idx]; + ConstPolygonRef polygon = *path.vertices; + const size_t start = path.start_vertex; + const size_t end = 1 - start; + const Point& p0 = polygon[start]; + const Point& p1 = polygon[end]; + // ignore line segments that are less than 5uM long + if(vSize2(p1 - p0) < MINIMUM_SQUARED_LINE_LENGTH) + { + continue; + } + addTravel(p0); + addExtrusionMove(p1, config, space_fill_type, flow_ratio, false, 1.0, fan_speed); + + // Wipe + if (wipe_dist != 0) + { + bool wipe = true; + int line_width = config.getLineWidth(); + + // Don't wipe is current extrusion is too small + if (vSize2(p1 - p0) <= line_width * line_width * 4) + { + wipe = false; + } + + // Don't wipe if next starting point is very near + if (wipe && (order_idx < order.paths.size() - 1)) + { + const PathOrder::Path& next_path = order.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]; + if (vSize2(next_p0 - p1) <= line_width * line_width * 4) + { + wipe = false; + } + } + + if (wipe) + { + addExtrusionMove(p1 + normal(p1-p0, wipe_dist), config, space_fill_type, 0.0, false, 1.0, fan_speed); + } + } + } +} + void LayerPlan::spiralizeWallSlice(const GCodePathConfig& config, ConstPolygonRef wall, ConstPolygonRef last_wall, const int seam_vertex_idx, const int last_seam_vertex_idx, const bool is_top_layer, const bool is_bottom_layer) { const bool smooth_contours = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("smooth_spiralized_contours"); diff --git a/src/LayerPlan.h b/src/LayerPlan.h index 6794a2a17b..06367b781a 100644 --- a/src/LayerPlan.h +++ b/src/LayerPlan.h @@ -551,6 +551,8 @@ class LayerPlan : public NoCopy */ void addPolygonsByOptimizer(const Polygons& polygons, const GCodePathConfig& config, WallOverlapComputation* wall_overlap_computation = nullptr, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, const std::optional start_near_location = std::optional()); + void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction); + /*! * Add a single line that is part of a wall to the gcode. * \param p0 The start vertex of the line From 7c0d3fa74ef29b962540ec6c8ab17e32c27da3cb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Jul 2021 13:56:35 +0200 Subject: [PATCH 19/52] Fix edge case failing to detect lines that exactly overlap There was an edge case not being caught when the two endpoints project to exactly the same points on the perpendicular vector. Since we were using < and > instead of <= and >=, we never detected that case of overlap then. I've streamlined the conditions a bit. It should be completely accurate now. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 1a78b58cdb..e75c9e3101 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -121,10 +121,15 @@ class PathOrderMonotonic : public PathOrder { std::swap(their_start, their_end); } - if( (my_start > their_start && my_start < their_end) //It overlaps if any endpoint is between the endpoints of the other line. + /*There are 5 possible cases of overlapping: + - We are behind them, partially overlapping. my_start is between their_start and their_end. + - We are in front of them, partially overlapping. my_end is between their_start and their_end. + - We are a smaller line, they completely overlap us. Both my_start and my_end are between their_start and their_end. (Caught with the first 2 conditions already.) + - We are a bigger line, and completely overlap them. Both their_start and their_end are between my_start and my_end. + - Lines are exactly equal. Start and end are the same. (Caught with the previous condition too.)*/ + if( (my_start > their_start && my_start < their_end) || (my_end > their_start && my_end < their_end) - || (their_start > my_start && their_start < my_end) - || (their_end > my_start && their_end < my_end)) + || (their_start >= my_start && their_end <= my_end)) { const auto is_unconnected = unconnected_polylines.find(*overlapping_line); if(is_unconnected == unconnected_polylines.end()) //It was already connected to another line. From 8eb7eb7be065c169624ad6743b29a37041ea409c Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Jul 2021 15:00:08 +0200 Subject: [PATCH 20/52] Don't connect lines that are next to each other, but spaced far apart We only want to constrain the order for lines that actually touch. If they are more than 110% line width apart, they are now considered to not touch any more. This prevents opposing pieces of skin from being printed monotonically too. Contributes to issue CURA-7928. --- src/FffGcodeWriter.cpp | 4 ++-- src/LayerPlan.cpp | 6 +++--- src/LayerPlan.h | 4 ++-- src/PathOrderMonotonic.h | 24 +++++++++++++++++++++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 3ecb00f823..548daef524 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2515,13 +2515,13 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La constexpr Ratio flow = 1.0_r; if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) { - gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, mesh.settings.get("infill_wipe_dist"), flow, fan_speed, monotonic_direction); + gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, mesh.settings.get("infill_wipe_dist"), flow, fan_speed, monotonic_direction, config.getLineWidth()); } else { const SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; constexpr coord_t wipe_dist = 0; - gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, wipe_dist, flow, fan_speed, monotonic_direction); + gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, wipe_dist, flow, fan_speed, monotonic_direction, config.getLineWidth()); } } else diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 3ee8a85447..fa8fc25c46 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include @@ -1125,10 +1125,10 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, const GCodePathCon } } -void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction) +void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction, const coord_t max_line_width) { const Point last_position = getLastPlannedPositionOrStartingPosition(); - PathOrderMonotonic order(monotonic_direction, last_position); + PathOrderMonotonic order(monotonic_direction, max_line_width * 1.1, last_position); //Allow sequencing lines of up to 1.1 times line width, to allow some leeway with rounding. for(size_t line_idx = 0; line_idx < polygons.size(); ++line_idx) { order.addPolyline(polygons[line_idx]); diff --git a/src/LayerPlan.h b/src/LayerPlan.h index 06367b781a..4dfb03d187 100644 --- a/src/LayerPlan.h +++ b/src/LayerPlan.h @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef LAYER_PLAN_H @@ -551,7 +551,7 @@ class LayerPlan : public NoCopy */ void addPolygonsByOptimizer(const Polygons& polygons, const GCodePathConfig& config, WallOverlapComputation* wall_overlap_computation = nullptr, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, const std::optional start_near_location = std::optional()); - void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction); + void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction, const coord_t max_line_width); /*! * Add a single line that is part of a wall to the gcode. diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index e75c9e3101..92bef0e986 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -42,8 +42,9 @@ class PathOrderMonotonic : public PathOrder public: using typename PathOrder::Path; - PathOrderMonotonic(const AngleRadians monotonic_direction, const Point start_point) + PathOrderMonotonic(const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const Point start_point) : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) + , max_adjacent_distance(max_adjacent_distance) { this->start_point = start_point; } @@ -104,6 +105,10 @@ class PathOrderMonotonic : public PathOrder for(auto polyline_it = polylines.begin(); polyline_it != polylines.end(); polyline_it++) { + const coord_t start_projection = dot((*polyline_it)->converted->front(), monotonic_vector); + const coord_t end_projection = dot((*polyline_it)->converted->back(), monotonic_vector); + const coord_t farthest_projection = std::max(start_projection, end_projection); + coord_t my_start = dot((*polyline_it)->converted->front(), perpendicular); coord_t my_end = dot((*polyline_it)->converted->back(), perpendicular); if(my_start > my_end) @@ -114,6 +119,15 @@ class PathOrderMonotonic : public PathOrder //Lines are already sorted, so the first overlapping line we encounter is the next closest line to overlap with. for(auto overlapping_line = polyline_it + 1; overlapping_line != polylines.end(); overlapping_line++) { + //Don't go beyond the maximum adjacent distance. + const coord_t start_their_projection = dot((*overlapping_line)->converted->front(), monotonic_vector); + const coord_t end_their_projection = dot((*overlapping_line)->converted->back(), monotonic_vector); + const coord_t closest_projection = std::min(start_their_projection, end_their_projection); + if(closest_projection - farthest_projection > max_adjacent_distance * 1000) //Distances are multiplied by 1000 since monotonic_vector is not a unit vector, but a vector of length 1000. + { + break; //Too far. This line and all subsequent lines are not adjacent any more, even though they might be side-by-side. + } + //Does this one overlap? coord_t their_start = dot((*overlapping_line)->converted->front(), perpendicular); coord_t their_end = dot((*overlapping_line)->converted->back(), perpendicular); @@ -191,6 +205,14 @@ class PathOrderMonotonic : public PathOrder */ Point monotonic_vector; + /*! + * Maximum distance at which lines are considered to be adjacent. + * + * The monotonicity constraint is only held for lines that are closer than + * this distance together. + */ + coord_t max_adjacent_distance; + /*! * For a given path, make sure that it is configured correctly to start * printing from the best endpoint. From cc55005dac1d1ad999babb614fb0198d2f62d4c3 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Jul 2021 15:24:18 +0200 Subject: [PATCH 21/52] Figure out the line width from the config yourself Instead of providing that as an extra parameter, it's already in the config really. Contributes to issue CURA-7928. --- src/FffGcodeWriter.cpp | 4 ++-- src/LayerPlan.cpp | 4 ++-- src/LayerPlan.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 548daef524..3ecb00f823 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2515,13 +2515,13 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La constexpr Ratio flow = 1.0_r; if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) { - gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, mesh.settings.get("infill_wipe_dist"), flow, fan_speed, monotonic_direction, config.getLineWidth()); + gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, mesh.settings.get("infill_wipe_dist"), flow, fan_speed, monotonic_direction); } else { const SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; constexpr coord_t wipe_dist = 0; - gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, wipe_dist, flow, fan_speed, monotonic_direction, config.getLineWidth()); + gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, wipe_dist, flow, fan_speed, monotonic_direction); } } else diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index fa8fc25c46..9188318cbf 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1125,10 +1125,10 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, const GCodePathCon } } -void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction, const coord_t max_line_width) +void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction) { const Point last_position = getLastPlannedPositionOrStartingPosition(); - PathOrderMonotonic order(monotonic_direction, max_line_width * 1.1, last_position); //Allow sequencing lines of up to 1.1 times line width, to allow some leeway with rounding. + PathOrderMonotonic order(monotonic_direction, config.getLineWidth() * 1.1, last_position); //Allow sequencing lines of up to 1.1 times line width, to allow some leeway with rounding. for(size_t line_idx = 0; line_idx < polygons.size(); ++line_idx) { order.addPolyline(polygons[line_idx]); diff --git a/src/LayerPlan.h b/src/LayerPlan.h index 4dfb03d187..00b4c06620 100644 --- a/src/LayerPlan.h +++ b/src/LayerPlan.h @@ -551,7 +551,7 @@ class LayerPlan : public NoCopy */ void addPolygonsByOptimizer(const Polygons& polygons, const GCodePathConfig& config, WallOverlapComputation* wall_overlap_computation = nullptr, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, const std::optional start_near_location = std::optional()); - void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction, const coord_t max_line_width); + void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction); /*! * Add a single line that is part of a wall to the gcode. From 61b105884c76f88b336ef193c14179e2303b0066 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Jul 2021 15:37:38 +0200 Subject: [PATCH 22/52] Provide option to perform ironing monotonically This doesn't work well yet since the monotonic order doesn't seem to deal well with the zigzag pattern yet. We'll have to see about that next. Contributes to issue CURA-7928. --- src/FffGcodeWriter.cpp | 4 ++-- src/LayerPlan.cpp | 4 ++-- src/LayerPlan.h | 2 +- src/TopSurface.cpp | 9 ++++++++- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 3ecb00f823..efe07de32e 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2515,13 +2515,13 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La constexpr Ratio flow = 1.0_r; if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) { - gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, mesh.settings.get("infill_wipe_dist"), flow, fan_speed, monotonic_direction); + gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, monotonic_direction, config.getLineWidth() * 1.1, mesh.settings.get("infill_wipe_dist"), flow, fan_speed); } else { const SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; constexpr coord_t wipe_dist = 0; - gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, wipe_dist, flow, fan_speed, monotonic_direction); + gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, monotonic_direction, config.getLineWidth() * 1.1, wipe_dist, flow, fan_speed); } } else diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9188318cbf..6e2754225b 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1125,10 +1125,10 @@ void LayerPlan::addLinesByOptimizer(const Polygons& polygons, const GCodePathCon } } -void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction) +void LayerPlan::addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed) { const Point last_position = getLastPlannedPositionOrStartingPosition(); - PathOrderMonotonic order(monotonic_direction, config.getLineWidth() * 1.1, last_position); //Allow sequencing lines of up to 1.1 times line width, to allow some leeway with rounding. + PathOrderMonotonic order(monotonic_direction, max_adjacent_distance, last_position); for(size_t line_idx = 0; line_idx < polygons.size(); ++line_idx) { order.addPolyline(polygons[line_idx]); diff --git a/src/LayerPlan.h b/src/LayerPlan.h index 00b4c06620..41dbde2344 100644 --- a/src/LayerPlan.h +++ b/src/LayerPlan.h @@ -551,7 +551,7 @@ class LayerPlan : public NoCopy */ void addPolygonsByOptimizer(const Polygons& polygons, const GCodePathConfig& config, WallOverlapComputation* wall_overlap_computation = nullptr, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, const std::optional start_near_location = std::optional()); - void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const coord_t wipe_dist, const Ratio flow_ratio, const double fan_speed, const AngleRadians monotonic_direction); + void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0_r, const double fan_speed = 100.0); /*! * Add a single line that is part of a wall to the gcode. diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 5754cc4d07..6e2f1dcd1a 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -122,7 +122,14 @@ bool TopSurface::ironing(const SliceMeshStorage& mesh, const GCodePathConfig& li } } - layer.addLinesByOptimizer(ironing_lines, line_config, SpaceFillType::PolyLines); + if(!mesh.settings.get("ironing_monotonic")) + { + layer.addLinesByOptimizer(ironing_lines, line_config, SpaceFillType::PolyLines); + } + else + { + layer.addLinesMonotonic(ironing_lines, line_config, SpaceFillType::PolyLines, (direction + 90) / 180.0 * M_PI, line_spacing * 1.1); + } added = true; } From 87e62e0a57bf59896f6e993be81aa696c4deece5 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Thu, 29 Jul 2021 16:17:02 +0200 Subject: [PATCH 23/52] Allow enabling monotonic separately for top surface You might just want to print the top surface monotonically. That's the only one it really matters for, honestly. Contributes to issue CURA-7928. --- src/FffGcodeWriter.cpp | 10 ++++++---- src/FffGcodeWriter.h | 4 +++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index efe07de32e..4cb0a550f7 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2297,7 +2297,8 @@ void FffGcodeWriter::processRoofing(const SliceDataStorage& storage, LayerPlan& const Ratio skin_density = 1.0; const coord_t skin_overlap = 0; // skinfill already expanded over the roofing areas; don't overlap with perimeters Polygons* perimeter_gaps_output = (fill_perimeter_gaps) ? &concentric_perimeter_gaps : nullptr; - processSkinPrintFeature(storage, gcode_layer, mesh, extruder_nr, skin_part.roofing_fill, mesh_config.roofing_config, pattern, roofing_angle, skin_overlap, skin_density, perimeter_gaps_output, added_something); + const bool monotonic = mesh.settings.get("roofing_monotonic"); + processSkinPrintFeature(storage, gcode_layer, mesh, extruder_nr, skin_part.roofing_fill, mesh_config.roofing_config, pattern, roofing_angle, skin_overlap, skin_density, monotonic, perimeter_gaps_output, added_something); } void FffGcodeWriter::processTopBottom(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const PathConfigStorage::MeshPathConfigs& mesh_config, const SkinPart& skin_part, Polygons& concentric_perimeter_gaps, bool& added_something) const @@ -2465,10 +2466,11 @@ void FffGcodeWriter::processTopBottom(const SliceDataStorage& storage, LayerPlan // calculate polygons and lines Polygons* perimeter_gaps_output = (generate_perimeter_gaps) ? &concentric_perimeter_gaps : nullptr; - processSkinPrintFeature(storage, gcode_layer, mesh, extruder_nr, skin_part.inner_infill, *skin_config, pattern, skin_angle, skin_overlap, skin_density, perimeter_gaps_output, added_something, fan_speed); + const bool monotonic = mesh.settings.get("skin_monotonic"); + processSkinPrintFeature(storage, gcode_layer, mesh, extruder_nr, skin_part.inner_infill, *skin_config, pattern, skin_angle, skin_overlap, skin_density, monotonic, perimeter_gaps_output, added_something, fan_speed); } -void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, const Ratio skin_density, Polygons* perimeter_gaps_output, bool& added_something, double fan_speed) const +void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, const Ratio skin_density, const bool monotonic, Polygons* perimeter_gaps_output, bool& added_something, double fan_speed) const { Polygons skin_polygons; Polygons skin_lines; @@ -2509,7 +2511,7 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La gcode_layer.addPolygonsByOptimizer(skin_polygons, config); } - if(mesh.settings.get("skin_monotonic")) + if(monotonic) { const AngleRadians monotonic_direction = (skin_angle + 90) / 180.0 * M_PI; constexpr Ratio flow = 1.0_r; diff --git a/src/FffGcodeWriter.h b/src/FffGcodeWriter.h index e0804d07e1..30b457f5f3 100644 --- a/src/FffGcodeWriter.h +++ b/src/FffGcodeWriter.h @@ -581,11 +581,13 @@ class FffGcodeWriter : public NoCopy * \param skin_angle the angle to use for linear infill types * \param skin_overlap The amount by which to expand the \p area * \param skin density Sets the density of the the skin lines by adjusting the distance between them (normal skin is 1.0) + * \param monotonic Whether to order lines monotonically (``true``) or to + * minimise travel moves (``false``). * \param[out] perimeter_gaps_output Optional output to store the gaps which occur if the pattern is concentric * \param[out] added_something Whether this function added anything to the layer plan * \param fan_speed fan speed override for this skin area */ - void processSkinPrintFeature(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, const Ratio skin_density, Polygons* perimeter_gaps_output, bool& added_something, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT) const; + void processSkinPrintFeature(const SliceDataStorage& storage, LayerPlan& gcode_layer, const SliceMeshStorage& mesh, const size_t extruder_nr, const Polygons& area, const GCodePathConfig& config, EFillMethod pattern, const AngleDegrees skin_angle, const coord_t skin_overlap, const Ratio skin_density, const bool monotonic, Polygons* perimeter_gaps_output, bool& added_something, double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT) const; /*! * Add perimeter gaps of a mesh with the given extruder. From 828912761c88e5b1ee42bf0fccc73cac0eeb5224 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 29 Jul 2021 16:56:51 +0200 Subject: [PATCH 24/52] Basis for case-tests for monotonic fill. CURA-7928 --- CMakeLists.txt | 3 +- src/utils/polygon.h | 5 + tests/PathOrderMonotonicTest.cpp | 261 +++++++++++++++++++++++++++++++ 3 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 tests/PathOrderMonotonicTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e8313a58fd..5786934cdd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,10 +226,9 @@ set(engine_TEST InfillTest LayerPlanTest MergeInfillLinesTest + PathOrderMonotonicTest TimeEstimateCalculatorTest ) -set(engine_TEST_INFILL -) set(engine_TEST_INTEGRATION SlicePhaseTest ) diff --git a/src/utils/polygon.h b/src/utils/polygon.h index 17633c1a26..357d21e1e7 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -381,6 +381,11 @@ class PolygonRef : public ConstPolygonRef return (*path)[index]; } + const Point& operator[] (const unsigned int& index) const + { + return path->at(index); + } + ClipperLib::Path::iterator begin() { return path->begin(); diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp new file mode 100644 index 0000000000..768695fa56 --- /dev/null +++ b/tests/PathOrderMonotonicTest.cpp @@ -0,0 +1,261 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#include + +#include +#include + +#include "../src/infill.h" +#include "../src/utils/math.h" +#include "../src/PathOrderMonotonic.h" +#include "../src/utils/polygon.h" +#include "ReadTestPolygons.h" + +#define TEST_PATHS_SVG_OUTPUT +#ifdef TEST_PATHS_SVG_OUTPUT +#include +#include "../src/utils/SVG.h" +#endif //TEST_PATHS_SVG_OUTPUT + +namespace cura +{ + /* Fixture to allow parameterized tests. + */ + class PathOrderMonotonicTest : public testing::TestWithParam> + {}; + + coord_t projectPathAlongAxis(const PathOrderMonotonic::Path& path, const Point& vector) + { + return dot(path.vertices[path.start_vertex], vector); + } + + coord_t projectEndAlongAxis(const PathOrderMonotonic::Path& path, const Point& vector) + { + return dot(path.vertices[path.vertices.size() - (1 + path.start_vertex)], vector); + } + + bool rangeOverlaps(const std::pair& range_b, const std::pair& range_a) + { + const coord_t len_b = std::abs(range_b.first - range_b.second); + const coord_t len_a = std::abs(range_a.first - range_a.second); + const coord_t len_total = std::max({ range_b.first, range_b.second, range_a.first, range_a.second }) + - std::min({ range_b.first, range_b.second, range_a.first, range_a.second }); + return len_total < (len_b + len_a); + } + + bool getInfillLines(const std::string& filename, const AngleRadians& angle, Polygons& output) + { + constexpr EFillMethod pattern = EFillMethod::LINES; + constexpr bool zig_zagify = false; + constexpr bool connect_polygons = false; + constexpr coord_t line_distance = 350; + constexpr coord_t outline_offset = 0; + constexpr coord_t infill_line_width = 350; + constexpr coord_t infill_overlap = 0; + constexpr size_t infill_multiplier = 1; + constexpr coord_t z = 2; + constexpr coord_t shift = 0; + constexpr coord_t max_resolution = 10; + constexpr coord_t max_deviation = 5; + + std::vector shapes; + if (!readTestPolygons(filename, shapes)) + { + return false; + } + + Polygons dummy_polys; + for (const auto& shape : shapes) + { + Infill infill_comp + ( + pattern, + zig_zagify, + connect_polygons, + shape, + outline_offset, + infill_line_width, + line_distance, + infill_overlap, + infill_multiplier, + AngleDegrees(angle), + z, + shift, + max_resolution, + max_deviation + ); + infill_comp.generate(dummy_polys, output); + } + return true; + } + +#ifdef TEST_PATHS_SVG_OUTPUT + void writeDebugSVG + ( + const std::string& original_filename, + const AngleRadians& angle, + const Point& monotonic_vec, + const std::vector::Path>>& sections + ) + { + constexpr int buff_size = 1024; + char buff[buff_size]; + const size_t xx = original_filename.find_first_of('_'); + std::string basename = original_filename.substr(xx, original_filename.find_last_of('.') - xx); + std::snprintf(buff, buff_size, "C:/bob/%s_%d.svg", basename.c_str(), (int) AngleDegrees(angle)); + const std::string filename(buff); + + AABB aabb; + for (const auto& section : sections) + { + for (const auto& path : section) + { + aabb.include(path.vertices[path.start_vertex]); + } + } + aabb.include(Point{0, 0}); + aabb.include(monotonic_vec); + + SVG svgFile(filename.c_str(), aabb); + + int color_id = -1; + for (const auto& section : sections) + { + ++color_id; + SVG::Color section_color{ (SVG::Color) (((int) SVG::Color::GRAY) + (color_id % 7)) }; + for (const auto& path : section) + { + svgFile.writePolyline(path.vertices, section_color); + } + } + svgFile.writeArrow(Point{ 0, 0 }, monotonic_vec, SVG::Color::BLACK); + // Note: SVG writes 'itself' when the object is destroyed. + } +#endif //TEST_PATHS_SVG_OUTPUT + + TEST_P(PathOrderMonotonicTest, SectionsTest) + { + const auto params = GetParam(); + const double angle_radians{ std::get<1>(params) }; + const auto& filename = std::get<0>(params); + Polygons polylines; + ASSERT_TRUE(getInfillLines(filename, angle_radians, polylines)) << "Input test-file could not be read, check setup."; + + const Point& pt_r = polylines.begin()->at(0); + const Point& pt_s = polylines.begin()->at(1); + const double angle_from_first_line = std::atan2(pt_s.Y - pt_r.Y, pt_s.X - pt_r.X) + 0.5 * M_PI; + const Point monotonic_axis{ std::cos(angle_from_first_line) * 1000, std::sin(angle_from_first_line) * 1000 }; + const Point perpendicular_axis{ turn90CCW(monotonic_axis) }; + + PathOrderMonotonic object_under_test(angle_from_first_line, monotonic_axis * -1000); + for (const auto& polyline : polylines) + { + object_under_test.addPolyline(polyline); + } + object_under_test.optimize(); + + // Collect sections: + std::vector::Path>> sections; + sections.emplace_back(); + coord_t last_path_mono_projection = projectPathAlongAxis(object_under_test.paths.front(), monotonic_axis); + for (const auto& path : object_under_test.paths) + { + const coord_t path_mono_projection{ projectPathAlongAxis(path, monotonic_axis) }; + if (path_mono_projection < last_path_mono_projection && ! sections.back().empty()) + { + sections.emplace_back(); + } + sections.back().push_back(path); + last_path_mono_projection = path_mono_projection; + } + +#ifdef TEST_PATHS_SVG_OUTPUT + writeDebugSVG(filename, angle_radians, monotonic_axis, sections); +#endif //TEST_PATHS_SVG_OUTPUT + + // Each section that intersects another section on the monotonic axis, + // needs to --for that overlapping (sub-)section-- _not_ overlap for the perpendicular axis. + // Each section that _doesn't_ intersect another on the monotonic axis, + // the earlier section has to have a lower starting point on that axis then the later one. + size_t section_a_id = 0; + for (const auto& section_a : sections) + { + ++section_a_id; + size_t section_b_id = 0; + for (const auto& section_b : sections) + { + ++section_b_id; + if (section_a_id >= section_b_id) + { + continue; // <-- So section B will always be 'later' than section A. + } + + // Check if the start of A is lower than the start of B, since it is ordered first. + const coord_t mono_a{ projectPathAlongAxis(section_a.front(), monotonic_axis) }; + const coord_t mono_b{ projectPathAlongAxis(section_b.front(), monotonic_axis) }; + EXPECT_LE(mono_a, mono_b) + << "Section ordered before another, A's start point should be before B when ordered along the monotonic axis."; + + // 'Neighbouring' lines should only overlap when projected to the perpendicular axis if they're from the same section. + // (This is technically not true in general, + // but the gap would have to be small enough for the neighbouring lines to touch; this won't really happen in practice.) + // Already tested for A start < B start in the monotonic direction, + // so assume A begins before B, so there is either no overlap, B lies 'witin' A, or B stops later than A. + auto it_a = section_a.begin(); + for (auto it_b = section_b.begin(); it_b != section_b.end(); ++it_b) + { + const coord_t mono_b = projectPathAlongAxis(*it_b, monotonic_axis); + for (; it_a != section_a.end() && projectPathAlongAxis(*it_a, monotonic_axis) <= mono_b; ++it_a) {} + const std::pair perp_b_range + { + projectPathAlongAxis(*it_b, perpendicular_axis), + projectEndAlongAxis(*it_b, perpendicular_axis) + }; + if (it_a == section_a.end()) + { + break; + } + + // Compare current line in B against next neighbouring line in A: + const std::pair perp_a_range + { + projectPathAlongAxis(*it_a, perpendicular_axis), + projectEndAlongAxis(*it_a, perpendicular_axis) + }; + EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range)) + << "Perpendicular range overlaps for neighbouring lines in different sections (next line of A / line in B)."; + + // Compare current line in B against previous neighbouring line in A: + if (it_a != section_a.begin() && it_b != section_b.begin()) + { + const std::pair perp_prev_a_range + { + projectPathAlongAxis(*std::prev(it_a), perpendicular_axis), + projectEndAlongAxis(*std::prev(it_a), perpendicular_axis) + }; + EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_prev_a_range)) + << "Perpendicular range overlaps for neighbouring lines in different sections (prev. line of A / line in B)."; + } + } + } + } + } + + const std::vector polygon_filenames = + { + "../tests/resources/polygon_concave.txt", + "../tests/resources/polygon_concave_hole.txt", + "../tests/resources/polygon_square.txt", + "../tests/resources/polygon_square_hole.txt", + "../tests/resources/polygon_triangle.txt", + "../tests/resources/polygon_two_squares.txt", + //"../tests/resources/polygon_slant_gap.txt", // TODO! + //"../tests/resources/polygon_sawtooth.txt", // TODO! + }; + const std::vector angle_radians = { 0, 0.01, 1.0, 0.5 * M_PI, M_PI, 1.5 * M_PI, 5.0, (2.0 * M_PI) - 0.01 }; + + INSTANTIATE_TEST_CASE_P(PathOrderMonotonicTestInstantiation, PathOrderMonotonicTest, + testing::Combine(testing::ValuesIn(polygon_filenames), testing::ValuesIn(angle_radians))); + +} // namespace cura From 3ea291dde117ff2633c9a8219b0deb8e3f1cdd25 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 30 Jul 2021 10:49:53 +0200 Subject: [PATCH 25/52] Main case-test for path-order monotonic almost done. Just need to make shortest distance work, then have some more specific unit-tests. CURA-7928 --- tests/PathOrderMonotonicTest.cpp | 111 ++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 39 deletions(-) diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index 768695fa56..b6db432a3c 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -25,14 +25,24 @@ namespace cura class PathOrderMonotonicTest : public testing::TestWithParam> {}; + inline Point startVertex(const PathOrderMonotonic::Path& path) + { + return path.vertices[path.start_vertex]; + } + + inline Point endVertex(const PathOrderMonotonic::Path& path) + { + return path.vertices[path.vertices.size() - (1 + path.start_vertex)]; + } + coord_t projectPathAlongAxis(const PathOrderMonotonic::Path& path, const Point& vector) { - return dot(path.vertices[path.start_vertex], vector); + return dot(startVertex(path), vector); } coord_t projectEndAlongAxis(const PathOrderMonotonic::Path& path, const Point& vector) { - return dot(path.vertices[path.vertices.size() - (1 + path.start_vertex)], vector); + return dot(endVertex(path), vector); } bool rangeOverlaps(const std::pair& range_b, const std::pair& range_a) @@ -44,21 +54,26 @@ namespace cura return len_total < (len_b + len_a); } - bool getInfillLines(const std::string& filename, const AngleRadians& angle, Polygons& output) + coord_t shortestDistance(const PathOrderMonotonic::Path& path_a, const PathOrderMonotonic::Path& path_b) { - constexpr EFillMethod pattern = EFillMethod::LINES; - constexpr bool zig_zagify = false; - constexpr bool connect_polygons = false; - constexpr coord_t line_distance = 350; - constexpr coord_t outline_offset = 0; - constexpr coord_t infill_line_width = 350; - constexpr coord_t infill_overlap = 0; - constexpr size_t infill_multiplier = 1; - constexpr coord_t z = 2; - constexpr coord_t shift = 0; - constexpr coord_t max_resolution = 10; - constexpr coord_t max_deviation = 5; + // NOTE: Assume these are more or less lines. + return std::numeric_limits::max(); // TODO! + } + constexpr EFillMethod pattern = EFillMethod::LINES; + constexpr bool zig_zagify = false; + constexpr bool connect_polygons = false; + constexpr coord_t line_distance = 350; + constexpr coord_t outline_offset = 0; + constexpr coord_t infill_line_width = 350; + constexpr coord_t infill_overlap = 0; + constexpr size_t infill_multiplier = 1; + constexpr coord_t z = 2; + constexpr coord_t shift = 0; + constexpr coord_t max_resolution = 10; + constexpr coord_t max_deviation = 5; + bool getInfillLines(const std::string& filename, const AngleRadians& angle, Polygons& output) + { std::vector shapes; if (!readTestPolygons(filename, shapes)) { @@ -111,7 +126,8 @@ namespace cura { for (const auto& path : section) { - aabb.include(path.vertices[path.start_vertex]); + aabb.include(startVertex(path.vertices)); + aabb.include(endVertex(path.vertices)); } } aabb.include(Point{0, 0}); @@ -148,7 +164,8 @@ namespace cura const Point monotonic_axis{ std::cos(angle_from_first_line) * 1000, std::sin(angle_from_first_line) * 1000 }; const Point perpendicular_axis{ turn90CCW(monotonic_axis) }; - PathOrderMonotonic object_under_test(angle_from_first_line, monotonic_axis * -1000); + constexpr coord_t max_adjacent_distance = line_distance + 1; + PathOrderMonotonic object_under_test(angle_from_first_line, max_adjacent_distance, monotonic_axis * -1000); for (const auto& polyline : polylines) { object_under_test.addPolyline(polyline); @@ -194,7 +211,7 @@ namespace cura // Check if the start of A is lower than the start of B, since it is ordered first. const coord_t mono_a{ projectPathAlongAxis(section_a.front(), monotonic_axis) }; const coord_t mono_b{ projectPathAlongAxis(section_b.front(), monotonic_axis) }; - EXPECT_LE(mono_a, mono_b) + EXPECT_LE(mono_a, mono_b) << "Section ordered before another, A's start point should be before B when ordered along the monotonic axis."; // 'Neighbouring' lines should only overlap when projected to the perpendicular axis if they're from the same section. @@ -214,28 +231,29 @@ namespace cura }; if (it_a == section_a.end()) { - break; + if (it_b == section_b.begin()) + { + // A is wholly before B in the monotonic direction, should A and B have been merged? + it_a = std::prev(it_a); // end of section A + const std::pair perp_a_range + { + projectPathAlongAxis(*it_a, perpendicular_axis), + projectEndAlongAxis(*it_a, perpendicular_axis) + }; + EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range) && shortestDistance(*it_a, *it_b) < max_adjacent_distance) + << "Sections A and B should have been one section, printed continuosly"; + } } - - // Compare current line in B against next neighbouring line in A: - const std::pair perp_a_range - { - projectPathAlongAxis(*it_a, perpendicular_axis), - projectEndAlongAxis(*it_a, perpendicular_axis) - }; - EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range)) - << "Perpendicular range overlaps for neighbouring lines in different sections (next line of A / line in B)."; - - // Compare current line in B against previous neighbouring line in A: - if (it_a != section_a.begin() && it_b != section_b.begin()) + else { - const std::pair perp_prev_a_range + // A and B intersect in monotonic direction, check if they overlap in the perpendicular direction: + const std::pair perp_a_range { - projectPathAlongAxis(*std::prev(it_a), perpendicular_axis), - projectEndAlongAxis(*std::prev(it_a), perpendicular_axis) + projectPathAlongAxis(*it_a, perpendicular_axis), + projectEndAlongAxis(*it_a, perpendicular_axis) }; - EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_prev_a_range)) - << "Perpendicular range overlaps for neighbouring lines in different sections (prev. line of A / line in B)."; + EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range)) + << "Perpendicular range overlaps for neighbouring lines in different sections (next line of A / line in B)."; } } } @@ -250,10 +268,25 @@ namespace cura "../tests/resources/polygon_square_hole.txt", "../tests/resources/polygon_triangle.txt", "../tests/resources/polygon_two_squares.txt", - //"../tests/resources/polygon_slant_gap.txt", // TODO! - //"../tests/resources/polygon_sawtooth.txt", // TODO! + "../tests/resources/polygon_slant_gap.txt", + "../tests/resources/polygon_sawtooth.txt", + }; + const std::vector angle_radians = + { + 0, + 0.1, + 0.25 * M_PI, + 1.0, + 0.5 * M_PI, + 0.75 * M_PI, + M_PI, + 1.25 * M_PI, + 4.0, + 1.5 * M_PI, + 1.75 * M_PI, + 5.0, + (2.0 * M_PI) - 0.1 }; - const std::vector angle_radians = { 0, 0.01, 1.0, 0.5 * M_PI, M_PI, 1.5 * M_PI, 5.0, (2.0 * M_PI) - 0.01 }; INSTANTIATE_TEST_CASE_P(PathOrderMonotonicTestInstantiation, PathOrderMonotonicTest, testing::Combine(testing::ValuesIn(polygon_filenames), testing::ValuesIn(angle_radians))); From 77039483badfea7e2fec2b3bb4aa9fc5318b9dd6 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 30 Jul 2021 14:31:05 +0200 Subject: [PATCH 26/52] Test path order monotonic: finish up big test. Added optimality check. There shouldn't be too much sections, even if they're tehcnically monotonic. CURA-7928 --- tests/PathOrderMonotonicTest.cpp | 64 ++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index b6db432a3c..5e02190f8b 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -7,12 +7,13 @@ #include #include "../src/infill.h" +#include "../src/utils/linearAlg2D.h" #include "../src/utils/math.h" #include "../src/PathOrderMonotonic.h" #include "../src/utils/polygon.h" #include "ReadTestPolygons.h" -#define TEST_PATHS_SVG_OUTPUT +//#define TEST_PATHS_SVG_OUTPUT #ifdef TEST_PATHS_SVG_OUTPUT #include #include "../src/utils/SVG.h" @@ -54,10 +55,22 @@ namespace cura return len_total < (len_b + len_a); } - coord_t shortestDistance(const PathOrderMonotonic::Path& path_a, const PathOrderMonotonic::Path& path_b) + coord_t shortestDistance + ( + const PathOrderMonotonic::Path& path_a, + const PathOrderMonotonic::Path& path_b + ) + { + // NOTE: Assume these are more or less lines. + const auto point_pair = + LinearAlg2D::getClosestConnection(startVertex(path_a), endVertex(path_a), startVertex(path_b), endVertex(path_b)); + return vSize(point_pair.second - point_pair.first); + } + + coord_t pathLength(const PathOrderMonotonic::Path& path) { // NOTE: Assume these are more or less lines. - return std::numeric_limits::max(); // TODO! + return vSize(endVertex(path) - startVertex(path)); } constexpr EFillMethod pattern = EFillMethod::LINES; @@ -118,7 +131,7 @@ namespace cura char buff[buff_size]; const size_t xx = original_filename.find_first_of('_'); std::string basename = original_filename.substr(xx, original_filename.find_last_of('.') - xx); - std::snprintf(buff, buff_size, "C:/bob/%s_%d.svg", basename.c_str(), (int) AngleDegrees(angle)); + std::snprintf(buff, buff_size, "/tmp/%s_%d.svg", basename.c_str(), (int) AngleDegrees(angle)); const std::string filename(buff); AABB aabb; @@ -191,14 +204,13 @@ namespace cura writeDebugSVG(filename, angle_radians, monotonic_axis, sections); #endif //TEST_PATHS_SVG_OUTPUT - // Each section that intersects another section on the monotonic axis, - // needs to --for that overlapping (sub-)section-- _not_ overlap for the perpendicular axis. - // Each section that _doesn't_ intersect another on the monotonic axis, - // the earlier section has to have a lower starting point on that axis then the later one. + std::unordered_map, size_t> split_section_counts_per_split_line; + size_t section_a_id = 0; for (const auto& section_a : sections) { ++section_a_id; + size_t section_b_id = 0; for (const auto& section_b : sections) { @@ -214,16 +226,13 @@ namespace cura EXPECT_LE(mono_a, mono_b) << "Section ordered before another, A's start point should be before B when ordered along the monotonic axis."; - // 'Neighbouring' lines should only overlap when projected to the perpendicular axis if they're from the same section. - // (This is technically not true in general, - // but the gap would have to be small enough for the neighbouring lines to touch; this won't really happen in practice.) // Already tested for A start < B start in the monotonic direction, // so assume A begins before B, so there is either no overlap, B lies 'witin' A, or B stops later than A. auto it_a = section_a.begin(); for (auto it_b = section_b.begin(); it_b != section_b.end(); ++it_b) { const coord_t mono_b = projectPathAlongAxis(*it_b, monotonic_axis); - for (; it_a != section_a.end() && projectPathAlongAxis(*it_a, monotonic_axis) <= mono_b; ++it_a) {} + for (; it_a != section_a.end() && projectPathAlongAxis(*it_a, monotonic_axis) < mono_b; ++it_a) {} const std::pair perp_b_range { projectPathAlongAxis(*it_b, perpendicular_axis), @@ -231,17 +240,36 @@ namespace cura }; if (it_a == section_a.end()) { + // A is wholly before B in the monotonic direction, test if A and B should indeed have been different sections: if (it_b == section_b.begin()) { - // A is wholly before B in the monotonic direction, should A and B have been merged? it_a = std::prev(it_a); // end of section A const std::pair perp_a_range { projectPathAlongAxis(*it_a, perpendicular_axis), projectEndAlongAxis(*it_a, perpendicular_axis) }; - EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range) && shortestDistance(*it_a, *it_b) < max_adjacent_distance) - << "Sections A and B should have been one section, printed continuosly"; + if (rangeOverlaps(perp_b_range, perp_a_range) && shortestDistance(*it_a, *it_b) <= max_adjacent_distance) + { + // This is only wrong if there is no split, so no 3rd or more section that ends at the same line, + // so collect those lines. + + // Take the longer line: + const std::pair line = pathLength(*it_a) > pathLength(*it_b) ? + std::make_pair(startVertex(*it_a), endVertex(*it_a)) : + std::make_pair(startVertex(*it_b), endVertex(*it_b)); + + // Collect the edges of the sections that split before that line: + if (split_section_counts_per_split_line.count(line) == 0) + { + split_section_counts_per_split_line[line] = 0; + } + ++split_section_counts_per_split_line[line]; + } + } + else + { + break; } } else @@ -258,6 +286,12 @@ namespace cura } } } + + // If there is a line where a section ends, and only one other section begins, then they should've been 1 section to begin with: + for (const auto& line_count_pair : split_section_counts_per_split_line) + { + EXPECT_GE(line_count_pair.second, 2) << "A section was split up while it could have been printed monotonically."; + } } const std::vector polygon_filenames = From afeb608fdedc951f7b8a26edd5f779eec57c5948 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Fri, 30 Jul 2021 14:58:46 +0200 Subject: [PATCH 27/52] Test Monotonic fill: Big test done(?) Also seems the polygon resources had not been added to the previous commits yet. CURA-7928 --- tests/PathOrderMonotonicTest.cpp | 1 + tests/resources/polygon_letter_y.txt | 17 +++++++++++++++++ tests/resources/polygon_sawtooth.txt | 19 +++++++++++++++++++ tests/resources/polygon_slant_gap.txt | 12 ++++++++++++ 4 files changed, 49 insertions(+) create mode 100644 tests/resources/polygon_letter_y.txt create mode 100644 tests/resources/polygon_sawtooth.txt create mode 100644 tests/resources/polygon_slant_gap.txt diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index 5e02190f8b..421419497b 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -304,6 +304,7 @@ namespace cura "../tests/resources/polygon_two_squares.txt", "../tests/resources/polygon_slant_gap.txt", "../tests/resources/polygon_sawtooth.txt", + "../tests/resources/polygon_letter_y.txt" }; const std::vector angle_radians = { diff --git a/tests/resources/polygon_letter_y.txt b/tests/resources/polygon_letter_y.txt new file mode 100644 index 0000000000..29f39baa74 --- /dev/null +++ b/tests/resources/polygon_letter_y.txt @@ -0,0 +1,17 @@ +v 10000 10000 +v 15000 18000 +v 20000 10000 +v 35000 50000 +v 50000 10000 +v 55000 18000 +v 60000 10000 +v 50000 60000 +v 50000 90000 +v 35000 70000 +v 15000 90000 +v 15000 80000 +v 20000 80000 +v 20000 60000 +# + +A letter y shape with some embelishments, a tree. diff --git a/tests/resources/polygon_sawtooth.txt b/tests/resources/polygon_sawtooth.txt new file mode 100644 index 0000000000..8522034e4d --- /dev/null +++ b/tests/resources/polygon_sawtooth.txt @@ -0,0 +1,19 @@ +v 10000 10000 +v 10000 90000 +v 90000 90000 +v 80000 60000 +v 70000 80000 +v 60000 60000 +v 50000 80000 +v 40000 60000 +v 30000 80000 +v 30000 20000 +v 40000 40000 +v 50000 20000 +v 60000 40000 +v 70000 20000 +v 80000 40000 +v 90000 10000 +# + +A sawtooth figure (in the concave hole of a square) with teeth opposing each other. diff --git a/tests/resources/polygon_slant_gap.txt b/tests/resources/polygon_slant_gap.txt new file mode 100644 index 0000000000..c9d66a0b92 --- /dev/null +++ b/tests/resources/polygon_slant_gap.txt @@ -0,0 +1,12 @@ +v 10000 10000 +v 10000 50000 +v 90000 50000 +v 90000 10000 +x +v 15000 15000 +v 85000 40000 +v 85000 40500 +v 15000 15500 +# + +A square with a very askew gap. From 4a323f67ee1ea5fad201515555b86375e9ff66b1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 02:34:42 +0200 Subject: [PATCH 28/52] Detect strings of polylines with incident endpoints This stuff is quite complex. We're now finding two types of sequences of polylines: The old case where polylines were adjacent to one another, and a new case where polylines have endpoints that are incident to one another. With the incident endpoints we're creating a string of polylines that are incident. We're then finding the endpoints of that string and optimising with which one to start to maintain monotonic ordering. At the same time we're still finding all overlaps of all lines, stringed or not. From that we're creating more sequences of the older type, and also marking starting lines in case a line overlaps with more than one other polyline. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 293 +++++++++++++++++++++++++++++++-------- 1 file changed, 232 insertions(+), 61 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 92bef0e986..da270f4280 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -13,6 +13,9 @@ namespace cura { +constexpr coord_t COINCIDENT_POINT_DISTANCE = 5; // In uM. Points closer than this may be considered overlapping / at the same place +constexpr coord_t SQUARED_COINCIDENT_POINT_DISTANCE = COINCIDENT_POINT_DISTANCE * COINCIDENT_POINT_DISTANCE; + /*! * Class that orders paths monotonically. * @@ -77,10 +80,11 @@ class PathOrderMonotonic : public PathOrder else { polylines.push_back(&path); + polylines.back()->start_vertex = polylines.back()->converted->size(); //Assign an invalid starting vertex to indicate we don't know the starting point yet. } } - //Sort the polylines by their projection on the monotonic vector. + //Sort the polylines by their projection on the monotonic vector. This helps find adjacent lines quickly. std::sort(polylines.begin(), polylines.end(), [this](Path* a, Path* b) { const coord_t a_start_projection = dot(a->converted->front(), monotonic_vector); const coord_t a_end_projection = dot(a->converted->back(), monotonic_vector); @@ -92,71 +96,76 @@ class PathOrderMonotonic : public PathOrder return a_projection < b_projection; }); + //Create a bucket grid to be able to find adjacent lines quickly. + SparsePointGridInclusive line_bucket_grid(MM2INT(2)); //Grid size of 2mm. + for(Path* polyline : polylines) + { + if(polyline->converted->empty()) + { + continue; + } + else + { + line_bucket_grid.insert(polyline->converted->front(), polyline); + line_bucket_grid.insert(polyline->converted->back(), polyline); + } + } - //Find out which lines overlap with which adjacent lines, when projected perpendicularly to the monotonic vector. - //Create a DAG structure of overlapping lines. - const Point perpendicular = turn90CCW(monotonic_vector); + //Create sequences of line segments that get printed together in a monotonic direction. + //There are several constraints we impose here: + // - Strings of incident polylines are printed in sequence. That is, if their endpoints are incident. + // - The endpoint of the string that is earlier in the montonic direction will get printed first. + // - The start_vertex of this line will already be set to indicate where to start from. + // - If a line overlaps with another line in the perpendicular direction, and is within max_adjacent_distance (~1 line width) in the monotonic direction, it must be printed in monotonic order. + // - The earlier line is marked as being in sequence with the later line. + // - The later line is no longer a starting point, unless there are multiple adjacent lines before it. + //The ``starting_lines`` set indicates possible locations to start from. Each starting line represents one "sequence", which is either a set of adjacent line segments or a string of polylines. + //The ``connections`` map indicates, starting from each starting segment, the sequence of line segments to print in order. + //Note that for performance reasons, the ``connections`` map will sometimes link the end of one segment to the start of the next segment. This link should be ignored. + const Point perpendicular = turn90CCW(monotonic_vector); //To project on to detect adjacent lines. - std::unordered_set unconnected_polylines; //Polylines that haven't been overlapped yet by a previous line. - unconnected_polylines.insert(polylines.begin(), polylines.end()); + std::unordered_set connected_lines; //Lines that are reachable from one of the starting lines through its connections. std::unordered_set starting_lines; //Starting points of a linearly connected segment. - starting_lines.insert(polylines.begin(), polylines.end()); std::unordered_map connections; //For each polyline, which polyline it overlaps with, closest in the projected order. for(auto polyline_it = polylines.begin(); polyline_it != polylines.end(); polyline_it++) { - const coord_t start_projection = dot((*polyline_it)->converted->front(), monotonic_vector); - const coord_t end_projection = dot((*polyline_it)->converted->back(), monotonic_vector); - const coord_t farthest_projection = std::max(start_projection, end_projection); + //First find out if this polyline is part of a string of polylines. + std::deque polystring = findPolylineString(*polyline_it, line_bucket_grid, monotonic_vector); + std::vector overlapping_lines = getOverlappingLines(polyline_it, perpendicular, polylines); - coord_t my_start = dot((*polyline_it)->converted->front(), perpendicular); - coord_t my_end = dot((*polyline_it)->converted->back(), perpendicular); - if(my_start > my_end) - { - std::swap(my_start, my_end); - } - //Find the next polyline this line overlaps with. - //Lines are already sorted, so the first overlapping line we encounter is the next closest line to overlap with. - for(auto overlapping_line = polyline_it + 1; overlapping_line != polylines.end(); overlapping_line++) + //If we're part of a string of polylines, connect up the whole string and mark all of them as being connected. + if(polystring.size() > 1) { - //Don't go beyond the maximum adjacent distance. - const coord_t start_their_projection = dot((*overlapping_line)->converted->front(), monotonic_vector); - const coord_t end_their_projection = dot((*overlapping_line)->converted->back(), monotonic_vector); - const coord_t closest_projection = std::min(start_their_projection, end_their_projection); - if(closest_projection - farthest_projection > max_adjacent_distance * 1000) //Distances are multiplied by 1000 since monotonic_vector is not a unit vector, but a vector of length 1000. + starting_lines.insert(polystring[0]); + connected_lines.insert(polystring[0]); + for(size_t i = 0; i < polystring.size() - 1; ++i) //Iterate over every pair of adjacent polylines in the string (so skip the last one)! { - break; //Too far. This line and all subsequent lines are not adjacent any more, even though they might be side-by-side. + connections[polystring[i]] = polystring[i + 1]; + connected_lines.insert(polystring[i + 1]); + for(Path* overlapping_line : getOverlappingLines(std::find(polyline_it, polylines.end(), polystring[i]), perpendicular, polylines)) //Last one is done in the else case below. + { + if(std::find(polystring.begin(), polystring.end(), overlapping_line) == polystring.end()) //Mark all overlapping lines not part of the string as possible starting points. + { + starting_lines.insert(overlapping_line); + } + } } - - //Does this one overlap? - coord_t their_start = dot((*overlapping_line)->converted->front(), perpendicular); - coord_t their_end = dot((*overlapping_line)->converted->back(), perpendicular); - if(their_start > their_end) + } + else if(overlapping_lines.size() == 1) //If we're not a string of polylines, but adjacent to only one other polyline, create a sequence of polylines. + { + connections[*polyline_it] = overlapping_lines[0]; + connected_lines.insert(overlapping_lines[0]); + if(connected_lines.find(*polyline_it) == connected_lines.end()) //This line was not connected to yet. { - std::swap(their_start, their_end); + starting_lines.insert(*polyline_it); //Must be a starting point then (since all possibly connected lines before it are already processed). } - /*There are 5 possible cases of overlapping: - - We are behind them, partially overlapping. my_start is between their_start and their_end. - - We are in front of them, partially overlapping. my_end is between their_start and their_end. - - We are a smaller line, they completely overlap us. Both my_start and my_end are between their_start and their_end. (Caught with the first 2 conditions already.) - - We are a bigger line, and completely overlap them. Both their_start and their_end are between my_start and my_end. - - Lines are exactly equal. Start and end are the same. (Caught with the previous condition too.)*/ - if( (my_start > their_start && my_start < their_end) - || (my_end > their_start && my_end < their_end) - || (their_start >= my_start && their_end <= my_end)) + } + else //Either 0 (the for loop terminates immediately) or multiple overlapping lines. For multiple lines we need to mark all of them a starting position. + { + for(Path* overlapping_line : overlapping_lines) { - const auto is_unconnected = unconnected_polylines.find(*overlapping_line); - if(is_unconnected == unconnected_polylines.end()) //It was already connected to another line. - { - starting_lines.insert(*overlapping_line); //The overlapping line is a junction where two segments come together. Make it possible to start from there. - } - else - { - connections.emplace(*polyline_it, *overlapping_line); - unconnected_polylines.erase(is_unconnected); //The overlapping line is now connected. - starting_lines.erase(*overlapping_line); //If it was a starting line, it is no longer. It is now a later line in a sequence. - } - break; + starting_lines.insert(overlapping_line); } } } @@ -220,6 +229,9 @@ class PathOrderMonotonic : public PathOrder * This changes the path's ``start_vertex`` and ``backwards`` fields, and * also adjusts the \ref current_pos in-place. * + * If the path already had a ``start_vertex`` set, this will not be + * adjusted. Only the ``current_pos`` will be set then. + * * Will cause a crash if given a path with 0 vertices! * \param path The path to adjust the start and direction parameters for. * \param current_pos The last position of the nozzle before printing this @@ -227,21 +239,180 @@ class PathOrderMonotonic : public PathOrder */ void optimizeClosestStartPoint(Path& path, Point& current_pos) { - const coord_t dist_start = vSize2(current_pos - path.converted->front()); - const coord_t dist_end = vSize2(current_pos - path.converted->back()); - if(dist_start < dist_end) + if(path.start_vertex != path.converted->size()) { - path.start_vertex = 0; - path.backwards = false; - current_pos = path.converted->back(); + const coord_t dist_start = vSize2(current_pos - path.converted->front()); + const coord_t dist_end = vSize2(current_pos - path.converted->back()); + if(dist_start < dist_end) + { + path.start_vertex = 0; + path.backwards = false; + } + else + { + path.start_vertex = path.converted->size() - 1; + path.backwards = true; + } + } + current_pos = (*path.converted)[path.converted->size() - 1 - path.start_vertex]; //Opposite of the start vertex. + } + + /*! + * Some input contains line segments or polylines that are separate paths, + * but are still intended to be printed as a long sequence. This function + * finds such strings of polylines. + * \param polyline Any polyline which may be part of a string of polylines. + * \param line_bucket_grid A pre-computed bucket grid to allow quick look-up + * of which vertices are nearby. + * \return A list of polylines, in the order in which they should be + * printed. All paths in this string already have their start_vertex set + * correctly. + */ + std::deque findPolylineString(Path* polyline, const SparsePointGridInclusive& line_bucket_grid, const Point monotonic_vector) + { + std::deque result; + if(polyline->converted->empty()) + { + return result; + } + + //Find the two endpoints of the polyline string, on either side. + result.push_back(polyline); + Point first_endpoint = polyline->converted->front(); + Point last_endpoint = polyline->converted->back(); + std::vector lines_before = line_bucket_grid.getNearbyVals(first_endpoint, COINCIDENT_POINT_DISTANCE); + lines_before.erase(std::remove(lines_before.begin(), lines_before.end(), polyline), lines_before.end()); //Don't find yourself. + std::vector lines_after = line_bucket_grid.getNearbyVals(last_endpoint, COINCIDENT_POINT_DISTANCE); + lines_after.erase(std::remove(lines_after.begin(), lines_after.end(), polyline), lines_after.end()); + + while(!lines_before.empty()) + { + Path* first = lines_before.front(); + if(std::find(result.begin(), result.end(), first) != result.end()) //Did we get into a loop? + { + break; //Stop on the last one before we looped! + } + result.push_front(first); //Store this one in the sequence. It's a good one. + size_t farthest_vertex = getFarthestEndpoint(first, first_endpoint); //Get to the opposite side. + first->start_vertex = farthest_vertex; + first->backwards = farthest_vertex != 0; + first_endpoint = (*first->converted)[farthest_vertex]; + lines_before = line_bucket_grid.getNearbyVals(first_endpoint, COINCIDENT_POINT_DISTANCE); + lines_before.erase(std::remove(lines_before.begin(), lines_before.end(), first), lines_before.end()); //Don't find yourself. + } + while(!lines_after.empty()) + { + Path* last = lines_after.front(); + if(std::find(result.begin(), result.end(), last) != result.end()) //Did we get into a loop? + { + break; //Stop on the last one before we looped! + } + result.push_back(last); + size_t farthest_vertex = getFarthestEndpoint(last, last_endpoint); //Get to the opposite side. + last->start_vertex = (farthest_vertex == 0) ? last->converted->size() - 1 : 0; + last->backwards = (farthest_vertex != 0); + last_endpoint = (*last->converted)[farthest_vertex]; + lines_after = line_bucket_grid.getNearbyVals(last_endpoint, COINCIDENT_POINT_DISTANCE); + lines_after.erase(std::remove(lines_after.begin(), lines_after.end(), last), lines_after.end()); //Don't find yourself. + } + + //Figure out which of the two endpoints to start with: The one monotonically earliest. + const coord_t first_projection = dot(first_endpoint, monotonic_vector); + const coord_t last_projection = dot(last_endpoint, monotonic_vector); + //If the last endpoint should be printed first (unlikely due to monotonic start, but possible), flip the whole polyline! + if(last_projection < first_projection) + { + std::reverse(result.begin(), result.end()); + for(Path* path : result) //Also reverse their start_vertex. + { + path->start_vertex = (path->start_vertex == 0) ? path->converted->size() - 1 : 0; + path->backwards = !path->backwards; + } + } + + return result; + } + + /*! + * Get the endpoint of the polyline that is farthest away from the given + * point. + * \param polyline The polyline to get an endpoint of. + * \param point The point to get far away from. + * \return The vertex index of the endpoint that is farthest away. + */ + size_t getFarthestEndpoint(Path* polyline, const Point point) + { + const coord_t front_dist = vSize2(polyline->converted->front() - point); + const coord_t back_dist = vSize2(polyline->converted->back() - point); + if(front_dist < back_dist) + { + return polyline->converted->size() - 1; } else { - path.start_vertex = path.converted->size() - 1; - path.backwards = true; - current_pos = path.converted->front(); + return 0; } } + + /*! + * Find which lines are overlapping with a certain line. + * \param polyline_it The line with which to find overlaps. Given as an + * iterator into the sorted polylines list, to cut down on the search space. + * If the lines don't have too much overlap, this should result in only a + * handful of lines being searched at all. + * \param perpendicular A vector perpendicular to the monotonic vector, pre- + * calculated. + * \param polylines The sorted list of polylines. + */ + std::vector getOverlappingLines(const typename std::vector::iterator polyline_it, const Point perpendicular, const std::vector& polylines) + { + //How far this extends in the monotonic direction, to make sure we only go up to max_adjacent_distance in that direction. + const coord_t start_projection = dot((*polyline_it)->converted->front(), monotonic_vector); + const coord_t end_projection = dot((*polyline_it)->converted->back(), monotonic_vector); + const coord_t farthest_projection = std::max(start_projection, end_projection); + //How far this line reaches in the perpendicular direction -- the range at which the line overlaps other lines. + coord_t my_start = dot((*polyline_it)->converted->front(), perpendicular); + coord_t my_end = dot((*polyline_it)->converted->back(), perpendicular); + if(my_start > my_end) + { + std::swap(my_start, my_end); + } + + std::vector overlapping_lines; + for(auto overlapping_line = polyline_it + 1; overlapping_line != polylines.end(); overlapping_line++) + { + //Don't go beyond the maximum adjacent distance. + const coord_t start_their_projection = dot((*overlapping_line)->converted->front(), monotonic_vector); + const coord_t end_their_projection = dot((*overlapping_line)->converted->back(), monotonic_vector); + const coord_t closest_projection = std::min(start_their_projection, end_their_projection); + if(closest_projection - farthest_projection > max_adjacent_distance * 1000) //Distances are multiplied by 1000 since monotonic_vector is not a unit vector, but a vector of length 1000. + { + break; //Too far. This line and all subsequent lines are not adjacent any more, even though they might be side-by-side. + } + + //Does this one overlap? + coord_t their_start = dot((*overlapping_line)->converted->front(), perpendicular); + coord_t their_end = dot((*overlapping_line)->converted->back(), perpendicular); + if(their_start > their_end) + { + std::swap(their_start, their_end); + } + /*There are 5 possible cases of overlapping: + - We are behind them, partially overlapping. my_start is between their_start and their_end. + - We are in front of them, partially overlapping. my_end is between their_start and their_end. + - We are a smaller line, they completely overlap us. Both my_start and my_end are between their_start and their_end. (Caught with the first 2 conditions already.) + - We are a bigger line, and completely overlap them. Both their_start and their_end are between my_start and my_end. + - Lines are exactly equal. Start and end are the same. (Caught with the previous condition too.)*/ + if( (my_start > their_start && my_start < their_end) + || (my_end > their_start && my_end < their_end) + || (their_start >= my_start && their_end <= my_end)) + { + overlapping_lines.push_back(*overlapping_line); + } + } + + return overlapping_lines; //Uses RVO! + } }; } From 135a8db91f4e23cf9ce55dbcc26865e6df4412bc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 02:46:55 +0200 Subject: [PATCH 29/52] Don't re-connect lines that already have a connection Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index da270f4280..bff445d59a 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -130,6 +130,10 @@ class PathOrderMonotonic : public PathOrder for(auto polyline_it = polylines.begin(); polyline_it != polylines.end(); polyline_it++) { + if(connections.find(*polyline_it) != connections.end()) //Already connected this one through a polyline. + { + continue; + } //First find out if this polyline is part of a string of polylines. std::deque polystring = findPolylineString(*polyline_it, line_bucket_grid, monotonic_vector); std::vector overlapping_lines = getOverlappingLines(polyline_it, perpendicular, polylines); From 9f9294b77d44828299eb20cb05dbdf541f7047f0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 02:49:06 +0200 Subject: [PATCH 30/52] Also re-connect halfway through polyline string if it splits If a single polyline overlaps multiple other polylines, even if it is part of a string of polylines, we must be able to stop there and continue with the rest of the string later, in order to prevent non-monotonic ordering if parts interweave with these polyline strings. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index bff445d59a..9303355242 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -152,6 +152,7 @@ class PathOrderMonotonic : public PathOrder if(std::find(polystring.begin(), polystring.end(), overlapping_line) == polystring.end()) //Mark all overlapping lines not part of the string as possible starting points. { starting_lines.insert(overlapping_line); + starting_lines.insert(polystring[i + 1]); //Also be able to re-start from this point in the string. } } } From 30729286be4992bb4c7993ba51d667ff68a07751 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 03:18:42 +0200 Subject: [PATCH 31/52] Only find coincident points when using the grid locator The bucket grid apparently rounds the search space up to whole grid cells. It doesn't actually result in only finding points up to a certain distance. So we'll need to manually filter these results afterwards. The types get really complicated because of that, but with little possibility of shortening them due to needing to pass them to functions. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 9303355242..24597a4a94 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -285,14 +285,18 @@ class PathOrderMonotonic : public PathOrder result.push_back(polyline); Point first_endpoint = polyline->converted->front(); Point last_endpoint = polyline->converted->back(); - std::vector lines_before = line_bucket_grid.getNearbyVals(first_endpoint, COINCIDENT_POINT_DISTANCE); - lines_before.erase(std::remove(lines_before.begin(), lines_before.end(), polyline), lines_before.end()); //Don't find yourself. - std::vector lines_after = line_bucket_grid.getNearbyVals(last_endpoint, COINCIDENT_POINT_DISTANCE); - lines_after.erase(std::remove(lines_after.begin(), lines_after.end(), polyline), lines_after.end()); + std::vector> lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); + auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return found_path.val != polyline && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + }); + std::vector> lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); + auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return found_path.val != polyline && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + }); - while(!lines_before.empty()) + while(close_line_before != lines_before.end()) { - Path* first = lines_before.front(); + Path* first = close_line_before->val; if(std::find(result.begin(), result.end(), first) != result.end()) //Did we get into a loop? { break; //Stop on the last one before we looped! @@ -302,12 +306,14 @@ class PathOrderMonotonic : public PathOrder first->start_vertex = farthest_vertex; first->backwards = farthest_vertex != 0; first_endpoint = (*first->converted)[farthest_vertex]; - lines_before = line_bucket_grid.getNearbyVals(first_endpoint, COINCIDENT_POINT_DISTANCE); - lines_before.erase(std::remove(lines_before.begin(), lines_before.end(), first), lines_before.end()); //Don't find yourself. + lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); + close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return found_path.val != polyline && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + }); } - while(!lines_after.empty()) + while(close_line_after != lines_after.end()) { - Path* last = lines_after.front(); + Path* last = close_line_after->val; if(std::find(result.begin(), result.end(), last) != result.end()) //Did we get into a loop? { break; //Stop on the last one before we looped! @@ -317,8 +323,10 @@ class PathOrderMonotonic : public PathOrder last->start_vertex = (farthest_vertex == 0) ? last->converted->size() - 1 : 0; last->backwards = (farthest_vertex != 0); last_endpoint = (*last->converted)[farthest_vertex]; - lines_after = line_bucket_grid.getNearbyVals(last_endpoint, COINCIDENT_POINT_DISTANCE); - lines_after.erase(std::remove(lines_after.begin(), lines_after.end(), last), lines_after.end()); //Don't find yourself. + lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); + close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return found_path.val != polyline && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + }); } //Figure out which of the two endpoints to start with: The one monotonically earliest. From 8d2aa9b1314c2c62111bb35538b490cb977474f2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 10:52:21 +0200 Subject: [PATCH 32/52] Fix which paths we set the start_vertex of We need to calculate the start vertex if it is invalid, not if it is not invalid. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 24597a4a94..b5094edf1c 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -244,7 +244,7 @@ class PathOrderMonotonic : public PathOrder */ void optimizeClosestStartPoint(Path& path, Point& current_pos) { - if(path.start_vertex != path.converted->size()) + if(path.start_vertex == path.converted->size()) { const coord_t dist_start = vSize2(current_pos - path.converted->front()); const coord_t dist_end = vSize2(current_pos - path.converted->back()); From 420e2213f1d327e877837b27250d7bd52a938b5d Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 11:31:02 +0200 Subject: [PATCH 33/52] Fix marking starting points of sequences When working with sequences, there are two cases where we must start: - At the start of a sequence, when nothing connects to a line yet. If nothing connects to a line by the time we monotonically iterate to it, then nothing will overlap with the line any more so we must mark it as a starting point. Otherwise that sequence will not be printed. - When a sequence joins with another sequence, we must print both of those sequences before continuing with the joint sequence. So we must mark the start of the joint sequence as a starting point, so that we will stop before the joint, and can continue with the joint later. The third case, when a sequence splits into multiple sequences, is handled by the else case that handles having multiple overlapping lines. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index b5094edf1c..66e0386d75 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -160,10 +160,17 @@ class PathOrderMonotonic : public PathOrder else if(overlapping_lines.size() == 1) //If we're not a string of polylines, but adjacent to only one other polyline, create a sequence of polylines. { connections[*polyline_it] = overlapping_lines[0]; - connected_lines.insert(overlapping_lines[0]); - if(connected_lines.find(*polyline_it) == connected_lines.end()) //This line was not connected to yet. + if(connected_lines.find(*polyline_it) == connected_lines.end()) //Nothing connects to this line yet. { - starting_lines.insert(*polyline_it); //Must be a starting point then (since all possibly connected lines before it are already processed). + starting_lines.insert(*polyline_it); //This is a starting point then. + } + if(connected_lines.find(overlapping_lines[0]) != connected_lines.end()) //This line was already connected to. + { + starting_lines.insert(overlapping_lines[0]); //Multiple lines connect to it, so we must be able to start there. + } + else + { + connected_lines.insert(overlapping_lines[0]); } } else //Either 0 (the for loop terminates immediately) or multiple overlapping lines. For multiple lines we need to mark all of them a starting position. From 020c49d0db5ff97923eb6ca5c7bad1880b15a004 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 13:10:36 +0200 Subject: [PATCH 34/52] Fix broken detection of loops in polyline strings It was marking it as looping way too often because it was only trying the first adjacent line that wasn't itself. If we make it check all adjacent lines that works better. And it's the same performance, since we needed to check that anyway to detect loops in the first place. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 63 +++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 33 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 66e0386d75..135f292f29 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -136,48 +136,51 @@ class PathOrderMonotonic : public PathOrder } //First find out if this polyline is part of a string of polylines. std::deque polystring = findPolylineString(*polyline_it, line_bucket_grid, monotonic_vector); - std::vector overlapping_lines = getOverlappingLines(polyline_it, perpendicular, polylines); //If we're part of a string of polylines, connect up the whole string and mark all of them as being connected. if(polystring.size() > 1) { starting_lines.insert(polystring[0]); - connected_lines.insert(polystring[0]); for(size_t i = 0; i < polystring.size() - 1; ++i) //Iterate over every pair of adjacent polylines in the string (so skip the last one)! { connections[polystring[i]] = polystring[i + 1]; connected_lines.insert(polystring[i + 1]); - for(Path* overlapping_line : getOverlappingLines(std::find(polyline_it, polylines.end(), polystring[i]), perpendicular, polylines)) //Last one is done in the else case below. + const std::vector overlapping_lines = getOverlappingLines(std::find(polylines.begin(), polylines.end(), polystring[i]), perpendicular, polylines); + for(Path* overlapping_line : overlapping_lines) //Last one is done in the else case below. { if(std::find(polystring.begin(), polystring.end(), overlapping_line) == polystring.end()) //Mark all overlapping lines not part of the string as possible starting points. { starting_lines.insert(overlapping_line); - starting_lines.insert(polystring[i + 1]); //Also be able to re-start from this point in the string. + //starting_lines.insert(polystring[i + 1]); //Also be able to re-start from this point in the string. } } } } - else if(overlapping_lines.size() == 1) //If we're not a string of polylines, but adjacent to only one other polyline, create a sequence of polylines. + else { - connections[*polyline_it] = overlapping_lines[0]; - if(connected_lines.find(*polyline_it) == connected_lines.end()) //Nothing connects to this line yet. - { - starting_lines.insert(*polyline_it); //This is a starting point then. - } - if(connected_lines.find(overlapping_lines[0]) != connected_lines.end()) //This line was already connected to. - { - starting_lines.insert(overlapping_lines[0]); //Multiple lines connect to it, so we must be able to start there. - } - else + const std::vector overlapping_lines = getOverlappingLines(polyline_it, perpendicular, polylines); + if(overlapping_lines.size() == 1) //If we're not a string of polylines, but adjacent to only one other polyline, create a sequence of polylines. { - connected_lines.insert(overlapping_lines[0]); + connections[*polyline_it] = overlapping_lines[0]; + if(connected_lines.find(*polyline_it) == connected_lines.end()) //Nothing connects to this line yet. + { + starting_lines.insert(*polyline_it); //This is a starting point then. + } + if(connected_lines.find(overlapping_lines[0]) != connected_lines.end()) //This line was already connected to. + { + starting_lines.insert(overlapping_lines[0]); //Multiple lines connect to it, so we must be able to start there. + } + else + { + connected_lines.insert(overlapping_lines[0]); + } } - } - else //Either 0 (the for loop terminates immediately) or multiple overlapping lines. For multiple lines we need to mark all of them a starting position. - { - for(Path* overlapping_line : overlapping_lines) + else //Either 0 (the for loop terminates immediately) or multiple overlapping lines. For multiple lines we need to mark all of them a starting position. { - starting_lines.insert(overlapping_line); + for(Path* overlapping_line : overlapping_lines) + { + starting_lines.insert(overlapping_line); + } } } } @@ -304,35 +307,29 @@ class PathOrderMonotonic : public PathOrder while(close_line_before != lines_before.end()) { Path* first = close_line_before->val; - if(std::find(result.begin(), result.end(), first) != result.end()) //Did we get into a loop? - { - break; //Stop on the last one before we looped! - } result.push_front(first); //Store this one in the sequence. It's a good one. size_t farthest_vertex = getFarthestEndpoint(first, first_endpoint); //Get to the opposite side. first->start_vertex = farthest_vertex; first->backwards = farthest_vertex != 0; first_endpoint = (*first->converted)[farthest_vertex]; lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); - close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val != polyline && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); } while(close_line_after != lines_after.end()) { Path* last = close_line_after->val; - if(std::find(result.begin(), result.end(), last) != result.end()) //Did we get into a loop? - { - break; //Stop on the last one before we looped! - } result.push_back(last); size_t farthest_vertex = getFarthestEndpoint(last, last_endpoint); //Get to the opposite side. last->start_vertex = (farthest_vertex == 0) ? last->converted->size() - 1 : 0; last->backwards = (farthest_vertex != 0); last_endpoint = (*last->converted)[farthest_vertex]; lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); - close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val != polyline && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); } From c3b8b6d4c2d01776371a46fd521be2b31042d140 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 13:43:57 +0200 Subject: [PATCH 35/52] Better handle ties in projection distance In some cases we had two paths where the starting point was equal, but we chose the path that goes in the monotonic direction rather than the path that was really part of the polyline preceding it. This was resulting in non-montonic orderings. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 135f292f29..b51c891c56 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -191,13 +191,15 @@ class PathOrderMonotonic : public PathOrder std::partial_sort_copy(starting_lines.begin(), starting_lines.end(), starting_lines_monotonic.begin(), starting_lines_monotonic.end(), [this](Path* a, Path* b) { const coord_t a_start_projection = dot(a->converted->front(), monotonic_vector); const coord_t a_end_projection = dot(a->converted->back(), monotonic_vector); - const coord_t a_projection = std::min(a_start_projection, a_end_projection); //The projection of a path is the endpoint furthest back of the two endpoints. + const coord_t a_projection_min = std::min(a_start_projection, a_end_projection); //The projection of a path is the endpoint furthest back of the two endpoints. + const coord_t a_projection_max = std::max(a_start_projection, a_end_projection); //But in case of ties, the other endpoint counts too. Important for polylines where multiple endpoints have the same position! const coord_t b_start_projection = dot(b->converted->front(), monotonic_vector); const coord_t b_end_projection = dot(b->converted->back(), monotonic_vector); - const coord_t b_projection = std::min(b_start_projection, b_end_projection); + const coord_t b_projection_min = std::min(b_start_projection, b_end_projection); + const coord_t b_projection_max = std::max(b_start_projection, b_end_projection); - return a_projection < b_projection; + return a_projection_min < b_projection_min || (a_projection_min == b_projection_min && a_projection_max < b_projection_max); }); //Now that we have the segments of overlapping lines, and know in which order to print the segments, print segments in monotonic order. From b32fb36530dc8fb37d2d569764c18a84540ea5d0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 14:57:57 +0200 Subject: [PATCH 36/52] Also be able to restart from halfway a polyline chain Had this before, but tried disabling that for debugging. Shouldn't have committed that. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index b51c891c56..e25a6efd1e 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -151,7 +151,7 @@ class PathOrderMonotonic : public PathOrder if(std::find(polystring.begin(), polystring.end(), overlapping_line) == polystring.end()) //Mark all overlapping lines not part of the string as possible starting points. { starting_lines.insert(overlapping_line); - //starting_lines.insert(polystring[i + 1]); //Also be able to re-start from this point in the string. + starting_lines.insert(polystring[i + 1]); //Also be able to re-start from this point in the string. } } } From b4abcd6d34b1627bcb791019f638296a49020d76 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 14:59:05 +0200 Subject: [PATCH 37/52] Also search all adjacent lines for initial proximity search Not just try the first hit which is not itself. Also actually try finding it in the entire result. This is the same as in the for loops below. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index e25a6efd1e..3a75ee61e5 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -298,12 +298,14 @@ class PathOrderMonotonic : public PathOrder Point first_endpoint = polyline->converted->front(); Point last_endpoint = polyline->converted->back(); std::vector> lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); - auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val != polyline && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); std::vector> lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); - auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, polyline](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val != polyline && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //Don't find yourself. And only find close lines. + auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); while(close_line_before != lines_before.end()) From f3080d0bad1ac63ceb24e28202a83e966b865cf0 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 15:01:52 +0200 Subject: [PATCH 38/52] Get farthest endpoint from closest endpoint, not from previous point If the endpoints are not 100% equal (within the 5um allowed deviation), it could be that the farthest endpoint is the same as the closest endpoint. Instead, we check which is the farthest from the closest endpoint. Since the lines should not be 0um long here, that should always give the opposite endpoint except if the polyline is actually a polygon with closing end. Polygons with closing end should have been marked as such in the start of the algorithm so they shouldn't occur here (and if so, the polyline string just terminates early, nothing terrible). Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 3a75ee61e5..8f1b4e16a1 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -312,7 +312,7 @@ class PathOrderMonotonic : public PathOrder { Path* first = close_line_before->val; result.push_front(first); //Store this one in the sequence. It's a good one. - size_t farthest_vertex = getFarthestEndpoint(first, first_endpoint); //Get to the opposite side. + size_t farthest_vertex = getFarthestEndpoint(first, close_line_before->point); //Get to the opposite side. first->start_vertex = farthest_vertex; first->backwards = farthest_vertex != 0; first_endpoint = (*first->converted)[farthest_vertex]; @@ -326,7 +326,7 @@ class PathOrderMonotonic : public PathOrder { Path* last = close_line_after->val; result.push_back(last); - size_t farthest_vertex = getFarthestEndpoint(last, last_endpoint); //Get to the opposite side. + size_t farthest_vertex = getFarthestEndpoint(last, close_line_after->point); //Get to the opposite side. last->start_vertex = (farthest_vertex == 0) ? last->converted->size() - 1 : 0; last->backwards = (farthest_vertex != 0); last_endpoint = (*last->converted)[farthest_vertex]; From cfb18995fc1720dbd7bf3792041b70f4876d9112 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 15:04:40 +0200 Subject: [PATCH 39/52] Limit adjacency in both directions, and allow edge cases We only want to find adjacent lines if they are adjacent. Due to the polyline chains, we might end up marking a polyline as adjacent even if they are way *before* the current polyline. We now no longer mark anything before the limit as adjacent, but also check if it's before the limit but not too far behind the limit either. Also, we allow lines to be adjacent even if their endpoints match exactly. This is necessary for polyline chains. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 8f1b4e16a1..0b5fa78479 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -390,7 +390,8 @@ class PathOrderMonotonic : public PathOrder //How far this extends in the monotonic direction, to make sure we only go up to max_adjacent_distance in that direction. const coord_t start_projection = dot((*polyline_it)->converted->front(), monotonic_vector); const coord_t end_projection = dot((*polyline_it)->converted->back(), monotonic_vector); - const coord_t farthest_projection = std::max(start_projection, end_projection); + const coord_t my_farthest_projection = std::max(start_projection, end_projection); + const coord_t my_closest_projection = std::min(start_projection, end_projection); //How far this line reaches in the perpendicular direction -- the range at which the line overlaps other lines. coord_t my_start = dot((*polyline_it)->converted->front(), perpendicular); coord_t my_end = dot((*polyline_it)->converted->back(), perpendicular); @@ -405,8 +406,10 @@ class PathOrderMonotonic : public PathOrder //Don't go beyond the maximum adjacent distance. const coord_t start_their_projection = dot((*overlapping_line)->converted->front(), monotonic_vector); const coord_t end_their_projection = dot((*overlapping_line)->converted->back(), monotonic_vector); - const coord_t closest_projection = std::min(start_their_projection, end_their_projection); - if(closest_projection - farthest_projection > max_adjacent_distance * 1000) //Distances are multiplied by 1000 since monotonic_vector is not a unit vector, but a vector of length 1000. + const coord_t their_farthest_projection = std::max(start_their_projection, end_their_projection); + const coord_t their_closest_projection = std::min(start_their_projection, end_their_projection); + if(their_closest_projection - my_farthest_projection > max_adjacent_distance * 1000 + || my_closest_projection - their_farthest_projection > max_adjacent_distance * 1000) //Distances are multiplied by 1000 since monotonic_vector is not a unit vector, but a vector of length 1000. { break; //Too far. This line and all subsequent lines are not adjacent any more, even though they might be side-by-side. } @@ -424,8 +427,8 @@ class PathOrderMonotonic : public PathOrder - We are a smaller line, they completely overlap us. Both my_start and my_end are between their_start and their_end. (Caught with the first 2 conditions already.) - We are a bigger line, and completely overlap them. Both their_start and their_end are between my_start and my_end. - Lines are exactly equal. Start and end are the same. (Caught with the previous condition too.)*/ - if( (my_start > their_start && my_start < their_end) - || (my_end > their_start && my_end < their_end) + if( (my_start >= their_start && my_start <= their_end) + || (my_end >= their_start && my_end <= their_end) || (their_start >= my_start && their_end <= my_end)) { overlapping_lines.push_back(*overlapping_line); From 485f3ae372616b4c8269eb24d99f627e101e3697 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 15:20:12 +0200 Subject: [PATCH 40/52] Use normal constructor to fix compiler warning It still casts to integers. The compiler warns when it casts an initialiser list of doubles to an initialiser list of integers, but not when casting individual parameters to integer. Contributes to issue CURA-7928. --- tests/PathOrderMonotonicTest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index 421419497b..aac5acbf35 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -174,7 +174,7 @@ namespace cura const Point& pt_r = polylines.begin()->at(0); const Point& pt_s = polylines.begin()->at(1); const double angle_from_first_line = std::atan2(pt_s.Y - pt_r.Y, pt_s.X - pt_r.X) + 0.5 * M_PI; - const Point monotonic_axis{ std::cos(angle_from_first_line) * 1000, std::sin(angle_from_first_line) * 1000 }; + const Point monotonic_axis(std::cos(angle_from_first_line) * 1000, std::sin(angle_from_first_line) * 1000); const Point perpendicular_axis{ turn90CCW(monotonic_axis) }; constexpr coord_t max_adjacent_distance = line_distance + 1; From dd7dd17146cc7a67cb74203e7073991de899a4f1 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 15:38:57 +0200 Subject: [PATCH 41/52] Document addLinesMonotonic function Contributes to issue CURA-7928. --- src/LayerPlan.h | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.h b/src/LayerPlan.h index 41dbde2344..ea72b70b4f 100644 --- a/src/LayerPlan.h +++ b/src/LayerPlan.h @@ -551,8 +551,6 @@ class LayerPlan : public NoCopy */ void addPolygonsByOptimizer(const Polygons& polygons, const GCodePathConfig& config, WallOverlapComputation* wall_overlap_computation = nullptr, const ZSeamConfig& z_seam_config = ZSeamConfig(), coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio flow_ratio = 1.0_r, bool always_retract = false, bool reverse_order = false, const std::optional start_near_location = std::optional()); - void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0_r, const double fan_speed = 100.0); - /*! * Add a single line that is part of a wall to the gcode. * \param p0 The start vertex of the line @@ -609,6 +607,25 @@ class LayerPlan : public NoCopy */ void addLinesByOptimizer(const Polygons& polygons, const GCodePathConfig& config, SpaceFillType space_fill_type, bool enable_travel_optimization = false, int wipe_dist = 0, float flow_ratio = 1.0, std::optional near_start_location = std::optional(), double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT); + /*! + * Add polygons to the g-code with monotonic order. + * \param polygons The lines to add. + * \param config The settings to print those lines with. + * \param space_fill_type The type of space filling used to generate the + * line segments (should be either Lines or PolyLines!) + * \param monotonic_direction The directions in which to sort the lines + * monotonically. + * \param max_adjacent_distance With a monotonic order, adjacent lines must + * be printed in a certain direction. Lines that are not adjacent may be + * printed in any order. This limit is the longest distance at which two + * lines are still considered to be adjacent. + * \param wipe_dist The distance wiped without extruding after laying down a + * line. + * \param flow_ratio The ratio with which to multiply the extrusion amount. + * \param fan_speed Fan speed override for this path. + */ + void addLinesMonotonic(const Polygons& polygons, const GCodePathConfig& config, const SpaceFillType space_fill_type, const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const coord_t wipe_dist = 0, const Ratio flow_ratio = 1.0_r, const double fan_speed = 100.0); + /*! * Add a spiralized slice of wall that is interpolated in X/Y between \p last_wall and \p wall. * From c91c3600bc99dfe94a4ad90c7fdcc954ef909a77 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Mon, 2 Aug 2021 17:19:03 +0200 Subject: [PATCH 42/52] Don't connect to different polyline chains either Not only do we want to check if we're connecting to an earlier part of our own chain, but we also don't want to connect to different chains. There is actually an easier way to check if we're connecting to any chain: Just check if the start_vertex is set for the polylines. If it is, it's already been processed as part of a string of polylines. This is constant-time instead of linear. As long as we also properly process that for the first polyline anyway, which should've been done in any case. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 0b5fa78479..418d65f5cd 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -295,16 +295,17 @@ class PathOrderMonotonic : public PathOrder //Find the two endpoints of the polyline string, on either side. result.push_back(polyline); + polyline->start_vertex = 0; Point first_endpoint = polyline->converted->front(); Point last_endpoint = polyline->converted->back(); std::vector> lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); std::vector> lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); @@ -318,7 +319,7 @@ class PathOrderMonotonic : public PathOrder first_endpoint = (*first->converted)[farthest_vertex]; lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); } @@ -332,7 +333,7 @@ class PathOrderMonotonic : public PathOrder last_endpoint = (*last->converted)[farthest_vertex]; lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return std::find(result.begin(), result.end(), found_path.val) == result.end() //Don't find any line already in the string. + return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. }); } @@ -351,6 +352,10 @@ class PathOrderMonotonic : public PathOrder } } + if(result.size() == 1) + { + result[0]->start_vertex = result[0]->converted->size(); //Reset start vertex as "unknown" again if it's not a string of polylines. + } return result; } From 14a92ab2a82b2edbf5bd0202c0229a5e1a2935ed Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 3 Aug 2021 13:32:25 +0200 Subject: [PATCH 43/52] Remove test for aligning sections This test is invalid, since there are cases where it's required to split up two neighbouring sections that could've been printed in sequence. This is unpreventable. The current algorithm is also sometimes naive in that it picks one order of printing segments which is guaranteed to work, instead of picking the order that minimises travel moves. This is out of scope. Contributes to issue CURA-7928. --- tests/PathOrderMonotonicTest.cpp | 46 ++------------------------------ 1 file changed, 2 insertions(+), 44 deletions(-) diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index aac5acbf35..935bd3ef3c 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -204,8 +204,6 @@ namespace cura writeDebugSVG(filename, angle_radians, monotonic_axis, sections); #endif //TEST_PATHS_SVG_OUTPUT - std::unordered_map, size_t> split_section_counts_per_split_line; - size_t section_a_id = 0; for (const auto& section_a : sections) { @@ -238,41 +236,7 @@ namespace cura projectPathAlongAxis(*it_b, perpendicular_axis), projectEndAlongAxis(*it_b, perpendicular_axis) }; - if (it_a == section_a.end()) - { - // A is wholly before B in the monotonic direction, test if A and B should indeed have been different sections: - if (it_b == section_b.begin()) - { - it_a = std::prev(it_a); // end of section A - const std::pair perp_a_range - { - projectPathAlongAxis(*it_a, perpendicular_axis), - projectEndAlongAxis(*it_a, perpendicular_axis) - }; - if (rangeOverlaps(perp_b_range, perp_a_range) && shortestDistance(*it_a, *it_b) <= max_adjacent_distance) - { - // This is only wrong if there is no split, so no 3rd or more section that ends at the same line, - // so collect those lines. - - // Take the longer line: - const std::pair line = pathLength(*it_a) > pathLength(*it_b) ? - std::make_pair(startVertex(*it_a), endVertex(*it_a)) : - std::make_pair(startVertex(*it_b), endVertex(*it_b)); - - // Collect the edges of the sections that split before that line: - if (split_section_counts_per_split_line.count(line) == 0) - { - split_section_counts_per_split_line[line] = 0; - } - ++split_section_counts_per_split_line[line]; - } - } - else - { - break; - } - } - else + if(it_a != section_a.end()) { // A and B intersect in monotonic direction, check if they overlap in the perpendicular direction: const std::pair perp_a_range @@ -281,17 +245,11 @@ namespace cura projectEndAlongAxis(*it_a, perpendicular_axis) }; EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range)) - << "Perpendicular range overlaps for neighbouring lines in different sections (next line of A / line in B)."; + << "Perpendicular range overlaps for neighboring lines in different sections (next line of A / line in B)."; } } } } - - // If there is a line where a section ends, and only one other section begins, then they should've been 1 section to begin with: - for (const auto& line_count_pair : split_section_counts_per_split_line) - { - EXPECT_GE(line_count_pair.second, 2) << "A section was split up while it could have been printed monotonically."; - } } const std::vector polygon_filenames = From 070cef33e35d7acf4db595b8eeb43f808c038371 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Tue, 3 Aug 2021 13:48:33 +0200 Subject: [PATCH 44/52] Move monotonicity check to per-line range overlap checks The individual sections may start in a non-monotonic order, as long as they don't overlap. When checking adjacent lines though, they may not overlap if they are in the wrong order. Contributes to issue CURA-7928. --- tests/PathOrderMonotonicTest.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index 935bd3ef3c..5162ee0c36 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -13,7 +13,7 @@ #include "../src/utils/polygon.h" #include "ReadTestPolygons.h" -//#define TEST_PATHS_SVG_OUTPUT +#define TEST_PATHS_SVG_OUTPUT #ifdef TEST_PATHS_SVG_OUTPUT #include #include "../src/utils/SVG.h" @@ -218,12 +218,6 @@ namespace cura continue; // <-- So section B will always be 'later' than section A. } - // Check if the start of A is lower than the start of B, since it is ordered first. - const coord_t mono_a{ projectPathAlongAxis(section_a.front(), monotonic_axis) }; - const coord_t mono_b{ projectPathAlongAxis(section_b.front(), monotonic_axis) }; - EXPECT_LE(mono_a, mono_b) - << "Section ordered before another, A's start point should be before B when ordered along the monotonic axis."; - // Already tested for A start < B start in the monotonic direction, // so assume A begins before B, so there is either no overlap, B lies 'witin' A, or B stops later than A. auto it_a = section_a.begin(); @@ -244,8 +238,13 @@ namespace cura projectPathAlongAxis(*it_a, perpendicular_axis), projectEndAlongAxis(*it_a, perpendicular_axis) }; - EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range)) - << "Perpendicular range overlaps for neighboring lines in different sections (next line of A / line in B)."; + const coord_t mono_a = projectPathAlongAxis(*it_a, monotonic_axis); + const coord_t mono_b = projectPathAlongAxis(*it_b, monotonic_axis); + if(mono_a < mono_b) + { + EXPECT_FALSE(rangeOverlaps(perp_b_range, perp_a_range)) + << "Perpendicular range overlaps for neighboring lines in different sections (next line of A / line in B)."; + } } } } From c641818be85da0f1c727ee12da58c4a7606d94c8 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 09:53:13 +0200 Subject: [PATCH 45/52] Document max_adjacent_distance parameter in separate variable This makes it more clear where the *1.1 is coming from. Contributes to issue CURA-7928. --- src/FffGcodeWriter.cpp | 5 +++-- src/TopSurface.cpp | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 4cb0a550f7..11f74e56cf 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2515,15 +2515,16 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La { const AngleRadians monotonic_direction = (skin_angle + 90) / 180.0 * M_PI; constexpr Ratio flow = 1.0_r; + const coord_t max_adjacent_distance = config.getLineWidth() * 1.1; //Lines are considered adjacent if they are 1 line width apart, with 10% extra play. The monotonic order is enforced if they are adjacent. if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) { - gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, monotonic_direction, config.getLineWidth() * 1.1, mesh.settings.get("infill_wipe_dist"), flow, fan_speed); + gcode_layer.addLinesMonotonic(skin_lines, config, SpaceFillType::Lines, monotonic_direction, max_adjacent_distance, mesh.settings.get("infill_wipe_dist"), flow, fan_speed); } else { const SpaceFillType space_fill_type = (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines; constexpr coord_t wipe_dist = 0; - gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, monotonic_direction, config.getLineWidth() * 1.1, wipe_dist, flow, fan_speed); + gcode_layer.addLinesMonotonic(skin_lines, config, space_fill_type, monotonic_direction, max_adjacent_distance, wipe_dist, flow, fan_speed); } } else diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 6e2f1dcd1a..9407fe2dbc 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -128,7 +128,8 @@ bool TopSurface::ironing(const SliceMeshStorage& mesh, const GCodePathConfig& li } else { - layer.addLinesMonotonic(ironing_lines, line_config, SpaceFillType::PolyLines, (direction + 90) / 180.0 * M_PI, line_spacing * 1.1); + const coord_t max_adjacent_distance = line_spacing * 1.1; //Lines are considered adjacent - meaning they need to be printed in monotonic order - if spaced 1 line apart, with 10% extra play. + layer.addLinesMonotonic(ironing_lines, line_config, SpaceFillType::PolyLines, (direction + 90) / 180.0 * M_PI, max_adjacent_distance); } added = true; } From ca28df1077e733d3aea67407385f017518117da4 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 11:49:04 +0200 Subject: [PATCH 46/52] Add function to convert angles from degrees to radians And use it in a few places. This creates an interdependency between AngleDegrees and AngleRadians. Since they need to be header-only for performance reasons, we need to define them with interweaved definitions. This way we guarantee that the conversions still get inlined. As discussed with rburema, it's okay to define these two structs in one file then, even though that breaks our code style. Contributes to issue CURA-7928. --- src/ConicalOverhang.cpp | 4 +- src/FffGcodeWriter.cpp | 2 +- src/FffPolygonGenerator.cpp | 2 +- src/TopSurface.cpp | 2 +- src/TreeSupport.cpp | 4 +- src/Weaver.cpp | 4 +- src/Wireframe2gcode.h | 4 +- src/infill.h | 4 +- src/infill/SubDivCube.cpp | 4 +- src/settings/AdaptiveLayerHeights.cpp | 4 +- src/settings/Settings.cpp | 5 +- src/settings/types/Angle.h | 149 ++++++++++++++++++++++++++ src/settings/types/AngleDegrees.h | 81 -------------- src/settings/types/AngleRadians.h | 73 ------------- src/skin.cpp | 4 +- src/sliceDataStorage.h | 4 +- src/support.cpp | 4 +- src/utils/polygon.h | 2 +- tests/settings/SettingsTest.cpp | 5 +- 19 files changed, 177 insertions(+), 184 deletions(-) create mode 100644 src/settings/types/Angle.h delete mode 100644 src/settings/types/AngleDegrees.h delete mode 100644 src/settings/types/AngleRadians.h diff --git a/src/ConicalOverhang.cpp b/src/ConicalOverhang.cpp index f169a56eab..2d529eac14 100644 --- a/src/ConicalOverhang.cpp +++ b/src/ConicalOverhang.cpp @@ -1,11 +1,11 @@ //Copyright (c) 2016 Tim Kuipers -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "ConicalOverhang.h" #include "mesh.h" #include "slicer.h" -#include "settings/types/AngleRadians.h" //To process the overhang angle. +#include "settings/types/Angle.h" //To process the overhang angle. namespace cura { diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 11f74e56cf..4a66e32020 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2513,7 +2513,7 @@ void FffGcodeWriter::processSkinPrintFeature(const SliceDataStorage& storage, La if(monotonic) { - const AngleRadians monotonic_direction = (skin_angle + 90) / 180.0 * M_PI; + const AngleRadians monotonic_direction = AngleRadians(skin_angle + 90); constexpr Ratio flow = 1.0_r; const coord_t max_adjacent_distance = config.getLineWidth() * 1.1; //Lines are considered adjacent if they are 1 line width apart, with 10% extra play. The monotonic order is enforced if they are adjacent. if(pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC || pattern == EFillMethod::TETRAHEDRAL || pattern == EFillMethod::QUARTER_CUBIC || pattern == EFillMethod::CUBICSUBDIV) diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index cfb141b42e..62bd549175 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -39,7 +39,7 @@ #include "progress/ProgressEstimatorLinear.h" #include "progress/ProgressStageEstimator.h" #include "settings/AdaptiveLayerHeights.h" -#include "settings/types/AngleRadians.h" +#include "settings/types/Angle.h" #include "settings/types/LayerIndex.h" #include "utils/algorithm.h" #include "utils/gettime.h" diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 9407fe2dbc..fed6bac479 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -129,7 +129,7 @@ bool TopSurface::ironing(const SliceMeshStorage& mesh, const GCodePathConfig& li else { const coord_t max_adjacent_distance = line_spacing * 1.1; //Lines are considered adjacent - meaning they need to be printed in monotonic order - if spaced 1 line apart, with 10% extra play. - layer.addLinesMonotonic(ironing_lines, line_config, SpaceFillType::PolyLines, (direction + 90) / 180.0 * M_PI, max_adjacent_distance); + layer.addLinesMonotonic(ironing_lines, line_config, SpaceFillType::PolyLines, AngleRadians(direction + 90.0), max_adjacent_distance); } added = true; } diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 8f68fa56c2..76a8aa24ed 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "Application.h" //To get settings. @@ -8,7 +8,7 @@ #include "TreeSupport.h" #include "progress/Progress.h" #include "settings/EnumSettings.h" -#include "settings/types/AngleRadians.h" //Creating the correct branch angles. +#include "settings/types/Angle.h" //Creating the correct branch angles. #include "settings/types/Ratio.h" #include "utils/IntPoint.h" //To normalize vectors. #include "utils/logoutput.h" diff --git a/src/Weaver.cpp b/src/Weaver.cpp index 8edbfaed40..776fd3b24f 100644 --- a/src/Weaver.cpp +++ b/src/Weaver.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2019 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include // sqrt @@ -13,7 +13,7 @@ #include "progress/Progress.h" #include "utils/logoutput.h" #include "settings/AdaptiveLayerHeights.h" -#include "settings/types/AngleRadians.h" +#include "settings/types/Angle.h" namespace cura { diff --git a/src/Wireframe2gcode.h b/src/Wireframe2gcode.h index a404c5304b..c9c3173b34 100644 --- a/src/Wireframe2gcode.h +++ b/src/Wireframe2gcode.h @@ -1,4 +1,4 @@ -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef WIREFRAME2GCODE_H @@ -7,7 +7,7 @@ #include // passing function pointer or lambda as argument to a function #include "RetractionConfig.h" -#include "settings/types/AngleRadians.h" //For the nozzle expansion angle setting. +#include "settings/types/Angle.h" //For the nozzle expansion angle setting. #include "utils/NoCopy.h" namespace cura diff --git a/src/infill.h b/src/infill.h index 055836b2f8..25869e830a 100644 --- a/src/infill.h +++ b/src/infill.h @@ -1,4 +1,4 @@ -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef INFILL_H @@ -6,7 +6,7 @@ #include "infill/ZigzagConnectorProcessor.h" #include "settings/EnumSettings.h" //For infill types. -#include "settings/types/AngleDegrees.h" +#include "settings/types/Angle.h" #include "utils/IntPoint.h" namespace cura diff --git a/src/infill/SubDivCube.cpp b/src/infill/SubDivCube.cpp index 9af85d25f3..7c6a9924e8 100644 --- a/src/infill/SubDivCube.cpp +++ b/src/infill/SubDivCube.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include "SubDivCube.h" @@ -6,7 +6,7 @@ #include #include "../sliceDataStorage.h" -#include "../settings/types/AngleRadians.h" //For the infill angle. +#include "../settings/types/Angle.h" //For the infill angle. #include "../utils/math.h" #include "../utils/polygonUtils.h" diff --git a/src/settings/AdaptiveLayerHeights.cpp b/src/settings/AdaptiveLayerHeights.cpp index cf8ced1fe1..b347d83490 100644 --- a/src/settings/AdaptiveLayerHeights.cpp +++ b/src/settings/AdaptiveLayerHeights.cpp @@ -1,4 +1,4 @@ -//Copyright (C) 2020 Ultimaker B.V. +//Copyright (C) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include @@ -8,7 +8,7 @@ #include "AdaptiveLayerHeights.h" #include "EnumSettings.h" -#include "types/AngleRadians.h" +#include "types/Angle.h" #include "../Application.h" #include "../Slice.h" #include "../utils/floatpoint.h" diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index 126edf862f..0bfd1693e9 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include @@ -11,8 +11,7 @@ #include "EnumSettings.h" #include "FlowTempGraph.h" #include "Settings.h" -#include "types/AngleDegrees.h" //For angle settings. -#include "types/AngleRadians.h" //For angle settings. +#include "types/Angle.h" #include "types/Duration.h" //For duration and time settings. #include "types/LayerIndex.h" //For layer index settings. #include "types/Ratio.h" //For ratio settings and percentages. diff --git a/src/settings/types/Angle.h b/src/settings/types/Angle.h new file mode 100644 index 0000000000..2bb7a2fccf --- /dev/null +++ b/src/settings/types/Angle.h @@ -0,0 +1,149 @@ +//Copyright (c) 2021 Ultimaker B.V. +//CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef ANGLE_H +#define ANGLE_H + +#include //For fmod. +#include "../../utils/math.h" //For M_PI. + +#define TAU (2.0 * M_PI) + +namespace cura +{ +//AngleDegrees and AngleRadians classes defined together here interweaved, to resolve their interdependencies. +class AngleRadians; + +/* + * \brief Represents an angle in degrees. + * + * This is a facade. It behaves like a double, but this is using clock + * arithmetic which guarantees that the value is always between 0 and 360. + */ +class AngleDegrees +{ +public: + /* + * \brief Default constructor setting the angle to 0. + */ + AngleDegrees() : value(0.0) {}; + + /* + * \brief Converts radians to degrees. + */ + AngleDegrees(const AngleRadians& value); + + /* + * \brief Casts a double to an AngleDegrees instance. + */ + AngleDegrees(double value) : value(std::fmod(std::fmod(value, 360) + 360, 360)) {}; + + /* + * \brief Casts the AngleDegrees instance to a double. + */ + operator double() const + { + return value; + } + + /* + * Some operators implementing the clock arithmetic. + */ + AngleDegrees operator +(const AngleDegrees& other) const + { + return std::fmod(std::fmod(value + other.value, 360) + 360, 360); + } + AngleDegrees operator +(const int& other) const + { + return operator+(AngleDegrees(other)); + } + AngleDegrees& operator +=(const AngleDegrees& other) + { + value = std::fmod(std::fmod(value + other.value, 360) + 360, 360); + return *this; + } + AngleDegrees operator -(const AngleDegrees& other) const + { + return std::fmod(std::fmod(value - other.value, 360) + 360, 360); + } + AngleDegrees& operator -=(const AngleDegrees& other) + { + value = std::fmod(std::fmod(value - other.value, 360) + 360, 360); + return *this; + } + + /* + * \brief The actual angle, as a double. + * + * This value should always be between 0 and 360. + */ + double value = 0; +}; + +/* + * \brief Represents an angle in radians. + * + * This is a facade. It behaves like a double, but this is using clock + * arithmetic which guarantees that the value is always between 0 and 2 * pi. + */ +struct AngleRadians +{ + /* + * \brief Default constructor setting the angle to 0. + */ + AngleRadians() : value(0.0) {}; + + /*! + * \brief Converts an angle from degrees into radians. + */ + AngleRadians(const AngleDegrees& value); + + /* + * \brief Translate the double value in degrees to an AngleRadians instance. + */ + AngleRadians(double value) : value(std::fmod(std::fmod(value, TAU) + TAU, TAU)) {}; + + /* + * \brief Casts the AngleRadians instance to a double. + */ + operator double() const + { + return value; + } + + /* + * Some operators implementing the clock arithmetic. + */ + AngleRadians operator +(const AngleRadians& other) const + { + return std::fmod(std::fmod(value + other.value, TAU) + TAU, TAU); + } + AngleRadians& operator +=(const AngleRadians& other) + { + value = std::fmod(std::fmod(value + other.value, TAU) + TAU, TAU); + return *this; + } + AngleRadians operator -(const AngleRadians& other) const + { + return std::fmod(std::fmod(value - other.value, TAU) + TAU, TAU); + } + AngleRadians& operator -=(const AngleRadians& other) + { + value = std::fmod(std::fmod(value - other.value, TAU) + TAU, TAU); + return *this; + } + + /* + * \brief The actual angle, as a double. + * + * This value should always be between 0 and 2 * pi. + */ + double value = 0; +}; + +inline AngleDegrees::AngleDegrees(const AngleRadians& value) : value(value * 360 / TAU) {} +inline AngleRadians::AngleRadians(const AngleDegrees& value) : value(double(value) * TAU / 360.0) {} + +} + +#endif //ANGLE_H \ No newline at end of file diff --git a/src/settings/types/AngleDegrees.h b/src/settings/types/AngleDegrees.h deleted file mode 100644 index 988bb86af4..0000000000 --- a/src/settings/types/AngleDegrees.h +++ /dev/null @@ -1,81 +0,0 @@ -//Copyright (c) 2019 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef ANGLEDEGREES_H -#define ANGLEDEGREES_H - -#include //For fmod. -#include "AngleRadians.h" //Specialisation to convert radians to degrees. - -namespace cura -{ - -/* - * \brief Represents an angle in degrees. - * - * This is a facade. It behaves like a double, but this is using clock - * arithmetic which guarantees that the value is always between 0 and 360. - */ -class AngleDegrees -{ -public: - /* - * \brief Default constructor setting the angle to 0. - */ - AngleDegrees() : value(0.0) {}; - - /* - * \brief Converts radians to degrees. - */ - AngleDegrees(AngleRadians value) : value(value * 360 / TAU) {}; - - /* - * \brief Casts a double to an AngleDegrees instance. - */ - AngleDegrees(double value) : value(std::fmod(std::fmod(value, 360) + 360, 360)) {}; - - /* - * \brief Casts the AngleDegrees instance to a double. - */ - operator double() const - { - return value; - } - - /* - * Some operators implementing the clock arithmetic. - */ - AngleDegrees operator +(const AngleDegrees& other) const - { - return std::fmod(std::fmod(value + other.value, 360) + 360, 360); - } - AngleDegrees operator +(const int& other) const - { - return operator+(AngleDegrees(other)); - } - AngleDegrees& operator +=(const AngleDegrees& other) - { - value = std::fmod(std::fmod(value + other.value, 360) + 360, 360); - return *this; - } - AngleDegrees operator -(const AngleDegrees& other) const - { - return std::fmod(std::fmod(value - other.value, 360) + 360, 360); - } - AngleDegrees& operator -=(const AngleDegrees& other) - { - value = std::fmod(std::fmod(value - other.value, 360) + 360, 360); - return *this; - } - - /* - * \brief The actual angle, as a double. - * - * This value should always be between 0 and 360. - */ - double value = 0; -}; - -} - -#endif //ANGLEDEGREES_H diff --git a/src/settings/types/AngleRadians.h b/src/settings/types/AngleRadians.h deleted file mode 100644 index 9371121c55..0000000000 --- a/src/settings/types/AngleRadians.h +++ /dev/null @@ -1,73 +0,0 @@ -//Copyright (c) 2018 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef ANGLERADIANS_H -#define ANGLERADIANS_H - -#include //For fmod. -#include "../../utils/math.h" - -#define TAU (2.0 * M_PI) - -namespace cura -{ - -/* - * \brief Represents an angle in radians. - * - * This is a facade. It behaves like a double, but this is using clock - * arithmetic which guarantees that the value is always between 0 and 2 * pi. - */ -struct AngleRadians -{ - /* - * \brief Default constructor setting the angle to 0. - */ - AngleRadians() : value(0.0) {}; - - /* - * \brief Translate the double value in degrees to an AngleRadians instance. - */ - AngleRadians(double value) : value(std::fmod(std::fmod(value, TAU) + TAU, TAU)) {}; - - /* - * \brief Casts the AngleRadians instance to a double. - */ - operator double() const - { - return value; - } - - /* - * Some operators implementing the clock arithmetic. - */ - AngleRadians operator +(const AngleRadians& other) const - { - return std::fmod(std::fmod(value + other.value, TAU) + TAU, TAU); - } - AngleRadians& operator +=(const AngleRadians& other) - { - value = std::fmod(std::fmod(value + other.value, TAU) + TAU, TAU); - return *this; - } - AngleRadians operator -(const AngleRadians& other) const - { - return std::fmod(std::fmod(value - other.value, TAU) + TAU, TAU); - } - AngleRadians& operator -=(const AngleRadians& other) - { - value = std::fmod(std::fmod(value - other.value, TAU) + TAU, TAU); - return *this; - } - - /* - * \brief The actual angle, as a double. - * - * This value should always be between 0 and 2 * pi. - */ - double value = 0; -}; - -} - -#endif //ANGLERADIANS_H \ No newline at end of file diff --git a/src/skin.cpp b/src/skin.cpp index 195a82e990..f96dc706b5 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include // std::ceil @@ -9,7 +9,7 @@ #include "skin.h" #include "sliceDataStorage.h" #include "settings/EnumSettings.h" //For EFillMethod. -#include "settings/types/AngleRadians.h" //For the infill support angle. +#include "settings/types/Angle.h" //For the infill support angle. #include "settings/types/Ratio.h" #include "utils/math.h" #include "utils/polygonUtils.h" diff --git a/src/sliceDataStorage.h b/src/sliceDataStorage.h index 1cae91e139..c12edb8810 100644 --- a/src/sliceDataStorage.h +++ b/src/sliceDataStorage.h @@ -1,4 +1,4 @@ -//Copyright (c) 2018 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef SLICE_DATA_STORAGE_H @@ -12,7 +12,7 @@ #include "SupportInfillPart.h" #include "TopSurface.h" #include "settings/Settings.h" //For MAX_EXTRUDERS. -#include "settings/types/AngleDegrees.h" //Infill angles. +#include "settings/types/Angle.h" //Infill angles. #include "settings/types/LayerIndex.h" #include "utils/AABB.h" #include "utils/AABB3D.h" diff --git a/src/support.cpp b/src/support.cpp index cca0e65f42..7b37ca7ef3 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include // sqrt, round @@ -21,7 +21,7 @@ #include "infill/UniformDensityProvider.h" #include "progress/Progress.h" #include "settings/EnumSettings.h" //For EFillMethod. -#include "settings/types/AngleRadians.h" //To compute overhang distance from the angle. +#include "settings/types/Angle.h" //To compute overhang distance from the angle. #include "settings/types/Ratio.h" #include "utils/logoutput.h" #include "utils/math.h" diff --git a/src/utils/polygon.h b/src/utils/polygon.h index 357d21e1e7..a866ac8d11 100644 --- a/src/utils/polygon.h +++ b/src/utils/polygon.h @@ -17,7 +17,7 @@ #include #include "IntPoint.h" -#include "../settings/types/AngleDegrees.h" //For angles between vertices. +#include "../settings/types/Angle.h" //For angles between vertices. #define CHECK_POLY_ACCESS #ifdef CHECK_POLY_ACCESS diff --git a/tests/settings/SettingsTest.cpp b/tests/settings/SettingsTest.cpp index bdb6fc61fa..4629629644 100644 --- a/tests/settings/SettingsTest.cpp +++ b/tests/settings/SettingsTest.cpp @@ -1,4 +1,4 @@ -//Copyright (c) 2020 Ultimaker B.V. +//Copyright (c) 2021 Ultimaker B.V. //CuraEngine is released under the terms of the AGPLv3 or higher. #include //For M_PI. @@ -11,8 +11,7 @@ #include "../src/settings/FlowTempGraph.h" #include "../src/settings/Settings.h" //The class under test. #include "../src/settings/types/LayerIndex.h" -#include "../src/settings/types/AngleRadians.h" -#include "../src/settings/types/AngleDegrees.h" +#include "../src/settings/types/Angle.h" #include "../src/settings/types/Temperature.h" #include "../src/settings/types/Velocity.h" #include "../src/settings/types/Ratio.h" From cf051f0d8e32f2b1ff34d5a74aa32ecbab0a80bf Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 11:55:44 +0200 Subject: [PATCH 47/52] Don't generate the SVG output by default Could have it be defined through CMake, but that's a bit overkill for a single test like this. Contributes to issue CURA-7928. --- tests/PathOrderMonotonicTest.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index 5162ee0c36..cb6b0875ad 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -13,7 +13,8 @@ #include "../src/utils/polygon.h" #include "ReadTestPolygons.h" -#define TEST_PATHS_SVG_OUTPUT +//To diagnose failing tests with visual images, uncomment the following line: +//#define TEST_PATHS_SVG_OUTPUT #ifdef TEST_PATHS_SVG_OUTPUT #include #include "../src/utils/SVG.h" From 57ad843b67160b7055544f98b36cf81416301934 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 12:03:31 +0200 Subject: [PATCH 48/52] Remove irrelevant/outdated documentation Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 2 +- tests/PathOrderMonotonicTest.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 418d65f5cd..df31b23a27 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -440,7 +440,7 @@ class PathOrderMonotonic : public PathOrder } } - return overlapping_lines; //Uses RVO! + return overlapping_lines; } }; diff --git a/tests/PathOrderMonotonicTest.cpp b/tests/PathOrderMonotonicTest.cpp index cb6b0875ad..2ce1c2c017 100644 --- a/tests/PathOrderMonotonicTest.cpp +++ b/tests/PathOrderMonotonicTest.cpp @@ -219,8 +219,6 @@ namespace cura continue; // <-- So section B will always be 'later' than section A. } - // Already tested for A start < B start in the monotonic direction, - // so assume A begins before B, so there is either no overlap, B lies 'witin' A, or B stops later than A. auto it_a = section_a.begin(); for (auto it_b = section_b.begin(); it_b != section_b.end(); ++it_b) { From f5033e612a93ec2c6ad86e5be6556acf2290c5b2 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 12:35:12 +0200 Subject: [PATCH 49/52] Define static coincident_point_distance for all path ordering algorithms They'll use the same error margin then. Contributes to issue CURA-7928. --- src/PathOrder.h | 13 +++++++++++-- src/PathOrderMonotonic.h | 20 +++++++++----------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/PathOrder.h b/src/PathOrder.h index af18261dd7..2c11bb8241 100644 --- a/src/PathOrder.h +++ b/src/PathOrder.h @@ -91,7 +91,7 @@ class PathOrder }; /*! - * After reordering, this contains the path that need to be printed in the + * After reordering, this contains the paths that need to be printed in the * correct order. * * Each path contains the information necessary to print the paths: A @@ -149,6 +149,16 @@ class PathOrder virtual void optimize() = 0; protected: + /*! + * Two line endpoints are considered to be the same if they are within this + * margin of error apart from each other. + * + * If endpoints are very close together, this will cause the ordering to + * pretend they are the same point. + * This is used for detecting loops and chaining lines together. + */ + constexpr static coord_t coincident_point_distance = 10; + /*! * Get vertex data from the custom path type. * @@ -172,7 +182,6 @@ class PathOrder */ void detectLoops() { - constexpr coord_t coincident_point_distance = 10; //If the endpoints are closer together than 10 units, it is considered pretty much a closed loop. for(Path& path : paths) { if(path.is_closed) //Already a polygon. No need to detect loops. diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index df31b23a27..7ed44a0b55 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -13,9 +13,6 @@ namespace cura { -constexpr coord_t COINCIDENT_POINT_DISTANCE = 5; // In uM. Points closer than this may be considered overlapping / at the same place -constexpr coord_t SQUARED_COINCIDENT_POINT_DISTANCE = COINCIDENT_POINT_DISTANCE * COINCIDENT_POINT_DISTANCE; - /*! * Class that orders paths monotonically. * @@ -44,6 +41,7 @@ class PathOrderMonotonic : public PathOrder { public: using typename PathOrder::Path; + using PathOrder::coincident_point_distance; PathOrderMonotonic(const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const Point start_point) : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) @@ -298,15 +296,15 @@ class PathOrderMonotonic : public PathOrder polyline->start_vertex = 0; Point first_endpoint = polyline->converted->front(); Point last_endpoint = polyline->converted->back(); - std::vector> lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); + std::vector> lines_before = line_bucket_grid.getNearby(first_endpoint, coincident_point_distance); auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. + && vSize2(found_path.point - first_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. }); - std::vector> lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); + std::vector> lines_after = line_bucket_grid.getNearby(last_endpoint, coincident_point_distance); auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. + && vSize2(found_path.point - last_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. }); while(close_line_before != lines_before.end()) @@ -317,10 +315,10 @@ class PathOrderMonotonic : public PathOrder first->start_vertex = farthest_vertex; first->backwards = farthest_vertex != 0; first_endpoint = (*first->converted)[farthest_vertex]; - lines_before = line_bucket_grid.getNearby(first_endpoint, COINCIDENT_POINT_DISTANCE); + lines_before = line_bucket_grid.getNearby(first_endpoint, coincident_point_distance); close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - first_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. + && vSize2(found_path.point - first_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. }); } while(close_line_after != lines_after.end()) @@ -331,10 +329,10 @@ class PathOrderMonotonic : public PathOrder last->start_vertex = (farthest_vertex == 0) ? last->converted->size() - 1 : 0; last->backwards = (farthest_vertex != 0); last_endpoint = (*last->converted)[farthest_vertex]; - lines_after = line_bucket_grid.getNearby(last_endpoint, COINCIDENT_POINT_DISTANCE); + lines_after = line_bucket_grid.getNearby(last_endpoint, coincident_point_distance); close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - last_endpoint) < SQUARED_COINCIDENT_POINT_DISTANCE; //And only find close lines. + && vSize2(found_path.point - last_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. }); } From a0656e670f3470197d3def1612186ec759478ebb Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 13:13:19 +0200 Subject: [PATCH 50/52] Pull resolution of monotonic vector to a separate constant This way we can also document it a bit better. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 7ed44a0b55..54133eed77 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -44,7 +44,7 @@ class PathOrderMonotonic : public PathOrder using PathOrder::coincident_point_distance; PathOrderMonotonic(const AngleRadians monotonic_direction, const coord_t max_adjacent_distance, const Point start_point) - : monotonic_vector(std::cos(monotonic_direction) * 1000, std::sin(monotonic_direction) * 1000) + : monotonic_vector(std::cos(monotonic_direction) * monotonic_vector_resolution, std::sin(monotonic_direction) * monotonic_vector_resolution) , max_adjacent_distance(max_adjacent_distance) { this->start_point = start_point; @@ -222,7 +222,7 @@ class PathOrderMonotonic : public PathOrder protected: /*! * The direction in which to print montonically, encoded as vector of length - * 1000. + * ``monotonic_vector_resolution``. * * The resulting ordering will cause clusters of paths to be sorted * according to their projection on this vector. @@ -411,8 +411,8 @@ class PathOrderMonotonic : public PathOrder const coord_t end_their_projection = dot((*overlapping_line)->converted->back(), monotonic_vector); const coord_t their_farthest_projection = std::max(start_their_projection, end_their_projection); const coord_t their_closest_projection = std::min(start_their_projection, end_their_projection); - if(their_closest_projection - my_farthest_projection > max_adjacent_distance * 1000 - || my_closest_projection - their_farthest_projection > max_adjacent_distance * 1000) //Distances are multiplied by 1000 since monotonic_vector is not a unit vector, but a vector of length 1000. + if(their_closest_projection - my_farthest_projection > max_adjacent_distance * monotonic_vector_resolution + || my_closest_projection - their_farthest_projection > max_adjacent_distance * monotonic_vector_resolution) //Multiply by the length of the vector since we need to compare actual distances here. { break; //Too far. This line and all subsequent lines are not adjacent any more, even though they might be side-by-side. } @@ -440,6 +440,17 @@ class PathOrderMonotonic : public PathOrder return overlapping_lines; } + + protected: + /*! + * Length of the monotonic vector, as stored. + * + * This needs to be long enough to eliminate rounding errors caused by + * rounding the coordinates of the vector to integer coordinates for the + * ``coord_t`` data type, but not so long as to cause integer overflows if + * the quadratic is multiplied by a projection length. + */ + constexpr static coord_t monotonic_vector_resolution = 1000; }; } From 430edc166df19083824c3714319a16f820104cfc Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 13:18:12 +0200 Subject: [PATCH 51/52] Improve documentation for marking starting lines in polyline chains Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 54133eed77..16ee713d52 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -143,8 +143,13 @@ class PathOrderMonotonic : public PathOrder { connections[polystring[i]] = polystring[i + 1]; connected_lines.insert(polystring[i + 1]); + + //Even though we chain polylines, we still want to find lines that they overlap with. + //The strings of polylines may still have weird shapes which interweave with other strings of polylines or loose lines. + //So when a polyline string comes into contact with other lines, we still want to guarantee their order. + //So here we will look for which lines they come into contact with, and thus mark those as possible starting points, so that they function as a new junction. const std::vector overlapping_lines = getOverlappingLines(std::find(polylines.begin(), polylines.end(), polystring[i]), perpendicular, polylines); - for(Path* overlapping_line : overlapping_lines) //Last one is done in the else case below. + for(Path* overlapping_line : overlapping_lines) { if(std::find(polystring.begin(), polystring.end(), overlapping_line) == polystring.end()) //Mark all overlapping lines not part of the string as possible starting points. { @@ -154,7 +159,7 @@ class PathOrderMonotonic : public PathOrder } } } - else + else //Not a string of polylines, but simply adjacent line segments. { const std::vector overlapping_lines = getOverlappingLines(polyline_it, perpendicular, polylines); if(overlapping_lines.size() == 1) //If we're not a string of polylines, but adjacent to only one other polyline, create a sequence of polylines. From 47fe5b9e5a520de0682edad044ab79a70a281861 Mon Sep 17 00:00:00 2001 From: Ghostkeeper Date: Wed, 4 Aug 2021 13:48:14 +0200 Subject: [PATCH 52/52] Separate function to check if we can connect to incident polylines This reduces code duplication a bit. Due to the lack of std::bind for partial binding, I still have to use a lambda though. But not for business logic any more. Contributes to issue CURA-7928. --- src/PathOrderMonotonic.h | 41 +++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/src/PathOrderMonotonic.h b/src/PathOrderMonotonic.h index 16ee713d52..ab16dae22b 100644 --- a/src/PathOrderMonotonic.h +++ b/src/PathOrderMonotonic.h @@ -302,14 +302,12 @@ class PathOrderMonotonic : public PathOrder Point first_endpoint = polyline->converted->front(); Point last_endpoint = polyline->converted->back(); std::vector> lines_before = line_bucket_grid.getNearby(first_endpoint, coincident_point_distance); - auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - first_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. + auto close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return canConnectToPolyline(first_endpoint, found_path); }); std::vector> lines_after = line_bucket_grid.getNearby(last_endpoint, coincident_point_distance); - auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - last_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. + auto close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return canConnectToPolyline(last_endpoint, found_path); }); while(close_line_before != lines_before.end()) @@ -321,9 +319,8 @@ class PathOrderMonotonic : public PathOrder first->backwards = farthest_vertex != 0; first_endpoint = (*first->converted)[farthest_vertex]; lines_before = line_bucket_grid.getNearby(first_endpoint, coincident_point_distance); - close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - first_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. + close_line_before = std::find_if(lines_before.begin(), lines_before.end(), [first_endpoint](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return canConnectToPolyline(first_endpoint, found_path); }); } while(close_line_after != lines_after.end()) @@ -332,12 +329,11 @@ class PathOrderMonotonic : public PathOrder result.push_back(last); size_t farthest_vertex = getFarthestEndpoint(last, close_line_after->point); //Get to the opposite side. last->start_vertex = (farthest_vertex == 0) ? last->converted->size() - 1 : 0; - last->backwards = (farthest_vertex != 0); + last->backwards = farthest_vertex != 0; last_endpoint = (*last->converted)[farthest_vertex]; lines_after = line_bucket_grid.getNearby(last_endpoint, coincident_point_distance); - close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint, result](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { - return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. - && vSize2(found_path.point - last_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. + close_line_after = std::find_if(lines_after.begin(), lines_after.end(), [last_endpoint](SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) { + return canConnectToPolyline(last_endpoint, found_path); }); } @@ -456,6 +452,25 @@ class PathOrderMonotonic : public PathOrder * the quadratic is multiplied by a projection length. */ constexpr static coord_t monotonic_vector_resolution = 1000; + + private: + /*! + * Predicate to check if a nearby path is okay for polylines to connect + * with. + * + * It is okay if the endpoints are sufficiently close together, and the + * polyline is not yet connected to a different string of polylines. + * \param nearby_endpoint The endpoint of the current string of polylines. + * We'll check if the candidate polyline is nearby enough. + * \param found_path A candidate polyline, as found in the bucket grid. This + * struct of the bucket grid contains not only the actual path (via pointer) + * but also the endpoint of it that it found to be nearby. + */ + static bool canConnectToPolyline(const Point nearby_endpoint, SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem found_path) + { + return found_path.val->start_vertex == found_path.val->converted->size() //Don't find any line already in the string. + && vSize2(found_path.point - nearby_endpoint) < coincident_point_distance * coincident_point_distance; //And only find close lines. + } }; }