From 6159a2816c3007f63b62f830aa8296e99621e1a3 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:54:36 +0200 Subject: [PATCH 01/41] Fix for issue https://github.com/Ultimaker/Cura/issues/19586 --- src/support.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/support.cpp b/src/support.cpp index 17a55d855a..898b8b2835 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -1712,7 +1712,8 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh { return; } - const coord_t z_distance_top = round_up_divide(mesh.settings.get("support_top_distance"), layer_height); // Number of layers between support roof and model. + const coord_t support_top_distance = mesh.settings.get("support_top_distance"); + const coord_t z_distance_top = round_up_divide(support_top_distance, layer_height); // Number of layers between support roof and model. const coord_t roof_line_width = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_line_width"); const coord_t roof_outline_offset = mesh_group_settings.get("support_roof_extruder_nr").settings_.get("support_roof_offset"); @@ -1732,7 +1733,7 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh Shape roofs; generateSupportInterfaceLayer(global_support_areas_per_layer[layer_idx], mesh_outlines, roof_line_width, roof_outline_offset, minimum_roof_area, roofs); support_layers[layer_idx].support_roof.push_back(roofs); - if (layer_idx > 0 && layer_idx < support_layers.size() - 1) + if (layer_idx > 0 && layer_idx < support_layers.size() - 1 && support_top_distance % layer_height != 0) { support_layers[layer_idx].support_fractional_roof.push_back(roofs.difference(support_layers[layer_idx + 1].support_roof)); } From 762f8483015b5058820f58b7b34d660fdaed694e Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:37:17 +0200 Subject: [PATCH 02/41] Revert "Fix 'encompassing hole' issue (tree support)." This reverts commit 1752db52531887c67b7bbc4218cf85e1619537f8. --- src/TreeSupport.cpp | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 783807e002..67a68e1cf6 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2093,17 +2093,6 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) if (! found) { next_removed_holes_by_idx.emplace(idx); - - // Individual pieces of the hole could still be valid (if the 'hole' is made by branches surrounding others' for instance). - for (const auto& poly : hole) - { - if (poly.area() < 0) - { - auto poly_copy = poly; - poly_copy.reverse(); - valid_holes[layer_idx].push_back(poly_copy); - } - } } else { From bd1fc3e8e7e9b557956abdb880427740391683cc Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:29:02 +0200 Subject: [PATCH 03/41] Revert "Tree-support: Create more dissalowed area; prevents model-intersection." This reverts commit 885352e9 --- src/TreeSupport.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 67a68e1cf6..21a34796de 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2023,8 +2023,10 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) return; } - const Shape& relevant_forbidden = volumes_.getCollision(0, layer_idx, true); - Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); + Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) + .createTubeShape( + closing_dist, + 0); //.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); Shape holes_below; @@ -2041,11 +2043,6 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { holes_resting_outside[layer_idx].emplace(idx); } - else if (hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).area() > hole.length() * EPSILON) - { - holes_resting_outside[layer_idx].emplace( - idx); // technically not resting outside, also not valid, but the alternative is potentially having lines go though the model - } else { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) From 3600ea9514696d1da0e548a34c6b15b57fd7cc0f Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 5 Jun 2024 00:29:02 +0200 Subject: [PATCH 04/41] Fix issue causing support lines to overlap with the model --- src/TreeSupport.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 21a34796de..7d5380478a 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2023,6 +2023,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) return; } + Shape relevant_forbidden = volumes_.getCollision(0, layer_idx, true); Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) .createTubeShape( closing_dist, @@ -2043,6 +2044,11 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { holes_resting_outside[layer_idx].emplace(idx); } + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(relevant_forbidden, hole_aabb)).offset(-config.xy_min_distance / 2).empty()) + { + // technically not resting outside, also not valid, but the alternative is potentially having lines go through the model + holes_resting_outside[layer_idx].emplace(idx); + } else { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) From 469be9c4ec047209e3a44abfa6f5ee657d0e5789 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:46:36 +0200 Subject: [PATCH 05/41] Fix issue with tree support hole removal if the hole compasses other branches. Prevents removal of holes with holes. Also added a performance improvement found while debugging. --- src/TreeSupport.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 7d5380478a..ead27ed820 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2024,10 +2024,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) } Shape relevant_forbidden = volumes_.getCollision(0, layer_idx, true); - Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()) - .createTubeShape( - closing_dist, - 0); //.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); + Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); Shape holes_below; @@ -2040,7 +2037,13 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { AABB hole_aabb = AABB(hole); hole_aabb.expand(EPSILON); - if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) + if(hole.size() > 1) + { + // The hole contains other branches! It can not be fully removed. + // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring larger changes. + holes_resting_outside[layer_idx].emplace(idx); + } + else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) { holes_resting_outside[layer_idx].emplace(idx); } @@ -2053,8 +2056,9 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { + // TODO should technically be outline: Check if this is fine either way as it would save an offset if (hole_aabb.hit(AABB(hole2)) - && ! hole.intersection(hole2).empty()) // TODO should technically be outline: Check if this is fine either way as it would save an offset + && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) { hole_rest_map[layer_idx][idx].emplace_back(idx2); } From 8de3babdb0a6360b477956ba3b0cb4501e9ff877 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Tue, 22 Oct 2024 11:11:01 +0200 Subject: [PATCH 06/41] Restore a small change mistakenly undone by a previous commit. --- src/TreeSupport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index ead27ed820..8268bc2e9c 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2023,7 +2023,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) return; } - Shape relevant_forbidden = volumes_.getCollision(0, layer_idx, true); + const Shape& relevant_forbidden = volumes_.getCollision(0, layer_idx, true); Shape outer_walls = TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).createTubeShape(closing_dist, 0); Shape holes_below; From dcdeff62f3e0548ac2d290bc855ad9a8deb89cf9 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Wed, 23 Oct 2024 09:02:10 +0200 Subject: [PATCH 07/41] Only perform 'tiny loops' fix if there's space to do so. Previously, we created this 'resolveIntersection' function to handle infill line intersections that where very close to the walls and would therefore create 'tiny loops', which created problems, especially when extruding, like for connect infill lines. However, the alternative maneuvre (or at least the calculation thereof) needs at least _some_ space to work in. If the crossing to be prevented is 'on' the line (within an epsilon), the calculation will not be possible in the current state -- and if things are that close, the rest of the algos will take care of it. CURA-12173 --- src/infill.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/infill.cpp b/src/infill.cpp index 67fdd613c9..469c4035b8 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -931,12 +931,16 @@ void Infill::connectLines(OpenLinesSet& result_lines) } else { + constexpr coord_t epsilon_sqd = 25; + // Resolve any intersections of the fill lines close to the boundary, by inserting extra points so the lines don't create a tiny 'loop'. Point2LL intersect; if (vSize2(previous_point - next_point) < half_line_distance_squared && LinearAlg2D::lineLineIntersection(previous_segment->start_, previous_segment->end_, crossing->start_, crossing->end_, intersect) && LinearAlg2D::pointIsProjectedBeyondLine(intersect, previous_segment->start_, previous_segment->end_) == 0 - && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0) + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 + && vSize2(previous_point - intersect) > epsilon_sqd + && vSize2(next_point - intersect) > epsilon_sqd) { resolveIntersection(infill_line_width_, intersect, previous_point, next_point, previous_segment, crossing); } From 6db4b4ff830da77500dbcdceab9ddf6ef8949c9b Mon Sep 17 00:00:00 2001 From: rburema Date: Wed, 23 Oct 2024 07:02:54 +0000 Subject: [PATCH 08/41] Applied clang-format. --- src/infill.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/infill.cpp b/src/infill.cpp index 469c4035b8..695e7a134c 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -938,8 +938,7 @@ void Infill::connectLines(OpenLinesSet& result_lines) if (vSize2(previous_point - next_point) < half_line_distance_squared && LinearAlg2D::lineLineIntersection(previous_segment->start_, previous_segment->end_, crossing->start_, crossing->end_, intersect) && LinearAlg2D::pointIsProjectedBeyondLine(intersect, previous_segment->start_, previous_segment->end_) == 0 - && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 - && vSize2(previous_point - intersect) > epsilon_sqd + && LinearAlg2D::pointIsProjectedBeyondLine(intersect, crossing->start_, crossing->end_) == 0 && vSize2(previous_point - intersect) > epsilon_sqd && vSize2(next_point - intersect) > epsilon_sqd) { resolveIntersection(infill_line_width_, intersect, previous_point, next_point, previous_segment, crossing); From 0119f39db3028382f6ec6b9ab70f836c710a1416 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 23 Oct 2024 16:37:09 +0200 Subject: [PATCH 09/41] Fix scarf seam and coasting interaction CURA-12175 This is basically a rewrite of the coasting implementation. The coasting is now fully pre-calculated before processing the paths, because we need to do it partially anyway, then the second part just applies the result of the calculations. --- include/ExtruderPlan.h | 2 +- include/LayerPlan.h | 76 ++++++---- include/geometry/Point3LL.h | 5 + include/utils/Coord_t.h | 19 ++- src/LayerPlan.cpp | 278 +++++++++++++++++------------------- src/gcodeExport.cpp | 2 +- 6 files changed, 196 insertions(+), 186 deletions(-) diff --git a/include/ExtruderPlan.h b/include/ExtruderPlan.h index b5c579496c..cfd2070975 100644 --- a/include/ExtruderPlan.h +++ b/include/ExtruderPlan.h @@ -191,7 +191,7 @@ class ExtruderPlan /*! * @return distance between p0 and p1 as well as the time spend on the segment */ - std::pair getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path); + std::pair getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) const; /*! * Compute naive time estimates (without accounting for slow down at corners etc.) and naive material estimates. diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 0be204faf2..d373fff7ae 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -57,20 +57,27 @@ class LayerPlan : public NoCopy #endif public: - // 'AdjustCoasting'; because split-up paths from the same extruder (with no travel moves between them) should count as the same path w.r.t. coasting. - enum class AdjustCoasting - { - AsNormal, - CoastEntirePath, - ContinueCoasting - }; - const PathConfigStorage configs_storage_; //!< The line configs for this layer for each feature type const coord_t z_; coord_t final_travel_z_; bool mode_skip_agressive_merge_; //!< Whether to give every new path the 'skip_agressive_merge_hint' property (see GCodePath); default is false. private: + // Indicates how coasting should be processed on the given path. + enum class ApplyCoasting + { + NoCoasting, // Do not apply coasting on this path, extrude it normally + CoastEntirePath, // Fully coast this path, i.e. replace it by travel moves + PartialCoasting // Extrude the first part of the path and coast the end + }; + + struct PathCoasting + { + ApplyCoasting apply_coasting{ ApplyCoasting::NoCoasting }; + size_t coasting_start_index{ 0 }; + Point3LL coasting_start_pos; + }; + const SliceDataStorage& storage_; //!< The polygon data obtained from FffPolygonProcessor const LayerIndex layer_nr_; //!< The layer number of this layer plan const bool is_initial_layer_; //!< Whether this is the first layer (which might be raft) @@ -699,28 +706,6 @@ class LayerPlan : public NoCopy */ bool makeRetractSwitchRetract(unsigned int extruder_plan_idx, unsigned int path_idx); - /*! - * Writes a path to GCode and performs coasting, or returns false if it did nothing. - * - * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. - * - * \param gcode The gcode to write the planned paths to. - * \param extruder_plan_idx The index of the current extruder plan. - * \param path_idx The index into LayerPlan::paths for the next path to be - * written to GCode. - * \param layer_thickness The height of the current layer. - * \param insertTempOnTime A function that inserts temperature changes at a given time. - * \param coasting_adjust Paths can be split up, so we need to know when to continue coasting from last, or even coast the entire path. - * \return Whether any GCode has been written for the path. - */ - bool writePathWithCoasting( - GCodeExport& gcode, - const size_t extruder_plan_idx, - const size_t path_idx, - const coord_t layer_thickness, - const std::function insertTempOnTime, - const std::pair coasting_adjust); - /*! * Applying speed corrections for minimal layer times and determine the fanSpeed. * @@ -895,6 +880,37 @@ class LayerPlan : public NoCopy const coord_t decelerate_length, const bool is_scarf_closure); + /*! + * Pre-calculates the coasting to be applied on the paths + * + * \param extruder_settings The current extruder settings + * \param paths The current set of paths to be written to GCode + * \param current_position The last position set in the gcode writer + * \return The list of coasting settings to be applied on the paths. It will always have the same size as paths. + */ + std::vector calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const; + + /*! + * Writes a path to GCode and performs coasting, or returns false if it did nothing. + * + * Coasting replaces the last piece of an extruded path by move commands and uses the oozed material to lay down lines. + * + * \param gcode The gcode to write the planned paths to. + * \param extruder_plan_idx The index of the current extruder plan. + * \param path_idx The index into LayerPlan::paths for the next path to be + * written to GCode. + * \param layer_thickness The height of the current layer. + * \param insertTempOnTime A function that inserts temperature changes at a given time. + * \param path_coasting The actual coasting to be applied to the path. + * \return Whether any GCode has been written for the path. + */ + bool writePathWithCoasting( + GCodeExport& gcode, + const size_t extruder_plan_idx, + const size_t path_idx, + const std::function insertTempOnTime, + const PathCoasting& path_coasting); + /*! * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment * \param wall The currently processed wall diff --git a/include/geometry/Point3LL.h b/include/geometry/Point3LL.h index 0e4b62ed7b..604e93af3c 100644 --- a/include/geometry/Point3LL.h +++ b/include/geometry/Point3LL.h @@ -192,6 +192,11 @@ inline Point3LL operator*(const T i, const Point3LL& rhs) return rhs * i; } +inline Point3LL lerp(const Point3LL& a, const Point3LL& b, const double t) +{ + return Point3LL(cura::lerp(a.x_, b.x_, t), cura::lerp(a.y_, b.y_, t), cura::lerp(a.z_, b.z_, t)); +} + } // namespace cura diff --git a/include/utils/Coord_t.h b/include/utils/Coord_t.h index bd8f3e088b..8043bbd875 100644 --- a/include/utils/Coord_t.h +++ b/include/utils/Coord_t.h @@ -6,8 +6,11 @@ // Include Clipper to get the ClipperLib::IntPoint definition, which we reuse as Point definition. +#include #include +#include "utils/types/generic.h" + namespace cura { @@ -16,16 +19,22 @@ using coord_t = ClipperLib::cInt; static inline coord_t operator"" _mu(unsigned long long i) { return static_cast(i); -}; +} #define INT2MM(n) (static_cast(n) / 1000.0) #define INT2MM2(n) (static_cast(n) / 1000000.0) -#define MM2INT(n) (static_cast((n)*1000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM2_2INT(n) (static_cast((n)*1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM3_2INT(n) (static_cast((n)*1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2INT(n) (static_cast((n) * 1000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2_2INT(n) (static_cast((n) * 1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM3_2INT(n) (static_cast((n) * 1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) #define INT2MICRON(n) ((n) / 1) -#define MICRON2INT(n) ((n)*1) +#define MICRON2INT(n) ((n) * 1) + +template +[[nodiscard]] inline coord_t lerp(coord_t a, coord_t b, FactorType t) +{ + return std::llrint(std::lerp(static_cast(a), static_cast(b), t)); +} } // namespace cura diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2b17229c36..d560af1fc0 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1227,6 +1227,109 @@ void LayerPlan::addSplitWall( } } +std::vector + LayerPlan::calculatePathsCoasting(const Settings& extruder_settings, const std::vector& paths, const Point3LL& current_position) const +{ + std::vector path_coastings; + path_coastings.resize(paths.size()); + + if (extruder_settings.get("coasting_enable")) + { + // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). + // Note that this doesn't perform the coasting itself, it just calculates the coasting values which will be applied by the 'writePathWithCoasting' func. + // All of this is necessary since we split up paths because of scarf and acceleration-adjustments (start/end), so we need to have adjacency info. + + const double coasting_volume = extruder_settings.get("coasting_volume"); + const double coasting_min_volume = extruder_settings.get("coasting_min_volume"); + + for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse + | ranges::views::chunk_by( + [](const auto& path_a, const auto& path_b) + { + return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); + })) + { + double accumulated_volume = 0.0; + bool chunk_coasting_point_reached = false; + bool chunk_min_volume_reached = false; + + for (const auto& [path_idx, path] : reversed_chunk) + { + if (path.isTravelPath()) + { + continue; + } + + PathCoasting& path_coasting = path_coastings[path_idx]; + const double path_extrusion_per_length = path.config.extrusion_mm3_per_mm * path.flow; + + for (const auto& [point_idx, point] : path.points | ranges::views::enumerate | ranges::views::reverse) + { + Point3LL previous_point; + if (point_idx > 0) + { + previous_point = path.points[point_idx - 1]; + } + else if (path_idx > 0) + { + previous_point = paths[path_idx - 1].points.back(); + } + else + { + previous_point = current_position; + } + + const double segment_length = INT2MM((point - previous_point).vSize()); + const double segment_volume = segment_length * path_extrusion_per_length; + + accumulated_volume += segment_volume; + + if (! chunk_coasting_point_reached && path_coasting.apply_coasting == ApplyCoasting::NoCoasting) + { + if (accumulated_volume >= coasting_volume) + { + const double start_pos_factor = (accumulated_volume - coasting_volume) / segment_volume; + Point3LL coasting_start_pos = cura::lerp(previous_point, point, start_pos_factor); + path_coasting = { ApplyCoasting::PartialCoasting, point_idx, coasting_start_pos }; + chunk_coasting_point_reached = true; + } + else if (point_idx == 0) + { + path_coasting.apply_coasting = ApplyCoasting::CoastEntirePath; + } + } + + if (accumulated_volume >= coasting_min_volume) + { + chunk_min_volume_reached = true; + } + + if (chunk_min_volume_reached && chunk_coasting_point_reached) + { + break; + } + } + + if (chunk_min_volume_reached && chunk_coasting_point_reached) + { + break; + } + } + + if (! chunk_min_volume_reached) + { + // It turns out this chunk doesn't fit the minimum requirements for coasting, so skip it + for (const auto& [path_idx, path] : reversed_chunk) + { + path_coastings[path_idx].apply_coasting = ApplyCoasting::NoCoasting; + } + } + } + } + + return path_coastings; +} + coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const { coord_t distance_to_bridge_start = 0; @@ -2054,7 +2157,7 @@ double ExtruderPlan::getRetractTime(const GCodePath& path) return retraction_config_.distance / (path.retract ? retraction_config_.speed : retraction_config_.primeSpeed); } -std::pair ExtruderPlan::getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) +std::pair ExtruderPlan::getPointToPointTime(const Point3LL& p0, const Point3LL& p1, const GCodePath& path) const { const double length = (p0 - p1).vSizeMM(); return { length, length / (path.config.getSpeed() * path.speed_factor) }; @@ -2369,57 +2472,9 @@ void LayerPlan::writeGCode(GCodeExport& gcode) extruder_plan.handleInserts(path_idx, gcode, cumulative_path_time); }; - std::vector> coasting_adjust_per_path; - if (extruder.settings_.get("coasting_enable")) - { - // Chunk paths by travel paths, and find out which paths are a 'continuation' w.r.t. coasting (and which need to be 'coasted away' entirely). - // Note that this doesn't perform the coasting itself, it just calculates the 'adjust coasting' vector needed by the 'writePathWithCoasting' func. - // All of this is nescesary since we split up paths because of scarf and accelleration-adjustments (start/end), so we need to have adjacency info. - - const double coasting_volume = extruder.settings_.get("coasting_volume"); - coasting_adjust_per_path.assign(paths.size(), { AdjustCoasting::AsNormal, coasting_volume }); - - for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse - | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) - { - return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); - })) - { - double coasting_left = coasting_volume; - for (const auto& [path_idx, path] : reversed_chunk) - { - if (path.isTravelPath()) - { - break; - } - - coord_t accumulated_length = 0; - for (size_t i_pt = 1; i_pt < path.points.size(); i_pt++) - { - accumulated_length += (path.points[i_pt - 1] - path.points[i_pt]).vSize(); - } - const double path_volume = INT2MM2(INT2MM(accumulated_length * path.config.getLineWidth()) * layer_thickness_); + const std::vector coasting_per_path = calculatePathsCoasting(extruder.settings_, paths, gcode.getPosition()); - if (path_volume < coasting_left) - { - coasting_adjust_per_path[path_idx].first = AdjustCoasting::CoastEntirePath; - } - else - { - coasting_adjust_per_path[path_idx] = { AdjustCoasting::ContinueCoasting, coasting_left }; - } - - coasting_left -= path_volume; - if (coasting_left <= 0) - { - break; - } - } - } - } - - for (int64_t path_idx = 0; path_idx < paths.size(); path_idx++) + for (size_t path_idx = 0; path_idx < paths.size(); path_idx++) { extruder_plan.handleInserts(path_idx, gcode); cumulative_path_time = 0.; // reset to 0 for current path. @@ -2612,7 +2667,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) bool coasting = extruder.settings_.get("coasting_enable"); if (coasting) { - coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, layer_thickness_, insertTempOnTime, coasting_adjust_per_path[path_idx]); + coasting = writePathWithCoasting(gcode, extruder_plan_idx, path_idx, insertTempOnTime, coasting_per_path[path_idx]); } if (! coasting) // not same as 'else', cause we might have changed [coasting] in the line above... { // normal path to gcode algorithm @@ -2769,133 +2824,58 @@ bool LayerPlan::writePathWithCoasting( GCodeExport& gcode, const size_t extruder_plan_idx, const size_t path_idx, - const coord_t layer_thickness, const std::function insertTempOnTime, - const std::pair coasting_adjust) + const PathCoasting& path_coasting) { - ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; - const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - const double coasting_volume = std::min(extruder.settings_.get("coasting_volume"), coasting_adjust.second); - if (coasting_volume <= 0) + if (path_coasting.apply_coasting == ApplyCoasting::NoCoasting) { return false; } + + size_t coasting_start_index; + Point3LL previous_position = gcode.getPosition(); + const ExtruderPlan& extruder_plan = extruder_plans_[extruder_plan_idx]; const std::vector& paths = extruder_plan.paths_; const GCodePath& path = paths[path_idx]; - if (path_idx + 1 >= paths.size() || (path.isTravelPath() || ! (paths[path_idx + 1].config.isTravelPath() || coasting_adjust.first == AdjustCoasting::ContinueCoasting)) - || path.points.size() < 2) - { - return false; - } - - coord_t coasting_min_dist_considered = MM2INT(0.1); // hardcoded setting for when to not perform coasting - - const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; - - coord_t coasting_dist = coasting_adjust.first == AdjustCoasting::CoastEntirePath - ? std::numeric_limits::max() / 2 - : MM2INT(MM2_2INT(coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues - const double coasting_min_volume = extruder.settings_.get("coasting_min_volume"); - const coord_t coasting_min_dist - = MM2INT(MM2_2INT(coasting_min_volume + coasting_volume) / layer_thickness) / path.config.getLineWidth(); // closing brackets of MM2INT at weird places for precision issues - // /\ the minimal distance when coasting will coast the full coasting volume instead of linearly less with linearly smaller paths - - std::vector accumulated_dist_per_point; // the first accumulated dist is that of the last point! (that of the last point is always zero...) - accumulated_dist_per_point.push_back(0); - - coord_t accumulated_dist = 0; - - bool length_is_less_than_min_dist = true; - - std::optional acc_dist_idx_gt_coast_dist; // the index of the first point with accumulated_dist more than coasting_dist (= index into accumulated_dist_per_point) - // == the point printed BEFORE the start point for coasting - - const Point3LL* last = &path.points[path.points.size() - 1]; - for (unsigned int backward_point_idx = 1; backward_point_idx < path.points.size(); backward_point_idx++) - { - const Point3LL& point = path.points[path.points.size() - 1 - backward_point_idx]; - const coord_t distance = (point - *last).vSize(); - accumulated_dist += distance; - accumulated_dist_per_point.push_back(accumulated_dist); - - if (! acc_dist_idx_gt_coast_dist.has_value() && accumulated_dist >= coasting_dist) - { - acc_dist_idx_gt_coast_dist = backward_point_idx; // the newly added point - } - - if (accumulated_dist >= coasting_min_dist) - { - length_is_less_than_min_dist = false; - break; - } - - last = &point; - } - coasting_dist = std::min(coasting_dist, accumulated_dist); // if the path is shorter than coasting_dist, we should coast the whole path + const ExtruderTrain& extruder = Application::getInstance().current_slice_->scene.extruders[extruder_plan.extruder_nr_]; - if (accumulated_dist < coasting_min_dist_considered) + if (path_coasting.apply_coasting == ApplyCoasting::CoastEntirePath) { - return false; + coasting_start_index = 0; } - coord_t actual_coasting_dist = coasting_dist; - if (length_is_less_than_min_dist) + else { - // in this case accumulated_dist is the length of the whole path - actual_coasting_dist = accumulated_dist * coasting_dist / coasting_min_dist; - if (actual_coasting_dist == 0) // Downscaling due to Minimum Coasting Distance reduces coasting to less than 1 micron. - { - return false; // Skip coasting at all then. - } - for (acc_dist_idx_gt_coast_dist = 1; acc_dist_idx_gt_coast_dist.value() < accumulated_dist_per_point.size(); acc_dist_idx_gt_coast_dist.value()++) - { // search for the correct coast_dist_idx - if (accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value()] >= actual_coasting_dist) - { - break; - } - } - } - - assert( - acc_dist_idx_gt_coast_dist.has_value() && acc_dist_idx_gt_coast_dist < accumulated_dist_per_point.size()); // something has gone wrong; coasting_min_dist < coasting_dist ? - - const size_t point_idx_before_start = path.points.size() - 1 - acc_dist_idx_gt_coast_dist.value(); + const double extrude_speed = path.config.getSpeed() * path.speed_factor * path.speed_back_pressure_factor; + coasting_start_index = path_coasting.coasting_start_index; - Point3LL start; - { // computation of begin point of coasting - const coord_t residual_dist = actual_coasting_dist - accumulated_dist_per_point[acc_dist_idx_gt_coast_dist.value() - 1]; - const Point3LL& a = path.points[point_idx_before_start]; - const Point3LL& b = path.points[point_idx_before_start + 1]; - start = b + (a - b).resized(residual_dist); - } - - Point3LL prev_pt = gcode.getPositionXY(); - { // write normal extrude path: - for (size_t point_idx = 0; point_idx <= point_idx_before_start; point_idx++) + // write normal extrude path, followed by split extrusion to coasting starting point + for (size_t point_idx = 0; point_idx < coasting_start_index; point_idx++) { - auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); + auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); writeExtrusionRelativeZ(gcode, path.points[point_idx], extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); sendLineTo(path, path.points[point_idx], extrude_speed); - prev_pt = path.points[point_idx]; + previous_position = path.points[point_idx]; } - gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); - sendLineTo(path, start, extrude_speed); + gcode.writeExtrusion(path_coasting.coasting_start_pos, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); + sendLineTo(path, path_coasting.coasting_start_pos, extrude_speed); } // write coasting path - for (size_t point_idx = point_idx_before_start + 1; point_idx < path.points.size(); point_idx++) + for (size_t point_idx = coasting_start_index; point_idx < path.points.size(); point_idx++) { - auto [_, time] = extruder_plan.getPointToPointTime(prev_pt, path.points[point_idx], path); + auto [_, time] = extruder_plan.getPointToPointTime(previous_position, path.points[point_idx], path); insertTempOnTime(time, path_idx); const Ratio coasting_speed_modifier = extruder.settings_.get("coasting_speed"); const Velocity speed = Velocity(coasting_speed_modifier * path.config.getSpeed()); writeTravelRelativeZ(gcode, path.points[point_idx], speed, path.z_offset); - prev_pt = path.points[point_idx]; + previous_position = path.points[point_idx]; } + return true; } diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index e94c988b78..32eee9733f 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -417,7 +417,7 @@ const Point3LL& GCodeExport::getPosition() const } Point2LL GCodeExport::getPositionXY() const { - return Point2LL(current_position_.x_, current_position_.y_); + return current_position_.toPoint2LL(); } int GCodeExport::getPositionZ() const From f37622470e576687c1a06b3db42b58da6d02f89b Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 23 Oct 2024 14:37:48 +0000 Subject: [PATCH 10/41] Applied clang-format. --- include/utils/Coord_t.h | 8 ++++---- src/LayerPlan.cpp | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/utils/Coord_t.h b/include/utils/Coord_t.h index 8043bbd875..ac2e4741af 100644 --- a/include/utils/Coord_t.h +++ b/include/utils/Coord_t.h @@ -23,12 +23,12 @@ static inline coord_t operator"" _mu(unsigned long long i) #define INT2MM(n) (static_cast(n) / 1000.0) #define INT2MM2(n) (static_cast(n) / 1000000.0) -#define MM2INT(n) (static_cast((n) * 1000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM2_2INT(n) (static_cast((n) * 1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) -#define MM3_2INT(n) (static_cast((n) * 1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2INT(n) (static_cast((n)*1000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM2_2INT(n) (static_cast((n)*1000000 + 0.5 * (((n) > 0) - ((n) < 0)))) +#define MM3_2INT(n) (static_cast((n)*1000000000 + 0.5 * (((n) > 0) - ((n) < 0)))) #define INT2MICRON(n) ((n) / 1) -#define MICRON2INT(n) ((n) * 1) +#define MICRON2INT(n) ((n)*1) template [[nodiscard]] inline coord_t lerp(coord_t a, coord_t b, FactorType t) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index d560af1fc0..232bad63e5 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1244,7 +1244,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 3f771a788b92b9ce3a437d90e5207907ab0fe320 Mon Sep 17 00:00:00 2001 From: Remco Burema Date: Thu, 24 Oct 2024 10:28:12 +0200 Subject: [PATCH 11/41] Fix thin strips of infill missing. For a previous fix (see ticket CURA-11597 for the support issues fixed --doubling up lines of support-- and the related github discussion Cura/issues/18924 for the infill issues fixed --extra dots of infill at the edges--) we did some morpholigical operations to attempt to make the output more smooth. This caused more problems than its worth, and was in fact already removed in the original PR w.r.t. support (see commit eeec9636f7b3f749b790a1bea4c2901196cee027 ) -- seems we (read I) forgot to remove it for the infill case as well. part of CURA-12068 (or a belated contribution to CURA-11597). --- src/skin.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/skin.cpp b/src/skin.cpp index 351290be93..9d3f56db2c 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -530,7 +530,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); const Shape more_dense_infill = infill_area.difference(less_dense_infill); infill_area_per_combine_current_density.push_back( - simplifier.polygon(more_dense_infill.difference(sum_more_dense).offset(-infill_wall_width).offset(infill_wall_width))); + simplifier.polygon(more_dense_infill.difference(sum_more_dense))); if (is_connected) { sum_more_dense = sum_more_dense.unionPolygons(more_dense_infill); @@ -538,7 +538,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) } part.infill_area_per_combine_per_density.emplace_back(); std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); - infill_area_per_combine_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense).offset(-infill_wall_width).offset(infill_wall_width))); + infill_area_per_combine_current_density.push_back(simplifier.polygon(infill_area.difference(sum_more_dense))); part.infill_area_own = std::nullopt; // clear infill_area_own, it's not needed any more. assert(! part.infill_area_per_combine_per_density.empty() && "infill_area_per_combine_per_density is now initialized"); } From c0d91c9a41c68487fca32d44184ca4b03838aca2 Mon Sep 17 00:00:00 2001 From: rburema Date: Thu, 24 Oct 2024 08:32:56 +0000 Subject: [PATCH 12/41] Applied clang-format. --- src/TreeSupport.cpp | 8 ++++---- src/skin.cpp | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 8268bc2e9c..77e7acdd19 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -2037,10 +2037,11 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { AABB hole_aabb = AABB(hole); hole_aabb.expand(EPSILON); - if(hole.size() > 1) + if (hole.size() > 1) { // The hole contains other branches! It can not be fully removed. - // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring larger changes. + // This may not fully handle this case as there could be a situation where such a hole becomes invalid, but for now this is the best solution not requiring + // larger changes. holes_resting_outside[layer_idx].emplace(idx); } else if (! hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls, hole_aabb)).empty()) @@ -2057,8 +2058,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { // TODO should technically be outline: Check if this is fine either way as it would save an offset - if (hole_aabb.hit(AABB(hole2)) - && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) + if (hole_aabb.hit(AABB(hole2)) && ! hole.intersection(PolygonUtils::clipPolygonWithAABB(hole2, hole_aabb)).empty()) { hole_rest_map[layer_idx][idx].emplace_back(idx2); } diff --git a/src/skin.cpp b/src/skin.cpp index 9d3f56db2c..894692c815 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -529,8 +529,7 @@ void SkinInfillAreaComputation::generateGradualInfill(SliceMeshStorage& mesh) part.infill_area_per_combine_per_density.emplace_back(); std::vector& infill_area_per_combine_current_density = part.infill_area_per_combine_per_density.back(); const Shape more_dense_infill = infill_area.difference(less_dense_infill); - infill_area_per_combine_current_density.push_back( - simplifier.polygon(more_dense_infill.difference(sum_more_dense))); + infill_area_per_combine_current_density.push_back(simplifier.polygon(more_dense_infill.difference(sum_more_dense))); if (is_connected) { sum_more_dense = sum_more_dense.unionPolygons(more_dense_infill); From ebcd20a02d04e5f4a36fc30fabbca45d023d5b6f Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 25 Oct 2024 08:35:05 +0200 Subject: [PATCH 13/41] Add comment as suggested by review CURA-12175 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 232bad63e5..1cc3656ae7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1244,7 +1244,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -1293,7 +1293,7 @@ std::vector path_coasting = { ApplyCoasting::PartialCoasting, point_idx, coasting_start_pos }; chunk_coasting_point_reached = true; } - else if (point_idx == 0) + else if (point_idx == 0) // End of path reached (reverse iteration), coasting not fulfilled { path_coasting.apply_coasting = ApplyCoasting::CoastEntirePath; } From b92e174d3628a29d27647b2db3f2278dc475bd00 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 25 Oct 2024 06:35:54 +0000 Subject: [PATCH 14/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 1cc3656ae7..56d96c0e2a 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1244,7 +1244,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 893ad91048c0cf43972d5c9c26798697b785dda9 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 28 Oct 2024 14:42:58 +0100 Subject: [PATCH 15/41] Apply scarf seam and speed gradient in surface mode CURA-12176 The addSplitWall method is now a template so that it can handle either ExtrusionLine or Polygon objects. It also takes a function as parameter because the way we add extrusion lines is not the same for surface mode and normal mode. In order to simplify this function, I added a PathAdapter class to work with different types of paths with more transparency. --- CMakeLists.txt | 1 + include/LayerPlan.h | 88 +++++++++++--- src/FffGcodeWriter.cpp | 46 +++++-- src/LayerPlan.cpp | 269 +++++++++++++++++++++++++++++------------ src/TopSurface.cpp | 2 +- 5 files changed, 297 insertions(+), 109 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index daf6022fbb..47581ceca4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,6 +63,7 @@ set(engine_SRCS # Except main.cpp. src/Mold.cpp src/multiVolumes.cpp src/path_ordering.cpp + src/PathAdapter.cpp src/Preheat.cpp src/PrimeTower/PrimeTower.cpp src/PrimeTower/PrimeTowerNormal.cpp diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 0be204faf2..b2b207d3be 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -38,6 +38,9 @@ class Comb; class SliceDataStorage; class LayerPlanBuffer; +template +class PathAdapter; + /*! * The LayerPlan class stores multiple moves that are planned. * @@ -398,11 +401,15 @@ class LayerPlan : public NoCopy const Polygon& polygon, int startIdx, const bool reverse, + const Settings& settings, const GCodePathConfig& config, coord_t wall_0_wipe_dist = 0, bool spiralize = false, const Ratio& flow_ratio = 1.0_r, - bool always_retract = false); + bool always_retract = false, + bool scarf_seam = false, + bool smooth_speed = false, + bool is_candidate_small_feature = false); /*! * Add polygons to the gcode with optimized order. @@ -435,13 +442,16 @@ class LayerPlan : public NoCopy void addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, + const Settings& settings, 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()); + const std::optional start_near_location = std::optional(), + bool scarf_seam = false, + bool smooth_acceleration = false); /*! * Add a single line that is part of a wall to the gcode. @@ -836,18 +846,15 @@ class LayerPlan : public NoCopy /*! * \brief Add a wall to the gcode with optimized order, but split into pieces in order to facilitate the scarf seam and/or speed gradient. + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline * \param wall The full wall to be added * \param wall_length The pre-calculated full wall length * \param start_idx The index of the point where to start printing the wall * \param direction The direction along which to print the wall, which should be 1 or -1 * \param max_index The last index to be used when iterating over the wall segments - * \param settings The settings which should apply to this wall added to the layer plan * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air - * \param roofing_config The config with which to print the wall lines that are exposed to air - * \param bridge_config The config with which to print the wall lines that are spanning a bridge * \param flow_ratio The ratio with which to multiply the extrusion amount - * \param line_width_ratio The line width ratio to be applied - * \param non_bridge_line_volume A pseudo-volume that is derived from the print speed and flow of the non-bridge lines that have preceded this lin + * \param nominal_line_width The nominal line width for the wall * \param min_bridge_line_len The minimum line width to allow an extrusion move to be processed as a bridge move * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) * \param is_small_feature Indicates whether the wall is so small that it should be processed differently @@ -864,26 +871,26 @@ class LayerPlan : public NoCopy * \param end_speed_ratio The ratio of the top speed to be applied when finishing a segment * \param decelerate_length The pre-calculated length of the deceleration phase * \param is_scarf_closure Indicates whether this function is called to make the scarf closure (overlap over the first scarf pass) or the normal first pass of the wall + * \param compute_distance_to_bridge_start Whether we should compute the distance to start of bridge. This is + * possible only if PathType is ExtrusionLine and will be ignored otherwise. + * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters */ + template void addSplitWall( - const ExtrusionLine& wall, + const PathAdapter& wall, const coord_t wall_length, - size_t start_idx, - const int direction, + const size_t start_idx, const size_t max_index, - const Settings& settings, + const int direction, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - const double flow_ratio, - const Ratio line_width_ratio, - double& non_bridge_line_volume, - const coord_t min_bridge_line_len, const bool always_retract, const bool is_small_feature, Ratio small_feature_speed_factor, const coord_t max_area_deviation, const auto max_resolution, + const double flow_ratio, + const coord_t nominal_line_width, + const coord_t min_bridge_line_len, const auto scarf_seam_length, const auto scarf_seam_start_ratio, const auto scarf_split_distance, @@ -893,7 +900,52 @@ class LayerPlan : public NoCopy const coord_t accelerate_length, const Ratio end_speed_ratio, const coord_t decelerate_length, - const bool is_scarf_closure); + const bool is_scarf_closure, + const bool compute_distance_to_bridge_start, + const std::function& func_add_segment); + + /*! + * \brief Add a wall to the gcode with optimized order, possibly adding a scarf seam / speed gradient according to settings + * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline + * \param wall The full wall to be added + * \param start_idx The index of the point where to start printing the wall + * \param settings The settings which should apply to this wall added to the layer plan + * \param default_config The config with which to print the wall lines that are not spanning a bridge or are exposed to air + * \param flow_ratio The ratio with which to multiply the extrusion amount + * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param is_closed Indicates whether the path is closed (or open) + * \param is_reversed Indicates if the path is to be processed backwards + * \param is_candidate_small_feature Indicates whether the path should be tested for being treated as a smell feature + * \param scarf_seam Indicates whether we may set a scraf seam to the path + * \param smooth_speed Indicates whether we may set a speed gradient to the path + * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters + */ + template + void addWallWithScarfSeam( + const PathAdapter& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_candidate_small_feature, + const bool scarf_seam, + const bool smooth_speed, + const std::function& func_add_segment); /*! * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index fcba3b052b..a8642cf95c 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -735,6 +735,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_base_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -895,6 +896,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_interface_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -1073,6 +1075,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) gcode_layer.addPolygonsByOptimizer( raft_polygons, gcode_layer.configs_storage_.raft_surface_config, + mesh_group_settings, ZSeamConfig(), wipe_dist, spiralize, @@ -1477,13 +1480,14 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan void FffGcodeWriter::processOozeShield(const SliceDataStorage& storage, LayerPlan& gcode_layer) const { LayerIndex layer_nr = std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr()); - if (layer_nr == 0 && Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("adhesion_type") == EPlatformAdhesion::BRIM) + const Settings& mesh_group_settings = Application::getInstance().current_slice_->scene.current_mesh_group->settings; + if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::BRIM) { return; // ooze shield already generated by brim } if (storage.ooze_shield.size() > 0 && layer_nr < storage.ooze_shield.size()) { - gcode_layer.addPolygonsByOptimizer(storage.ooze_shield[layer_nr], gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0]); + gcode_layer.addPolygonsByOptimizer(storage.ooze_shield[layer_nr], gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0], mesh_group_settings); } } @@ -1516,7 +1520,7 @@ void FffGcodeWriter::processDraftShield(const SliceDataStorage& storage, LayerPl } } - gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0]); + gcode_layer.addPolygonsByOptimizer(storage.draft_protection_shield, gcode_layer.configs_storage_.skirt_brim_config_per_extruder[0], mesh_group_settings); } void FffGcodeWriter::calculateExtruderOrderPerLayer(const SliceDataStorage& storage) @@ -1723,7 +1727,26 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& mesh.settings.get("z_seam_corner"), mesh.settings.get("wall_line_width_0") * 2); const bool spiralize = Application::getInstance().current_slice_->scene.current_mesh_group->settings.get("magic_spiralize"); - gcode_layer.addPolygonsByOptimizer(polygons, mesh_config.inset0_config, z_seam_config, mesh.settings.get("wall_0_wipe_dist"), spiralize); + constexpr Ratio flow_ratio = 1.0; + constexpr bool always_retract = false; + constexpr bool reverse_order = false; + constexpr std::optional start_near_location = std::nullopt; + constexpr bool scarf_seam = true; + constexpr bool smooth_speed = true; + + gcode_layer.addPolygonsByOptimizer( + polygons, + mesh_config.inset0_config, + mesh.settings, + z_seam_config, + mesh.settings.get("wall_0_wipe_dist"), + spiralize, + flow_ratio, + always_retract, + reverse_order, + start_near_location, + scarf_seam, + smooth_speed); addMeshOpenPolyLinesToGCode(mesh, mesh_config, gcode_layer); } @@ -1966,7 +1989,7 @@ bool FffGcodeWriter::processMultiLayerInfill( { constexpr bool force_comb_retract = false; gcode_layer.addTravel(infill_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[combine_idx]); + gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[combine_idx], mesh.settings); } if (! infill_lines.empty()) @@ -2723,7 +2746,7 @@ bool FffGcodeWriter::processSingleLayerInfill( constexpr bool force_comb_retract = false; // start the infill polygons at the nearest vertex to the current location gcode_layer.addTravel(PolygonUtils::findNearestVert(gcode_layer.getLastPlannedPositionOrStartingPosition(), infill_polygons).p(), force_comb_retract); - gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); + gcode_layer.addPolygonsByOptimizer(infill_polygons, mesh_config.infill_config[0], mesh.settings, ZSeamConfig(), 0, false, 1.0_r, false, false, near_start_location); } const bool enable_travel_optimization = mesh.settings.get("infill_enable_travel_optimization"); if (pattern == EFillMethod::GRID || pattern == EFillMethod::LINES || pattern == EFillMethod::TRIANGLES || pattern == EFillMethod::CUBIC @@ -2965,7 +2988,7 @@ bool FffGcodeWriter::processInsets( gcode_layer.addTravel(spiral_inset[spiral_start_vertex]); } int wall_0_wipe_dist(0); - gcode_layer.addPolygonsByOptimizer(part.spiral_wall, mesh_config.inset0_config, ZSeamConfig(), wall_0_wipe_dist); + gcode_layer.addPolygonsByOptimizer(part.spiral_wall, mesh_config.inset0_config, mesh.settings, ZSeamConfig(), wall_0_wipe_dist); } } // for non-spiralized layers, determine the shape of the unsupported areas below this part @@ -3596,7 +3619,7 @@ void FffGcodeWriter::processSkinPrintFeature( { constexpr bool force_comb_retract = false; gcode_layer.addTravel(skin_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(skin_polygons, config); + gcode_layer.addPolygonsByOptimizer(skin_polygons, config, mesh.settings); } if (monotonic) @@ -4001,6 +4024,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer gcode_layer.addPolygonsByOptimizer( support_polygons, configs[combine_idx], + mesh_group_settings, z_seam_config, wall_0_wipe_dist, spiralize, @@ -4167,13 +4191,13 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, con gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support if (gcode_layer.getLayerNr() == 0) { - gcode_layer.addPolygonsByOptimizer(wall, current_roof_config); + gcode_layer.addPolygonsByOptimizer(wall, current_roof_config, roof_extruder.settings_); } if (! roof_polygons.empty()) { constexpr bool force_comb_retract = false; gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config); + gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config, roof_extruder.settings_); } if (! roof_paths.empty()) { @@ -4287,7 +4311,7 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L { constexpr bool force_comb_retract = false; gcode_layer.addTravel(bottom_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(bottom_polygons, gcode_layer.configs_storage_.support_bottom_config); + gcode_layer.addPolygonsByOptimizer(bottom_polygons, gcode_layer.configs_storage_.support_bottom_config, bottom_extruder.settings_); } if (! bottom_paths.empty()) { diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2b17229c36..c7467bf5fe 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -14,6 +14,7 @@ #include "Application.h" //To communicate layer view data. #include "ExtruderTrain.h" +#include "PathAdapter.h" #include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. #include "Slice.h" #include "WipeScriptConfig.h" @@ -557,29 +558,54 @@ void LayerPlan::addPolygon( const Polygon& polygon, int start_idx, const bool backwards, + const Settings& settings, const GCodePathConfig& config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio& flow_ratio, - bool always_retract) + bool always_retract, + bool scarf_seam, + bool smooth_speed, + bool is_candidate_small_feature) { - constexpr Ratio width_ratio = 1.0_r; // Not printed with variable line width. + constexpr bool is_closed = true; + Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); - const int direction = backwards ? -1 : 1; - for (size_t point_idx = 1; point_idx < polygon.size(); point_idx++) - { - Point2LL p1 = polygon[(start_idx + point_idx * direction + polygon.size()) % polygon.size()]; - addExtrusionMove(p1, config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize); - p0 = p1; - } + + addWallWithScarfSeam( + PathAdapter(polygon, config.getLineWidth()), + start_idx, + settings, + config, + flow_ratio, + always_retract, + is_closed, + backwards, + is_candidate_small_feature, + scarf_seam, + smooth_speed, + [this, &config, &spiralize]( + const Point3LL& /*start*/, + const Point3LL& end, + const Ratio& speed_factor, + const Ratio& actual_flow_ratio, + const Ratio& line_width_ratio, + const coord_t /*distance_to_bridge_start*/) + { + constexpr double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT; + constexpr bool travel_to_z = false; + + addExtrusionMove(end, config, SpaceFillType::Polygons, actual_flow_ratio, line_width_ratio, spiralize, speed_factor, fan_speed, travel_to_z); + }); + + if (polygon.size() > 2) { - addExtrusionMove(polygon[start_idx], config, SpaceFillType::Polygons, flow_ratio, width_ratio, spiralize); - if (wall_0_wipe_dist > 0) { // apply outer wall wipe p0 = polygon[start_idx]; + const int direction = backwards ? -1 : 1; int distance_traversed = 0; for (size_t point_idx = 1;; point_idx++) { @@ -611,13 +637,16 @@ void LayerPlan::addPolygon( void LayerPlan::addPolygonsByOptimizer( const Shape& polygons, const GCodePathConfig& config, + const Settings& settings, const ZSeamConfig& z_seam_config, coord_t wall_0_wipe_dist, bool spiralize, const Ratio flow_ratio, bool always_retract, bool reverse_order, - const std::optional start_near_location) + const std::optional start_near_location, + bool scarf_seam, + bool smooth_speed) { if (polygons.empty()) { @@ -630,20 +659,33 @@ void LayerPlan::addPolygonsByOptimizer( } orderOptimizer.optimize(); - if (! reverse_order) + auto add_polygons + = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &always_retract, &scarf_seam, &smooth_speed](const auto& iterator_begin, const auto& iterator_end) { - for (const PathOrdering& path : orderOptimizer.paths_) + for (auto iterator = iterator_begin; iterator != iterator_end; ++iterator) { - addPolygon(*path.vertices_, path.start_vertex_, path.backwards_, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); + addPolygon( + *iterator->vertices_, + iterator->start_vertex_, + iterator->backwards_, + settings, + config, + wall_0_wipe_dist, + spiralize, + flow_ratio, + always_retract, + scarf_seam, + smooth_speed); } + }; + + if (! reverse_order) + { + add_polygons(orderOptimizer.paths_.begin(), orderOptimizer.paths_.end()); } else { - for (int index = orderOptimizer.paths_.size() - 1; index >= 0; --index) - { - const PathOrdering& path = orderOptimizer.paths_[index]; - addPolygon(*path.vertices_, path.start_vertex_, path.backwards_, config, wall_0_wipe_dist, spiralize, flow_ratio, always_retract); - } + add_polygons(orderOptimizer.paths_.rbegin(), orderOptimizer.paths_.rend()); } } @@ -865,7 +907,7 @@ void LayerPlan::addWallLine( flow, width_factor, spiralize, - segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : 1.0_r, + segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -996,25 +1038,22 @@ void LayerPlan::addWall( addWall(ewall, start_idx, settings, default_config, roofing_config, bridge_config, wall_0_wipe_dist, flow_ratio, always_retract, is_closed, is_reversed, is_linked_path); } +template void LayerPlan::addSplitWall( - const ExtrusionLine& wall, + const PathAdapter& wall, const coord_t wall_length, - size_t start_idx, - const int direction, + const size_t start_idx, const size_t max_index, - const Settings& settings, + const int direction, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - const double flow_ratio, - const Ratio line_width_ratio, - double& non_bridge_line_volume, - const coord_t min_bridge_line_len, const bool always_retract, const bool is_small_feature, Ratio small_feature_speed_factor, const coord_t max_area_deviation, const auto max_resolution, + const double flow_ratio, + const coord_t nominal_line_width, + const coord_t min_bridge_line_len, const auto scarf_seam_length, const auto scarf_seam_start_ratio, const auto scarf_split_distance, @@ -1024,13 +1063,22 @@ void LayerPlan::addSplitWall( const coord_t accelerate_length, const Ratio end_speed_ratio, const coord_t decelerate_length, - const bool is_scarf_closure) + const bool is_scarf_closure, + const bool compute_distance_to_bridge_start, + const std::function& func_add_segment) { coord_t distance_to_bridge_start = 0; // will be updated before each line is processed - ExtrusionJunction p0 = wall[start_idx]; + Point2LL p0 = wall.pointAt(start_idx); + coord_t w0 = wall.lineWidthAt(start_idx); bool first_line = ! is_scarf_closure; bool first_split = ! is_scarf_closure; - Point3LL split_origin = p0.p_; + Point3LL split_origin = p0; if (! is_scarf_closure && scarf_seam_length > 0) { split_origin.z_ = scarf_max_z_offset; @@ -1044,16 +1092,21 @@ void LayerPlan::addSplitWall( for (size_t point_idx = 1; point_idx < max_index; point_idx++) { - const ExtrusionJunction& p1 = wall[(wall.size() + start_idx + point_idx * direction) % wall.size()]; + const size_t actual_point_index = (wall.size() + start_idx + point_idx * direction) % wall.size(); + const Point2LL& p1 = wall.pointAt(actual_point_index); + const coord_t w1 = wall.lineWidthAt(actual_point_index); - if (! bridge_wall_mask_.empty()) + if constexpr (std::is_same_v) { - distance_to_bridge_start = computeDistanceToBridgeStart(wall, (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); + if (compute_distance_to_bridge_start && ! bridge_wall_mask_.empty()) + { + distance_to_bridge_start = computeDistanceToBridgeStart(wall.getPath(), (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); + } } if (first_line) { - addTravel(p0.p_, always_retract); + addTravel(p0, always_retract); first_line = false; } @@ -1069,8 +1122,8 @@ void LayerPlan::addSplitWall( pieces we'd want to get low enough deviation, then check if each piece is not too short at the end. */ - const coord_t delta_line_width = p1.w_ - p0.w_; - const Point2LL line_vector = p1.p_ - p0.p_; + const coord_t delta_line_width = w1 - w0; + const Point2LL line_vector = p1 - p0; const coord_t line_length = vSize(line_vector); /* Calculate how much the line would deviate from the trapezoidal shape if printed at average width. @@ -1091,12 +1144,13 @@ void LayerPlan::addSplitWall( const double average_progress = (double(piece) + 0.5) / pieces; // How far along this line to sample the line width in the middle of this piece. // Round the line_width value to overcome floating point rounding issues, otherwise we may end up with slightly different values // and the generated GCodePath objects will not be merged together, which some subsequent algorithms rely on (e.g. coasting) - const coord_t line_width = std::lrint(static_cast(p0.w_) + average_progress * static_cast(delta_line_width)); - const Point2LL destination = p0.p_ + normal(line_vector, piece_length * (piece + 1)); + const coord_t line_width = std::lrint(static_cast(w0) + average_progress * static_cast(delta_line_width)); + const Ratio line_width_ratio = static_cast(line_width) / nominal_line_width; + const Point2LL destination = p0 + normal(line_vector, piece_length * (piece + 1)); if (is_small_feature && ! is_scarf_closure) { constexpr bool spiralize = false; - addExtrusionMove(destination, default_config, SpaceFillType::Polygons, flow_ratio, line_width * line_width_ratio, spiralize, small_feature_speed_factor); + addExtrusionMove(destination, default_config, SpaceFillType::Polygons, flow_ratio, line_width_ratio, spiralize, small_feature_speed_factor); } else { @@ -1170,7 +1224,7 @@ void LayerPlan::addSplitWall( if (first_split) { // Manually add a Z-only travel move to set the nozzle at the height of the first point - addTravel(p0.p_, always_retract, split_origin.z_); + addTravel(p0, always_retract, split_origin.z_); first_split = false; } } @@ -1198,20 +1252,13 @@ void LayerPlan::addSplitWall( } // now add the (sub-)segment - constexpr bool travel_to_z = false; - addWallLine( + func_add_segment( split_origin, split_destination, - settings, - default_config, - roofing_config, - bridge_config, - flow_ratio * scarf_segment_flow_ratio, - line_width * line_width_ratio, - non_bridge_line_volume, accelerate_speed_factor * decelerate_speed_factor, - distance_to_bridge_start, - travel_to_z); + flow_ratio * scarf_segment_flow_ratio, + line_width_ratio, + distance_to_bridge_start); wall_processed_distance = destination_position; piece_remaining_distance -= length_to_process; @@ -1308,39 +1355,41 @@ coord_t LayerPlan::computeDistanceToBridgeStart(const ExtrusionLine& wall, const return distance_to_bridge_start; } -void LayerPlan::addWall( - const ExtrusionLine& wall, +template +void LayerPlan::addWallWithScarfSeam( + const PathAdapter& wall, size_t start_idx, const Settings& settings, const GCodePathConfig& default_config, - const GCodePathConfig& roofing_config, - const GCodePathConfig& bridge_config, - coord_t wall_0_wipe_dist, const double flow_ratio, bool always_retract, const bool is_closed, const bool is_reversed, - const bool is_linked_path, + const bool is_candidate_small_feature, const bool scarf_seam, - const bool smooth_speed) + const bool smooth_speed, + const std::function& func_add_segment) { if (wall.empty()) { return; } - const bool actual_scarf_seam = scarf_seam && is_closed; - double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output + const bool actual_scarf_seam = scarf_seam && is_closed; const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); - const Ratio nominal_line_width_multiplier{ - 1.0 / Ratio{ static_cast(default_config.getLineWidth()) } - }; // we multiply the flow with the actual wanted line width (for that junction), and then multiply with this + const coord_t nominal_line_width = default_config.getLineWidth(); const coord_t wall_length = wall.length(); const coord_t small_feature_max_length = settings.get("small_feature_max_length"); - const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || wall.inset_idx_ == 0) && wall_length < small_feature_max_length; + const bool is_small_feature = (small_feature_max_length > 0) && (layer_nr_ == 0 || is_candidate_small_feature) && wall_length < small_feature_max_length; const Velocity min_speed = fan_speed_layer_time_settings_per_extruder_[getLastPlannedExtruderTrain()->extruder_nr_].cool_min_speed; Ratio small_feature_speed_factor = settings.get((layer_nr_ == 0) ? "small_feature_speed_factor_0" : "small_feature_speed_factor"); small_feature_speed_factor = std::max(static_cast(small_feature_speed_factor), static_cast(min_speed / default_config.getSpeed())); @@ -1368,25 +1417,23 @@ void LayerPlan::addWall( auto addSplitWallPass = [&](bool is_scarf_closure) { + constexpr bool compute_distance_to_bridge_start = true; + addSplitWall( - wall, + PathAdapter(wall), wall_length, start_idx, - direction, max_index, - settings, + direction, default_config, - roofing_config, - bridge_config, - flow_ratio, - nominal_line_width_multiplier, - non_bridge_line_volume, - min_bridge_line_len, always_retract, is_small_feature, small_feature_speed_factor, max_area_deviation, max_resolution, + flow_ratio, + nominal_line_width, + min_bridge_line_len, layer_nr_ > 0 ? scarf_seam_length : 0, scarf_seam_start_ratio, scarf_split_distance, @@ -1396,7 +1443,9 @@ void LayerPlan::addWall( accelerate_length, end_speed_ratio, decelerate_length, - is_scarf_closure); + is_scarf_closure, + compute_distance_to_bridge_start, + func_add_segment); }; // First pass to add the wall with the scarf beginning and acceleration @@ -1407,6 +1456,68 @@ void LayerPlan::addWall( // Second pass to add the scarf closure addSplitWallPass(true); } +} + +void LayerPlan::addWall( + const ExtrusionLine& wall, + size_t start_idx, + const Settings& settings, + const GCodePathConfig& default_config, + const GCodePathConfig& roofing_config, + const GCodePathConfig& bridge_config, + coord_t wall_0_wipe_dist, + const double flow_ratio, + bool always_retract, + const bool is_closed, + const bool is_reversed, + const bool is_linked_path, + const bool scarf_seam, + const bool smooth_speed) +{ + if (wall.empty()) + { + return; + } + + double non_bridge_line_volume = max_non_bridge_line_volume; // assume extruder is fully pressurised before first non-bridge line is output + const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); + + addWallWithScarfSeam( + PathAdapter(wall), + start_idx, + settings, + default_config, + flow_ratio, + always_retract, + is_closed, + is_reversed, + wall.inset_idx_ == 0, + scarf_seam, + smooth_speed, + [this, &settings, &default_config, &roofing_config, &bridge_config, &non_bridge_line_volume]( + const Point3LL& start, + const Point3LL& end, + const Ratio& speed_factor, + const Ratio& actual_flow_ratio, + const Ratio& line_width_ratio, + const coord_t distance_to_bridge_start) + { + constexpr bool travel_to_z = false; + + addWallLine( + start, + end, + settings, + default_config, + roofing_config, + bridge_config, + actual_flow_ratio, + line_width_ratio, + non_bridge_line_volume, + speed_factor, + distance_to_bridge_start, + travel_to_z); + }); if (wall.size() >= 2) { @@ -2381,7 +2492,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) diff --git a/src/TopSurface.cpp b/src/TopSurface.cpp index 15a6b03cf8..beb6154d3e 100644 --- a/src/TopSurface.cpp +++ b/src/TopSurface.cpp @@ -124,7 +124,7 @@ bool TopSurface::ironing(const SliceDataStorage& storage, const SliceMeshStorage { constexpr bool force_comb_retract = false; layer.addTravel(ironing_polygons[0][0], force_comb_retract); - layer.addPolygonsByOptimizer(ironing_polygons, line_config, ZSeamConfig()); + layer.addPolygonsByOptimizer(ironing_polygons, line_config, mesh.settings, ZSeamConfig()); added = true; } if (! ironing_lines.empty()) From ca5f958c9fec3a6394deb82429169b55fcf52c2c Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Mon, 28 Oct 2024 13:43:26 +0000 Subject: [PATCH 16/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index c7467bf5fe..66723a14bf 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2492,7 +2492,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From d036c0bb08ddf0fe1c6e9c287ea321374d2a477a Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 28 Oct 2024 15:04:46 +0100 Subject: [PATCH 17/41] Add missing PathAdapter files CURA-12176 --- include/PathAdapter.h | 63 +++++++++++++++++++++++++++++++++++++++++++ src/PathAdapter.cpp | 35 ++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 include/PathAdapter.h create mode 100644 src/PathAdapter.cpp diff --git a/include/PathAdapter.h b/include/PathAdapter.h new file mode 100644 index 0000000000..21a71ee1da --- /dev/null +++ b/include/PathAdapter.h @@ -0,0 +1,63 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef PATH_ADAPTER_H +#define PATH_ADAPTER_H + +#include + +#include "geometry/Point2LL.h" +#include "utils/Coord_t.h" + +namespace cura +{ + +/* Adapter class to allow extrusion-related functions to work with either an ExtrusionLine or a Polygon */ +template +class PathAdapter +{ +public: + /*! + * \brief Base constructor + * \param path The actual stored path + * \param fixed_line_width The fixed line width in case the stored path doesn't handle information about a variable + * line width. Can be omitted otherwise. + */ + PathAdapter(const PathType& path, coord_t fixed_line_width = 0) + : path_(path) + , fixed_line_width_(fixed_line_width) + { + } + + bool empty() const + { + return path_.empty(); + } + + size_t size() const + { + return path_.size(); + } + + coord_t length() const + { + return path_.length(); + } + + const Point2LL& pointAt(size_t index) const; + + coord_t lineWidthAt(size_t index) const; + + const PathType& getPath() const + { + return path_; + } + +private: + const PathType& path_; + const coord_t fixed_line_width_; +}; + +} // namespace cura + +#endif // PATH_ADAPTER_H diff --git a/src/PathAdapter.cpp b/src/PathAdapter.cpp new file mode 100644 index 0000000000..09a5437493 --- /dev/null +++ b/src/PathAdapter.cpp @@ -0,0 +1,35 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "PathAdapter.h" + +#include "utils/ExtrusionLine.h" + +namespace cura +{ + +template<> +const Point2LL& PathAdapter::pointAt(size_t index) const +{ + return path_.junctions_.at(index).p_; +} + +template<> +coord_t PathAdapter::lineWidthAt(size_t index) const +{ + return path_.junctions_.at(index).w_; +} + +template<> +const Point2LL& PathAdapter::pointAt(size_t index) const +{ + return path_.at(index); +} + +template<> +coord_t PathAdapter::lineWidthAt(size_t /*index*/) const +{ + return fixed_line_width_; +} + +} // namespace cura From 04262454d1560b98ae1f04a0694c26ac220f3b0c Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Tue, 29 Oct 2024 10:57:26 +0100 Subject: [PATCH 18/41] Add writeGcodeFile function to GCodeExport Introduce a new method writeGcodeFile to the GCodeExport class, which ensures that the output stream is properly flushed. This function is also called at the end of the Gcode writing process to finalize the file. NP-470 --- include/gcodeExport.h | 2 ++ src/FffGcodeWriter.cpp | 1 + src/gcodeExport.cpp | 5 +++++ 3 files changed, 8 insertions(+) diff --git a/include/gcodeExport.h b/include/gcodeExport.h index 7b211f8516..ee18936e39 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -409,6 +409,8 @@ class GCodeExport : public NoCopy */ bool needPrimeBlob() const; + void writeGcodeFile(); + private: /*! * Coordinates are build plate coordinates, which might be offsetted when extruder offsets are encoded in the gcode. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index fcba3b052b..bed6ad7176 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -4437,6 +4437,7 @@ void FffGcodeWriter::finalize() } gcode.writeComment("End of Gcode"); + gcode.writeGcodeFile(); /* the profile string below can be executed since the M25 doesn't end the gcode on an UMO and when printing via USB. gcode.writeCode("M25 ;Stop reading from this point on."); diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index e94c988b78..c93a068115 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1726,9 +1726,14 @@ void GCodeExport::finalize(const char* endCode) for (int n = 1; n < MAX_EXTRUDERS; n++) if (getTotalFilamentUsed(n) > 0) spdlog::info("Filament {}: {}", n + 1, int(getTotalFilamentUsed(n))); +} + +void GCodeExport::writeGcodeFile() +{ output_stream_->flush(); } + double GCodeExport::getExtrudedVolumeAfterLastWipe(size_t extruder) { return eToMm3(extruder_attr_[extruder].last_e_value_after_wipe_, extruder); From b394b7bfcc9658045aaa18e5fcaa736e6d7efbae Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Tue, 29 Oct 2024 10:59:44 +0100 Subject: [PATCH 19/41] Remove unnecessary blank line NP-470 --- src/gcodeExport.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index c93a068115..b4ac8f1bdd 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1733,7 +1733,6 @@ void GCodeExport::writeGcodeFile() output_stream_->flush(); } - double GCodeExport::getExtrudedVolumeAfterLastWipe(size_t extruder) { return eToMm3(extruder_attr_[extruder].last_e_value_after_wipe_, extruder); From 3bb6d32ec62ce7cd9b67dd607fd32ef25ac0968d Mon Sep 17 00:00:00 2001 From: saumyaj3 Date: Tue, 29 Oct 2024 11:51:02 +0100 Subject: [PATCH 20/41] Refactor writeGcodeFile to flushOutputStream Renamed the function writeGcodeFile to flushOutputStream for clarity. This change more accurately reflects the function's responsibility, which is to flush the output stream instead of managing file writing directly. Updated all references in the source files accordingly. NP-470 --- include/gcodeExport.h | 5 ++++- src/FffGcodeWriter.cpp | 2 +- src/gcodeExport.cpp | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/include/gcodeExport.h b/include/gcodeExport.h index ee18936e39..0dd81541b8 100644 --- a/include/gcodeExport.h +++ b/include/gcodeExport.h @@ -409,7 +409,10 @@ class GCodeExport : public NoCopy */ bool needPrimeBlob() const; - void writeGcodeFile(); + /* + * Function is used to write the content of output_stream to the gcode file + */ + void flushOutputStream(); private: /*! diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index bed6ad7176..2417723014 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -4437,7 +4437,7 @@ void FffGcodeWriter::finalize() } gcode.writeComment("End of Gcode"); - gcode.writeGcodeFile(); + gcode.flushOutputStream(); /* the profile string below can be executed since the M25 doesn't end the gcode on an UMO and when printing via USB. gcode.writeCode("M25 ;Stop reading from this point on."); diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index b4ac8f1bdd..f9749b0d6e 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -1726,9 +1726,10 @@ void GCodeExport::finalize(const char* endCode) for (int n = 1; n < MAX_EXTRUDERS; n++) if (getTotalFilamentUsed(n) > 0) spdlog::info("Filament {}: {}", n + 1, int(getTotalFilamentUsed(n))); + flushOutputStream(); } -void GCodeExport::writeGcodeFile() +void GCodeExport::flushOutputStream() { output_stream_->flush(); } From 81d2ce014358a3dc930c71770e3ad7341ff13d8a Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 13:27:05 +0100 Subject: [PATCH 21/41] Use a multi-pass scoring to better handle seams on regular shapes CURA-12218 The previous rework of the score calculation removed the relative comparison between vertices scores to ensure the consistency. The new strategy performed very badly on shapes where multiple points would get a similar score, e.g. finding sharp corners on a cylinder. We now process a multi-pass scoring, so that all points having a very similar score at their main criterion will now go through a second pass with different criteria. --- CMakeLists.txt | 6 + include/PathOrderOptimizer.h | 270 ++++-------------- include/utils/CriterionScore.h | 31 -- include/utils/Score.h | 61 ---- include/utils/scoring/BestElementFinder.h | 81 ++++++ .../utils/scoring/CornerScoringCriterion.h | 67 +++++ .../utils/scoring/DistanceScoringCriterion.h | 32 +++ .../scoring/ExclusionAreaScoringCriterion.h | 34 +++ .../utils/scoring/RandomScoringCriterion.h | 27 ++ include/utils/scoring/ScoringCriterion.h | 34 +++ src/utils/scoring/BestElementFinder.cpp | 74 +++++ src/utils/scoring/CornerScoringCriterion.cpp | 120 ++++++++ .../scoring/DistanceScoringCriterion.cpp | 33 +++ .../scoring/ExclusionAreaScoringCriterion.cpp | 24 ++ src/utils/scoring/RandomScoringCriterion.cpp | 20 ++ 15 files changed, 604 insertions(+), 310 deletions(-) delete mode 100644 include/utils/CriterionScore.h delete mode 100644 include/utils/Score.h create mode 100644 include/utils/scoring/BestElementFinder.h create mode 100644 include/utils/scoring/CornerScoringCriterion.h create mode 100644 include/utils/scoring/DistanceScoringCriterion.h create mode 100644 include/utils/scoring/ExclusionAreaScoringCriterion.h create mode 100644 include/utils/scoring/RandomScoringCriterion.h create mode 100644 include/utils/scoring/ScoringCriterion.h create mode 100644 src/utils/scoring/BestElementFinder.cpp create mode 100644 src/utils/scoring/CornerScoringCriterion.cpp create mode 100644 src/utils/scoring/DistanceScoringCriterion.cpp create mode 100644 src/utils/scoring/ExclusionAreaScoringCriterion.cpp create mode 100644 src/utils/scoring/RandomScoringCriterion.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index daf6022fbb..36b1ffa618 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -156,6 +156,12 @@ set(engine_SRCS # Except main.cpp. src/utils/VoxelUtils.cpp src/utils/MixedPolylineStitcher.cpp + src/utils/scoring/BestElementFinder.cpp + src/utils/scoring/CornerScoringCriterion.cpp + src/utils/scoring/DistanceScoringCriterion.cpp + src/utils/scoring/ExclusionAreaScoringCriterion.cpp + src/utils/scoring/RandomScoringCriterion.cpp + src/geometry/Point2LL.cpp src/geometry/Point3LL.cpp src/geometry/Polygon.cpp diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index d7ad78eea8..252890a635 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -21,10 +21,14 @@ #include "path_ordering.h" #include "settings/EnumSettings.h" //To get the seam settings. #include "settings/ZSeamConfig.h" //To read the seam configuration. -#include "utils/Score.h" #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. #include "utils/math.h" #include "utils/polygonUtils.h" +#include "utils/scoring/BestElementFinder.h" +#include "utils/scoring/CornerScoringCriterion.h" +#include "utils/scoring/DistanceScoringCriterion.h" +#include "utils/scoring/ExclusionAreaScoringCriterion.h" +#include "utils/scoring/RandomScoringCriterion.h" #include "utils/views/dfs.h" namespace cura @@ -723,166 +727,74 @@ class PathOrderOptimizer } // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. + const PointsSet& points = *path.converted_; - // ########## Step 1: define the weighs of each criterion + // ########## Step 1: define the main criteria to be applied and their weights // Standard weight for the "main" selection criterion, depending on the selected strategy. There should be // exactly one calculation using this criterion. - constexpr double weight_main_criterion = 1.0; + BestElementFinder best_candidate_finder; + BestElementFinder::CriteriaPass main_criteria_pass; + main_criteria_pass.outsider_delta_threshold = 0.05; - // If seam is not "shortest", we still compute the shortest distance score but with a very low weight - const double weight_distance = path.seam_config_.type_ == EZSeamType::SHORTEST ? weight_main_criterion : 0.02; + BestElementFinder::WeighedCriterion main_criterion; - // Avoiding overhangs is more important than the rest - constexpr double weight_exclude_overhang = 2.0; - - // In order to avoid jumping seams, we give a small score to the vertex X and Y position, so that if we have - // e.g. multiple corners with the same angle, we will always choose the ones at the top-right - constexpr double weight_consistency_x = 0.05; - constexpr double weight_consistency_y = weight_consistency_x / 2.0; // Less weight on Y to avoid symmetry effects - - // ########## Step 2: define which criteria should be taken into account in the total score - const bool calculate_forced_pos_score = path.force_start_index_.has_value(); - - bool calculate_distance_score = false; - bool calculate_corner_score = false; - bool calculate_random_score = false; - if (! calculate_forced_pos_score) + if (path.force_start_index_.has_value()) // Actually handles EZSeamType::USER_SPECIFIED { - // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - calculate_distance_score = path.seam_config_.type_ != EZSeamType::SHARPEST_CORNER || path.seam_config_.corner_pref_ == EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE; - - calculate_corner_score - = path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER - && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN); - - calculate_random_score = path.seam_config_.type_ == EZSeamType::RANDOM; + main_criterion.criterion = std::make_shared(points, points.at(path.force_start_index_.value())); } - - const bool calculate_consistency_score = calculate_distance_score || calculate_corner_score; - - // Whatever the strategy, always avoid overhang - const bool calculate_overhang_score = ! overhang_areas_.empty(); - - // ########## Step 3: calculate some values that we are going to need multiple times, depending on which scoring is active - const AABB path_bounding_box = calculate_consistency_score ? AABB(*path.converted_) : AABB(); - - const Point2LL forced_start_pos = path.force_start_index_.has_value() ? path.converted_->at(path.force_start_index_.value()) : Point2LL(); - - std::vector segments_sizes; - coord_t total_length = 0; - if (calculate_corner_score) + else { - segments_sizes.resize(path.converted_->size()); - for (size_t i = 0; i < path.converted_->size(); ++i) + if (path.seam_config_.type_ == EZSeamType::SHORTEST) { - const Point2LL& here = path.converted_->at(i); - const Point2LL& next = path.converted_->at((i + 1) % path.converted_->size()); - const coord_t segment_size = vSize(next - here); - segments_sizes[i] = segment_size; - total_length += segment_size; + main_criterion.criterion = std::make_shared(points, target_pos); } - } - - auto scoreFromDistance = [](const Point2LL& here, const Point2LL& remote_pos) -> double - { - // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered - // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 - constexpr double distance_divider = 20.0; - - // Use actual (non-squared) distance to ensure a proper scoring distribution - const double distance = vSizeMM(here - remote_pos); - - // Use reciprocal function to normalize distance score decreasingly - return 1.0 / (1.0 + (distance / distance_divider)); - }; - - // ########## Step 4: now calculate the total score of each vertex and select the best one - std::optional best_i; - Score best_score; - for (const auto& [i, here] : *path.converted_ | ranges::views::drop_last(1) | ranges::views::enumerate) - { - Score vertex_score; - - if (calculate_forced_pos_score) + else if ( + path.seam_config_.type_ == EZSeamType::SHARPEST_CORNER + && (path.seam_config_.corner_pref_ != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE && path.seam_config_.corner_pref_ != EZSeamCornerPrefType::PLUGIN)) { - vertex_score += CriterionScore{ .score = scoreFromDistance(here, forced_start_pos), .weight = weight_main_criterion }; + main_criterion.criterion = std::make_shared(points, path.seam_config_.corner_pref_); } - - if (calculate_distance_score) + else if (path.seam_config_.type_ == EZSeamType::RANDOM) { - vertex_score += CriterionScore{ .score = scoreFromDistance(here, target_pos), .weight = weight_distance }; + main_criterion.criterion = std::make_shared(); } + } - if (calculate_corner_score) - { - double corner_angle = cornerAngle(path, i, segments_sizes, total_length); - // angles < 0 are concave (left turning) - // angles > 0 are convex (right turning) - - CriterionScore score_corner{ .weight = weight_main_criterion }; - - switch (path.seam_config_.corner_pref_) - { - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - // Give advantage to concave corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(1.0, -1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - // Give advantage to convex corners. More advantage for sharper corners. - score_corner.score = cura::inverse_lerp(-1.0, 1.0, corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - // Still give sharper corners more advantage. - score_corner.score = std::abs(corner_angle); - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: - // Give sharper corners some advantage, but sharper concave corners even more. - if (corner_angle < 0) - { - score_corner.score = -corner_angle; - } - else - { - score_corner.score = corner_angle / 2.0; - } - break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: - case EZSeamCornerPrefType::PLUGIN: - break; - } + if (main_criterion.criterion) + { + main_criteria_pass.criteria.push_back(main_criterion); + } + else + { + spdlog::warn("Missing main criterion calculator"); + } - vertex_score += score_corner; - } + // Second criterion with heigher weight to avoid overhanging areas + if (! overhang_areas_.empty()) + { + BestElementFinder::WeighedCriterion overhang_criterion; + overhang_criterion.weight = 2.0; + overhang_criterion.criterion = std::make_shared(points, overhang_areas_); + main_criteria_pass.criteria.push_back(overhang_criterion); + } - if (calculate_random_score) - { - vertex_score += CriterionScore{ .score = cura::randf(), .weight = weight_main_criterion }; - } + best_candidate_finder.appendCriteriaPass(main_criteria_pass); - if (calculate_consistency_score) - { - CriterionScore score_consistency_x{ .weight = weight_consistency_x }; - score_consistency_x.score = cura::inverse_lerp(path_bounding_box.min_.X, path_bounding_box.max_.X, here.X); - vertex_score += score_consistency_x; + // ########## Step 2: add a second pass for criteria with very similar scores (e.g. corner on a cylinder) + BestElementFinder::CriteriaPass fallback_criteria_pass; - CriterionScore score_consistency_y{ .weight = weight_consistency_y }; - score_consistency_y.score = cura::inverse_lerp(path_bounding_box.min_.Y, path_bounding_box.max_.Y, here.Y); - vertex_score += score_consistency_y; - } + BestElementFinder::WeighedCriterion fallback_criterion; + // fallback strategy is to take points on the back-most position, relative to the path + const AABB path_bounding_box(points); + Point2LL fallback_position = path_bounding_box.max_; + fallback_position.X -= (path_bounding_box.max_.X - path_bounding_box.min_.X) / 2.0; + fallback_criterion.criterion = std::make_shared(points, fallback_position); - if (calculate_overhang_score) - { - CriterionScore score_exclude_overhang{ .weight = weight_exclude_overhang }; - score_exclude_overhang.score = overhang_areas_.inside(here, true) ? 0.0 : 1.0; - vertex_score += score_exclude_overhang; - } + fallback_criteria_pass.criteria.push_back(fallback_criterion); + best_candidate_finder.appendCriteriaPass(fallback_criteria_pass); - if (! best_i.has_value() || vertex_score > best_score) - { - best_i = i; - best_score = vertex_score; - } - } + // ########## Step 3: apply the criteria to find the vertex with the best global score + std::optional best_i = best_candidate_finder.findBestElement(points.size()); if (! disallowed_area_for_seams.empty()) { @@ -891,84 +803,6 @@ class PathOrderOptimizer return best_i.value_or(0); } - /*! - * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point - * is computed by travelling on the path and stopping when the distance has been reached, For example: - * |------|---------|------|--------------*---| - * H A B C N D - * In this case, H is the start point of the path and ABCD are the actual following points of the path. - * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. - * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path - * at a given physical distance. - * \param path The vertex data of a path - * \param here The starting point index - * \param distance The distance we want to travel on the path, which may be positive to go forward - * or negative to go backward - * \param segments_sizes The pre-computed sizes of the segments - * \return The position of the path a the given distance from the reference point - */ - static Point2LL findNeighbourPoint(const OrderablePath& path, int here, coord_t distance, const std::vector& segments_sizes) - { - const int direction = distance > 0 ? 1 : -1; - const int size_delta = distance > 0 ? -1 : 0; - distance = std::abs(distance); - - // Travel on the path until we reach the distance - int actual_delta = 0; - coord_t travelled_distance = 0; - coord_t segment_size = 0; - while (travelled_distance < distance) - { - actual_delta += direction; - segment_size = segments_sizes[(here + actual_delta + size_delta + path.converted_->size()) % path.converted_->size()]; - travelled_distance += segment_size; - } - - const Point2LL& next_pos = path.converted_->at((here + actual_delta + path.converted_->size()) % path.converted_->size()); - - if (travelled_distance > distance) [[likely]] - { - // We have overtaken the required distance, go backward on the last segment - int prev = (here + actual_delta - direction + path.converted_->size()) % path.converted_->size(); - const Point2LL& prev_pos = path.converted_->at(prev); - - const Point2LL vector = next_pos - prev_pos; - const Point2LL unit_vector = (vector * 1000) / segment_size; - const Point2LL vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); - return prev_pos + vector_delta / 1000; - } - else - { - // Luckily, the required distance stops exactly on an existing point - return next_pos; - } - } - - /*! - * Some models have very sharp corners, but also have a high resolution. If a sharp corner - * consists of many points each point individual might have a shallow corner, but the - * collective angle of all nearby points is greater. To counter this the cornerAngle is - * calculated from two points within angle_query_distance of the query point, no matter - * what segment this leads us to - * \param path The vertex data of a path - * \param i index of the query point - * \param segments_sizes The pre-computed sizes of the segments - * \param total_length The path total length - * \param angle_query_distance query range (default to 1mm) - * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] - */ - static double cornerAngle(const OrderablePath& path, int i, const std::vector& segments_sizes, coord_t total_length, const coord_t angle_query_distance = 1000) - { - const coord_t bounded_distance = std::min(angle_query_distance, total_length / 2); - const Point2LL& here = path.converted_->at(i); - const Point2LL next = findNeighbourPoint(path, i, bounded_distance, segments_sizes); - const Point2LL previous = findNeighbourPoint(path, i, -bounded_distance, segments_sizes); - - double angle = LinearAlg2D::getAngleLeft(previous, here, next) - std::numbers::pi; - - return angle / std::numbers::pi; - } - /*! * Calculate the direct Euclidean distance to move from one point to * another. diff --git a/include/utils/CriterionScore.h b/include/utils/CriterionScore.h deleted file mode 100644 index e4313d988e..0000000000 --- a/include/utils/CriterionScore.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2024 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef UTILS_CRITERION_SCORE_H -#define UTILS_CRITERION_SCORE_H - -namespace cura -{ - -/*! - * This structure represents a score given by a single crtierion when calculating a global score to select a best - * candidate among a list with multiple criteria. - */ -struct CriterionScore -{ - /*! - * The score given by the criterion. To ensure a proper selection, this value must be contained in [0.0, 1.0] and - * the different given scores must be evenly distributed in this range. - */ - double score{ 0.0 }; - - /*! - * The weight to be given when taking this score into the global score. A score that contributes "normally" to the - * global score should have a weight of 1.0, and others should be adjusted around this value, to give them more or - * less influence. - */ - double weight{ 0.0 }; -}; - -} // namespace cura -#endif // UTILS_CRITERION_SCORE_H diff --git a/include/utils/Score.h b/include/utils/Score.h deleted file mode 100644 index f8108498ee..0000000000 --- a/include/utils/Score.h +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2024 Ultimaker B.V. -// CuraEngine is released under the terms of the AGPLv3 or higher. - -#ifndef UTILS_SCORE_H -#define UTILS_SCORE_H - -#include - -#include "CriterionScore.h" - -namespace cura -{ - -/*! - * This class represents a score to be calculated over different criteria, to select the best candidate among a list. - */ -class Score -{ -private: - double value_{ 0.0 }; - -public: - /*! - * Get the actual score value, should be used for debug purposes only - */ - double getValue() const - { - return value_; - } - - /*! - * Add the calculated score of an inidividual criterion to the global score, taking care of its weight - */ - void operator+=(const CriterionScore& criterion_score) - { - value_ += criterion_score.score * criterion_score.weight; - } - - /*! - * Comparison operators to allow selecting the best global score - */ - auto operator<=>(const Score&) const = default; -}; - -} // namespace cura - -namespace fmt -{ - -template<> -struct formatter : formatter -{ - auto format(const cura::Score& score, format_context& ctx) - { - return fmt::format_to(ctx.out(), "Score{{{}}}", score.getValue()); - } -}; - -} // namespace fmt - -#endif // UTILS_SCORE_H diff --git a/include/utils/scoring/BestElementFinder.h b/include/utils/scoring/BestElementFinder.h new file mode 100644 index 0000000000..25cc4fae07 --- /dev/null +++ b/include/utils/scoring/BestElementFinder.h @@ -0,0 +1,81 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORING_BESTCANDIDATEFINDER_H +#define UTILS_SCORING_BESTCANDIDATEFINDER_H + +#include +#include +#include +#include + +namespace cura +{ +class ScoringCriterion; + +/*! + * This class implements an algorithm to find an element amongst a list, regarding one or multiple scoring criteria. The + * criteria are implemented by subclassing the CriterionScoring class. It is also possible to setup multiple passes of + * criteria. Thus, if the first pass gives a few best results that are too close to each other, a new pass is processed + * with different criteria, and so on until we have a single outsider or no more criteria. + */ +class BestElementFinder +{ +private: + /*! + * Contains the index of an element in the source list, and its calculated score + */ + struct Candidate + { + size_t candidate_index; + double score{ 0.0 }; + }; + +public: + /*! + * Contains a criterion to be processed to calculate the score of an element, and the weight is has on the global + * score calculation. + */ + struct WeighedCriterion + { + std::shared_ptr criterion; + + /*! + * The weight to be given when taking this criterion into the global score. A score that contributes "normally" + * to the global score should have a weight of 1.0, and others should be adjusted around this value, to give + * them more or less influence. + */ + double weight{ 1.0 }; + }; + + /*! + * Contains multiple criteria to be processed on each element in a single pass + */ + struct CriteriaPass + { + std::vector criteria; + + /*! + * Once we have calculated the global scores of each element for this pass, we calculate the score difference + * between the best candidate and the following ones. If the following ones have a score close enough to the + * best, within this threshold, they will also be considered outsiders and will be run for the next pass. + */ + double outsider_delta_threshold{ 0.0 }; + }; + +private: + std::vector criteria_; + +public: + explicit BestElementFinder() = default; + + void appendCriteriaPass(const CriteriaPass& pass) + { + criteria_.push_back(pass); + } + + std::optional findBestElement(const size_t candidates_count); +}; + +} // namespace cura +#endif // UTILS_SCORING_BESTCANDIDATEFINDER_H diff --git a/include/utils/scoring/CornerScoringCriterion.h b/include/utils/scoring/CornerScoringCriterion.h new file mode 100644 index 0000000000..210416f56b --- /dev/null +++ b/include/utils/scoring/CornerScoringCriterion.h @@ -0,0 +1,67 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef CORNERSCORINGCRITERION_H +#define CORNERSCORINGCRITERION_H + +#include + +#include "geometry/Point2LL.h" +#include "settings/EnumSettings.h" +#include "utils/Coord_t.h" +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; + +/*! + * Criterion that will give a score according to whether the point is creating a corner or lies on a flat line. + * Depending on the given preference, concave or convex corners may get a higher score. + */ +class CornerScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const EZSeamCornerPrefType corner_preference_; + std::vector segments_sizes_; + coord_t total_length_{ 0 }; + +public: + explicit CornerScoringCriterion(const PointsSet& points, const EZSeamCornerPrefType corner_preference); + + virtual double computeScore(const size_t candidate_index) const override; + +private: + /*! + * Some models have very sharp corners, but also have a high resolution. If a sharp corner + * consists of many points each point individual might have a shallow corner, but the + * collective angle of all nearby points is greater. To counter this the cornerAngle is + * calculated from two points within angle_query_distance of the query point, no matter + * what segment this leads us to + * \param vertex_index index of the query point + * \param angle_query_distance query range (default to 1mm) + * \return angle between the reference point and the two sibling points, weighed to [-1.0 ; 1.0] + */ + double cornerAngle(size_t vertex_index, const coord_t angle_query_distance = 1000) const; + + /*! + * Finds a neighbour point on the path, located before or after the given reference point. The neighbour point + * is computed by travelling on the path and stopping when the distance has been reached, For example: + * |------|---------|------|--------------*---| + * H A B C N D + * In this case, H is the start point of the path and ABCD are the actual following points of the path. + * The neighbour point N is found by reaching point D then going a bit backward on the previous segment. + * This approach gets rid of the mesh actual resolution and gives a neighbour point that is on the path + * at a given physical distance. + * \param vertex_index The starting point index + * \param distance The distance we want to travel on the path, which may be positive to go forward + * or negative to go backward + * \return The position of the path a the given distance from the reference point + */ + Point2LL findNeighbourPoint(size_t vertex_index, coord_t distance) const; +}; + +} // namespace cura + +#endif // CORNERSCORINGCRITERION_H diff --git a/include/utils/scoring/DistanceScoringCriterion.h b/include/utils/scoring/DistanceScoringCriterion.h new file mode 100644 index 0000000000..8c520fb1ed --- /dev/null +++ b/include/utils/scoring/DistanceScoringCriterion.h @@ -0,0 +1,32 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef DISTANCESCORINGCRITERION_H +#define DISTANCESCORINGCRITERION_H + +#include "geometry/Point2LL.h" +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; + +/*! + * Criterion that will give a score according to the distance from the point to a target point. Closer points will get + * a higher score. + */ +class DistanceScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const Point2LL& target_pos_; + +public: + explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // DISTANCESCORINGCRITERION_H diff --git a/include/utils/scoring/ExclusionAreaScoringCriterion.h b/include/utils/scoring/ExclusionAreaScoringCriterion.h new file mode 100644 index 0000000000..84fd0ab7a0 --- /dev/null +++ b/include/utils/scoring/ExclusionAreaScoringCriterion.h @@ -0,0 +1,34 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef EXCLUSIONAREASCORINGCRITERION_H +#define EXCLUSIONAREASCORINGCRITERION_H + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ +class PointsSet; +class Shape; + +/*! + * Criterion that will give a score according to whether the point is located inside or outside of an exclusion area. + * This is currently a binary test and the score will be either 0 or 1. + */ +class ExclusionAreaScoringCriterion : public ScoringCriterion +{ +private: + const PointsSet& points_; + const Shape& exclusion_area_; + +public: + explicit ExclusionAreaScoringCriterion(const PointsSet& points, const Shape& exclusion_area); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // EXCLUSIONAREASCORINGCRITERION_H diff --git a/include/utils/scoring/RandomScoringCriterion.h b/include/utils/scoring/RandomScoringCriterion.h new file mode 100644 index 0000000000..5a577be894 --- /dev/null +++ b/include/utils/scoring/RandomScoringCriterion.h @@ -0,0 +1,27 @@ +// Copyright (c) 2021 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef RANDOMSCORINGCRITERION_H +#define RANDOMSCORINGCRITERION_H + +#include + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ + +/*! + * Criterion that will give a random score whatever the element is. + */ +class RandomScoringCriterion : public ScoringCriterion +{ +public: + explicit RandomScoringCriterion(); + + virtual double computeScore(const size_t candidate_index) const override; +}; + +} // namespace cura + +#endif // RANDOMSCORINGCRITERION_H diff --git a/include/utils/scoring/ScoringCriterion.h b/include/utils/scoring/ScoringCriterion.h new file mode 100644 index 0000000000..41280b0a82 --- /dev/null +++ b/include/utils/scoring/ScoringCriterion.h @@ -0,0 +1,34 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#ifndef UTILS_SCORING_SCORINGCRITERION_H +#define UTILS_SCORING_SCORINGCRITERION_H + +#include + +namespace cura +{ + +/*! + * Base class for implementing a selection criterion when calculating a multi-criteria score to select the best element + * amongst a list. + */ +class ScoringCriterion +{ +public: + ScoringCriterion() = default; + + virtual ~ScoringCriterion() = default; + + /*! + * \brief Computes the score of an element regarding this criterion. To ensure a proper selection, this value must + * be contained in [0.0, 1.0] and the different given scores must be evenly distributed in this range. + * \param candidate_index The index of the candidate of the original list + * \return The raw score of the element regarding this criterion + */ + virtual double computeScore(const size_t candidate_index) const = 0; +}; + +} // namespace cura + +#endif // UTILS_SCORING_SCORINGCRITERION_H diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp new file mode 100644 index 0000000000..20330a108b --- /dev/null +++ b/src/utils/scoring/BestElementFinder.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2024 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/BestElementFinder.h" + +#include "utils/scoring/ScoringCriterion.h" + +namespace cura +{ + +std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) +{ + if (candidates_count == 0) + { + return std::nullopt; + } + + // Start by initializing the candidates list in natural order + std::vector candidates(candidates_count); + for (size_t i = 0; i < candidates_count; ++i) + { + candidates[i].candidate_index = i; + } + + const auto begin = candidates.begin(); + auto end = candidates.end(); + + // Now run the criteria passes until we have a single outsider or no more cirteria + for (const CriteriaPass& criteria_pass : criteria_) + { + // For each element, reset score, process each criterion and apply wweights to get the global score + for (auto iterator = begin; iterator != end; ++iterator) + { + iterator->score = 0.0; + + for (const auto& weighed_criterion : criteria_pass.criteria) + { + iterator->score += weighed_criterion.criterion->computeScore(iterator->candidate_index) * weighed_criterion.weight; + } + } + + // Now sort the candiates (sub)list to get the best candidates first + std::sort( + begin, + end, + [](const Candidate& candidate1, const Candidate& candidate2) + { + return candidate1.score > candidate2.score; + }); + + // Check whether the following candidates have a score similar enough to the actual best one, and skip others + if (criteria_pass.outsider_delta_threshold > 0.0) + { + const double highest_score = candidates.front().score; + auto iterator = std::next(begin); + while (iterator != end && (highest_score - iterator->score) <= criteria_pass.outsider_delta_threshold) + { + ++iterator; + } + + end = iterator; + } + + if (std::distance(begin, end) <= 1) + { + // We have a single outsider, don't go further + break; + } + } + + return begin->candidate_index; +} + +} // namespace cura diff --git a/src/utils/scoring/CornerScoringCriterion.cpp b/src/utils/scoring/CornerScoringCriterion.cpp new file mode 100644 index 0000000000..05451e6e6d --- /dev/null +++ b/src/utils/scoring/CornerScoringCriterion.cpp @@ -0,0 +1,120 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/CornerScoringCriterion.h" + +#include "geometry/PointsSet.h" +#include "utils/linearAlg2D.h" +#include "utils/math.h" + + +namespace cura +{ + +CornerScoringCriterion::CornerScoringCriterion(const PointsSet& points, const EZSeamCornerPrefType corner_preference) + : points_(points) + , corner_preference_(corner_preference) + , segments_sizes_(points.size()) +{ + // Pre-calculate the segments lengths because we are going to need them multiple times + for (size_t i = 0; i < points.size(); ++i) + { + const Point2LL& here = points_.at(i); + const Point2LL& next = points_.at((i + 1) % points_.size()); + const coord_t segment_size = vSize(next - here); + segments_sizes_[i] = segment_size; + total_length_ += segment_size; + } +} + +double CornerScoringCriterion::computeScore(const size_t candidate_index) const +{ + double corner_angle = cornerAngle(candidate_index); + // angles < 0 are concave (left turning) + // angles > 0 are convex (right turning) + + double score = 0.0; + + switch (corner_preference_) + { + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: + // Give advantage to concave corners. More advantage for sharper corners. + score = cura::inverse_lerp(1.0, -1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: + // Give advantage to convex corners. More advantage for sharper corners. + score = cura::inverse_lerp(-1.0, 1.0, corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: + // Still give sharper corners more advantage. + score = std::abs(corner_angle); + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: + // Give sharper corners some advantage, but sharper concave corners even more. + if (corner_angle < 0) + { + score = -corner_angle; + } + else + { + score = corner_angle / 2.0; + } + break; + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: + case EZSeamCornerPrefType::PLUGIN: + break; + } + + return score; +} + +double CornerScoringCriterion::cornerAngle(size_t vertex_index, const coord_t angle_query_distance) const +{ + const coord_t bounded_distance = std::min(angle_query_distance, total_length_ / 2); + const Point2LL& here = points_.at(vertex_index); + const Point2LL next = findNeighbourPoint(vertex_index, bounded_distance); + const Point2LL previous = findNeighbourPoint(vertex_index, -bounded_distance); + + double angle = LinearAlg2D::getAngleLeft(previous, here, next) - std::numbers::pi; + + return angle / std::numbers::pi; +} + +Point2LL CornerScoringCriterion::findNeighbourPoint(size_t vertex_index, coord_t distance) const +{ + const int direction = distance > 0 ? 1 : -1; + const int size_delta = distance > 0 ? -1 : 0; + distance = std::abs(distance); + + // Travel on the path until we reach the distance + int actual_delta = 0; + coord_t travelled_distance = 0; + coord_t segment_size = 0; + while (travelled_distance < distance) + { + actual_delta += direction; + segment_size = segments_sizes_[(vertex_index + actual_delta + size_delta + points_.size()) % points_.size()]; + travelled_distance += segment_size; + } + + const Point2LL& next_pos = points_.at((vertex_index + actual_delta + points_.size()) % points_.size()); + + if (travelled_distance > distance) [[likely]] + { + // We have overtaken the required distance, go backward on the last segment + int prev = (vertex_index + actual_delta - direction + points_.size()) % points_.size(); + const Point2LL& prev_pos = points_.at(prev); + + const Point2LL vector = next_pos - prev_pos; + const Point2LL unit_vector = (vector * 1000) / segment_size; + const Point2LL vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); + return prev_pos + vector_delta / 1000; + } + else + { + // Luckily, the required distance stops exactly on an existing point + return next_pos; + } +} + +} // namespace cura diff --git a/src/utils/scoring/DistanceScoringCriterion.cpp b/src/utils/scoring/DistanceScoringCriterion.cpp new file mode 100644 index 0000000000..8b901a71fc --- /dev/null +++ b/src/utils/scoring/DistanceScoringCriterion.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/DistanceScoringCriterion.h" + +#include "geometry/PointsSet.h" + + +namespace cura +{ + +DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos) + : points_(points) + , target_pos_(target_pos) +{ +} + +double DistanceScoringCriterion::computeScore(const size_t candidate_index) const +{ + const Point2LL& candidate_position = points_.at(candidate_index); + + // Fixed divider for shortest distances computation. The divider should be set so that the minimum encountered + // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 + constexpr double distance_divider = 20.0; + + // Use actual (non-squared) distance to ensure a proper scoring distribution + const double distance = vSizeMM(candidate_position - target_pos_); + + // Use reciprocal function to normalize distance score decreasingly + return 1.0 / (1.0 + (distance / distance_divider)); +} + +} // namespace cura diff --git a/src/utils/scoring/ExclusionAreaScoringCriterion.cpp b/src/utils/scoring/ExclusionAreaScoringCriterion.cpp new file mode 100644 index 0000000000..c4bdd097ed --- /dev/null +++ b/src/utils/scoring/ExclusionAreaScoringCriterion.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/ExclusionAreaScoringCriterion.h" + +#include "geometry/Shape.h" + + +namespace cura +{ + +ExclusionAreaScoringCriterion::ExclusionAreaScoringCriterion(const PointsSet& points, const Shape& exclusion_area) + : points_(points) + , exclusion_area_(exclusion_area) +{ +} + +double ExclusionAreaScoringCriterion::computeScore(const size_t candidate_index) const +{ + const Point2LL& candidate_position = points_.at(candidate_index); + return exclusion_area_.inside(candidate_position, true) ? 0.0 : 1.0; +} + +} // namespace cura diff --git a/src/utils/scoring/RandomScoringCriterion.cpp b/src/utils/scoring/RandomScoringCriterion.cpp new file mode 100644 index 0000000000..de6c81280c --- /dev/null +++ b/src/utils/scoring/RandomScoringCriterion.cpp @@ -0,0 +1,20 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "utils/scoring/RandomScoringCriterion.h" + +#include "utils/math.h" + +namespace cura +{ + +RandomScoringCriterion::RandomScoringCriterion() +{ +} + +double RandomScoringCriterion::computeScore(const size_t /*candidate_index*/) const +{ + return cura::randf(); +} + +} // namespace cura From 2dcbd3037747b522cddfbd5a7f0f7ca55d2c66aa Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 14:02:10 +0100 Subject: [PATCH 22/41] Add benchmark to allow testing different scoring strategies CURA-12218 --- benchmark/scoring_benchmark.h | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 benchmark/scoring_benchmark.h diff --git a/benchmark/scoring_benchmark.h b/benchmark/scoring_benchmark.h new file mode 100644 index 0000000000..b526c94302 --- /dev/null +++ b/benchmark/scoring_benchmark.h @@ -0,0 +1,73 @@ +// Copyright (c) 2024 UltiMaker +// CuraEngine is released under the terms of the AGPLv3 or higher + +#ifndef CURAENGINE_SCORING_BENCHMARK_H +#define CURAENGINE_SCORING_BENCHMARK_H + +#include + +#include "geometry/PointsSet.h" +#include "utils/scoring/BestElementFinder.h" +#include "utils/scoring/CornerScoringCriterion.h" +#include "utils/scoring/DistanceScoringCriterion.h" + +namespace cura +{ +class ScoringTestFixture : public benchmark::Fixture +{ +public: + PointsSet points; + + void SetUp(const ::benchmark::State& state) override + { + points.resize(state.range(0)); + constexpr double radius = 5000.0; + + for (size_t i = 0; i < points.size(); ++i) + { + const double angle = (i * std::numbers::pi * 2.0) / points.size(); + points[i] = Point2LL(std::cos(angle) * radius, std::sin(angle) * radius); + } + } + + void TearDown(const ::benchmark::State& state) override + { + } +}; + +BENCHMARK_DEFINE_F(ScoringTestFixture, ScoringTest_WorstCase)(benchmark::State& st) +{ + for (auto _ : st) + { + BestElementFinder best_element_finder; + + // Pass 1 : find corners + BestElementFinder::CriteriaPass main_criteria_pass; + main_criteria_pass.outsider_delta_threshold = 0.05; + + BestElementFinder::WeighedCriterion main_criterion; + main_criterion.criterion = std::make_shared(points, EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED); + main_criteria_pass.criteria.push_back(main_criterion); + + best_element_finder.appendCriteriaPass(main_criteria_pass); + + // Pass 2 : fallback to distance calculation + BestElementFinder::CriteriaPass fallback_criteria_pass; + BestElementFinder::WeighedCriterion fallback_criterion; + fallback_criterion.criterion = std::make_shared(points, Point2LL(1000, 0)); + + fallback_criteria_pass.criteria.push_back(fallback_criterion); + best_element_finder.appendCriteriaPass(fallback_criteria_pass); + + best_element_finder.findBestElement(points.size()); + } +} + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(10000)->Unit(benchmark::kMillisecond); + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(1000)->Unit(benchmark::kMillisecond); + +BENCHMARK_REGISTER_F(ScoringTestFixture, ScoringTest_WorstCase)->Arg(10)->Unit(benchmark::kMillisecond); + +} // namespace cura +#endif // CURAENGINE_SCORING_BENCHMARK_H From 6ccc9a04dd5f79a5a90f4464705eaa91f14da398 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 15:28:22 +0100 Subject: [PATCH 23/41] Optimize best element finding CURA-12218 Instead of sorting the whole scores list, which is very costly, pre- calculate the best score and only keep elements that are close enough to it. --- src/utils/scoring/BestElementFinder.cpp | 48 ++++++++++--------------- 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp index 20330a108b..c0e2f8da6e 100644 --- a/src/utils/scoring/BestElementFinder.cpp +++ b/src/utils/scoring/BestElementFinder.cpp @@ -3,6 +3,8 @@ #include "utils/scoring/BestElementFinder.h" +#include + #include "utils/scoring/ScoringCriterion.h" namespace cura @@ -10,25 +12,21 @@ namespace cura std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) { - if (candidates_count == 0) - { - return std::nullopt; - } - // Start by initializing the candidates list in natural order - std::vector candidates(candidates_count); + std::vector best_candidates(candidates_count); for (size_t i = 0; i < candidates_count; ++i) { - candidates[i].candidate_index = i; + best_candidates[i].candidate_index = i; } - const auto begin = candidates.begin(); - auto end = candidates.end(); + const auto begin = best_candidates.begin(); + auto end = best_candidates.end(); // Now run the criteria passes until we have a single outsider or no more cirteria for (const CriteriaPass& criteria_pass : criteria_) { - // For each element, reset score, process each criterion and apply wweights to get the global score + // For each element, reset score, process each criterion and apply weights to get the global score + double best_score = 0.0; for (auto iterator = begin; iterator != end; ++iterator) { iterator->score = 0.0; @@ -37,38 +35,28 @@ std::optional cura::BestElementFinder::findBestElement(const size_t cand { iterator->score += weighed_criterion.criterion->computeScore(iterator->candidate_index) * weighed_criterion.weight; } + + best_score = std::max(best_score, iterator->score); } - // Now sort the candiates (sub)list to get the best candidates first - std::sort( + // Skip candidates that have a score too far from the actual best one + const double delta_threshold = criteria_pass.outsider_delta_threshold + std::numeric_limits::epsilon(); + end = std::remove_if( begin, end, - [](const Candidate& candidate1, const Candidate& candidate2) + [&best_score, &delta_threshold](const Candidate& candidate) { - return candidate1.score > candidate2.score; + return best_score - candidate.score > delta_threshold; }); - // Check whether the following candidates have a score similar enough to the actual best one, and skip others - if (criteria_pass.outsider_delta_threshold > 0.0) - { - const double highest_score = candidates.front().score; - auto iterator = std::next(begin); - while (iterator != end && (highest_score - iterator->score) <= criteria_pass.outsider_delta_threshold) - { - ++iterator; - } - - end = iterator; - } - - if (std::distance(begin, end) <= 1) + if (std::distance(begin, end) == 1) { // We have a single outsider, don't go further - break; + return begin->candidate_index; } } - return begin->candidate_index; + return begin != end ? std::make_optional(begin->candidate_index) : std::nullopt; } } // namespace cura From 12491b406a5687fb961b556a8fdf61f1a14a261b Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 29 Oct 2024 15:48:41 +0100 Subject: [PATCH 24/41] Fix Mac build CURA-12176 --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index a8642cf95c..65eae9c2c2 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -1730,7 +1730,7 @@ void FffGcodeWriter::addMeshLayerToGCode_meshSurfaceMode(const SliceMeshStorage& constexpr Ratio flow_ratio = 1.0; constexpr bool always_retract = false; constexpr bool reverse_order = false; - constexpr std::optional start_near_location = std::nullopt; + const std::optional start_near_location = std::nullopt; constexpr bool scarf_seam = true; constexpr bool smooth_speed = true; From 71ac23e50ea58b7ec08a5823b0bbd5a03dec1c25 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 09:58:58 +0100 Subject: [PATCH 25/41] Fix some cases of unaligned seams (e.g. cubes) CURA-12218 In order to restore the previous seam consistency even better, we now use two different fallback passes, the first one working on Y only, and the second one on X only. This way it really mimics the former behavior and always aligns the seam even on very symmetric shapes. --- include/PathOrderOptimizer.h | 21 ++++++++++--------- include/utils/scoring/BestElementFinder.h | 10 +++++++-- .../utils/scoring/DistanceScoringCriterion.h | 11 +++++++++- src/utils/scoring/BestElementFinder.cpp | 11 ++++++++++ .../scoring/DistanceScoringCriterion.cpp | 19 ++++++++++++++--- 5 files changed, 56 insertions(+), 16 deletions(-) diff --git a/include/PathOrderOptimizer.h b/include/PathOrderOptimizer.h index 252890a635..ae01be4b25 100644 --- a/include/PathOrderOptimizer.h +++ b/include/PathOrderOptimizer.h @@ -780,18 +780,19 @@ class PathOrderOptimizer best_candidate_finder.appendCriteriaPass(main_criteria_pass); - // ########## Step 2: add a second pass for criteria with very similar scores (e.g. corner on a cylinder) - BestElementFinder::CriteriaPass fallback_criteria_pass; - - BestElementFinder::WeighedCriterion fallback_criterion; - // fallback strategy is to take points on the back-most position, relative to the path + // ########## Step 2: add fallback passes for criteria with very similar scores (e.g. corner on a cylinder) const AABB path_bounding_box(points); - Point2LL fallback_position = path_bounding_box.max_; - fallback_position.X -= (path_bounding_box.max_.X - path_bounding_box.min_.X) / 2.0; - fallback_criterion.criterion = std::make_shared(points, fallback_position); - fallback_criteria_pass.criteria.push_back(fallback_criterion); - best_candidate_finder.appendCriteriaPass(fallback_criteria_pass); + { // First fallback strategy is to take points on the back-most position + auto fallback_criterion = std::make_shared(points, path_bounding_box.max_, DistanceScoringCriterion::DistanceType::YOnly); + constexpr double outsider_delta_threshold = 0.01; + best_candidate_finder.appendSingleCriterionPass(fallback_criterion, outsider_delta_threshold); + } + + { // Second fallback strategy, in case we still have multiple points that are aligned on Y (e.g. cube), take the right-most point + auto fallback_criterion = std::make_shared(points, path_bounding_box.max_, DistanceScoringCriterion::DistanceType::XOnly); + best_candidate_finder.appendSingleCriterionPass(fallback_criterion); + } // ########## Step 3: apply the criteria to find the vertex with the best global score std::optional best_i = best_candidate_finder.findBestElement(points.size()); diff --git a/include/utils/scoring/BestElementFinder.h b/include/utils/scoring/BestElementFinder.h index 25cc4fae07..e2ede975d5 100644 --- a/include/utils/scoring/BestElementFinder.h +++ b/include/utils/scoring/BestElementFinder.h @@ -16,8 +16,9 @@ class ScoringCriterion; /*! * This class implements an algorithm to find an element amongst a list, regarding one or multiple scoring criteria. The * criteria are implemented by subclassing the CriterionScoring class. It is also possible to setup multiple passes of - * criteria. Thus, if the first pass gives a few best results that are too close to each other, a new pass is processed - * with different criteria, and so on until we have a single outsider or no more criteria. + * criteria. Thus, if the first pass gives a few best candidates that are too close to each other, we keep only the best + * candidates and process a second pass with different criteria, and so on until we have a single outsider, or no more + * criteria. */ class BestElementFinder { @@ -74,6 +75,11 @@ class BestElementFinder criteria_.push_back(pass); } + /*! + * Convenience method to add a pass with a single criterion + */ + void appendSingleCriterionPass(std::shared_ptr criterion, const double outsider_delta_threshold = 0.0); + std::optional findBestElement(const size_t candidates_count); }; diff --git a/include/utils/scoring/DistanceScoringCriterion.h b/include/utils/scoring/DistanceScoringCriterion.h index 8c520fb1ed..3b6d8785d3 100644 --- a/include/utils/scoring/DistanceScoringCriterion.h +++ b/include/utils/scoring/DistanceScoringCriterion.h @@ -17,12 +17,21 @@ class PointsSet; */ class DistanceScoringCriterion : public ScoringCriterion { +public: + enum class DistanceType + { + Euclidian, // Classic euclidian distance between the points + XOnly, // Only difference on X coordinate + YOnly, // Only difference on Y coordinate + }; + private: const PointsSet& points_; const Point2LL& target_pos_; + const DistanceType distance_type_; public: - explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos); + explicit DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type = DistanceType::Euclidian); virtual double computeScore(const size_t candidate_index) const override; }; diff --git a/src/utils/scoring/BestElementFinder.cpp b/src/utils/scoring/BestElementFinder.cpp index c0e2f8da6e..ecd63e89cb 100644 --- a/src/utils/scoring/BestElementFinder.cpp +++ b/src/utils/scoring/BestElementFinder.cpp @@ -10,6 +10,17 @@ namespace cura { +void BestElementFinder::appendSingleCriterionPass(std::shared_ptr criterion, const double outsider_delta_threshold) +{ + WeighedCriterion weighed_criterion; + weighed_criterion.criterion = criterion; + + CriteriaPass criteria_pass; + criteria_pass.outsider_delta_threshold = outsider_delta_threshold; + criteria_pass.criteria.push_back(weighed_criterion); + appendCriteriaPass(criteria_pass); +} + std::optional cura::BestElementFinder::findBestElement(const size_t candidates_count) { // Start by initializing the candidates list in natural order diff --git a/src/utils/scoring/DistanceScoringCriterion.cpp b/src/utils/scoring/DistanceScoringCriterion.cpp index 8b901a71fc..0c68e875b8 100644 --- a/src/utils/scoring/DistanceScoringCriterion.cpp +++ b/src/utils/scoring/DistanceScoringCriterion.cpp @@ -9,9 +9,10 @@ namespace cura { -DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos) +DistanceScoringCriterion::DistanceScoringCriterion(const PointsSet& points, const Point2LL& target_pos, DistanceType distance_type) : points_(points) , target_pos_(target_pos) + , distance_type_(distance_type) { } @@ -23,8 +24,20 @@ double DistanceScoringCriterion::computeScore(const size_t candidate_index) cons // distance gives a score very close to 1.0, and a medium-far distance gives a score close to 0.5 constexpr double distance_divider = 20.0; - // Use actual (non-squared) distance to ensure a proper scoring distribution - const double distance = vSizeMM(candidate_position - target_pos_); + double distance = 0.0; + switch (distance_type_) + { + case DistanceType::Euclidian: + // Use actual (non-squared) distance to ensure a proper scoring distribution + distance = vSizeMM(candidate_position - target_pos_); + break; + case DistanceType::XOnly: + distance = INT2MM(std::abs(candidate_position.X - target_pos_.X)); + break; + case DistanceType::YOnly: + distance = INT2MM(std::abs(candidate_position.Y - target_pos_.Y)); + break; + } // Use reciprocal function to normalize distance score decreasingly return 1.0 / (1.0 + (distance / distance_divider)); From 43d5d2da84973e2d9af69f0a94fcbcaeb307a3ce Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 11:46:41 +0100 Subject: [PATCH 26/41] Fix spiralize mode CURA-12245 This was broken when moving to 3D GCodePath points for the scarf seam --- src/LayerPlan.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 2b17229c36..9b07d2d22a 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -2664,14 +2664,14 @@ void LayerPlan::writeGCode(GCodeExport& gcode) const Point2LL p1 = spiral_path.points[point_idx].toPoint2LL(); length += vSizeMM(p0 - p1); p0 = p1; - gcode.setZ(std::round(z_ + layer_thickness_ * length / totalLength)); + const coord_t z_offset = std::round(layer_thickness_ * length / totalLength); const double extrude_speed = speed * spiral_path.speed_back_pressure_factor; writeExtrusionRelativeZ( gcode, spiral_path.points[point_idx], extrude_speed, - path.z_offset, + path.z_offset + z_offset, spiral_path.getExtrusionMM3perMM(), spiral_path.config.type, update_extrusion_offset); From 9a09cb29c051d328b3e50cba52467702de3b7cf6 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Wed, 30 Oct 2024 10:58:45 +0000 Subject: [PATCH 27/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9b07d2d22a..7d7dc3a2d1 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From ad8ee70e7b233683fed1297fc8c3accb4a2ac098 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 14:38:13 +0100 Subject: [PATCH 28/41] Fix crashes when using extra infill support lines CURA-12232 Some references were created to temporary objects, which most of the times work but sometimes crash if the memory was reallocated at the point the object is being used. --- src/FffGcodeWriter.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 2417723014..b310b13c88 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2186,7 +2186,7 @@ void wall_tool_paths2lines(const std::vector>& w { for (const ExtrusionLine& c : b) { - const Polygon& poly = c.toPolygon(); + const Polygon poly = c.toPolygon(); if (c.is_closed_) { result.push_back(poly.toPseudoOpenPolyline()); @@ -2343,12 +2343,12 @@ void addExtraLinesToSupportSurfacesAbove( // invert the supported_area by adding one huge polygon around the outside supported_area.push_back(AABB{ supported_area }.toPolygon()); - const Shape& inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); + const Shape inv_supported_area = supported_area.intersection(part.getOwnInfillArea()); OpenLinesSet unsupported_line_segments = inv_supported_area.intersection(printed_lines_on_layer_above); // This is to work around a rounding issue in the shape library with border points. - const Shape& expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); + const Shape expanded_inv_supported_area = inv_supported_area.offset(-EPSILON); Simplify s{ MM2INT(1000), // max() doesnt work here, so just pick a big number. infill_line_width, @@ -2358,7 +2358,7 @@ void addExtraLinesToSupportSurfacesAbove( for (const OpenPolyline& a : unsupported_line_segments) { - const OpenPolyline& simplified = s.polyline(a); + const OpenPolyline simplified = s.polyline(a); for (const Point2LL& point : simplified) { size_t idx = expanded_inv_supported_area.findInside(point); From 917c3afc17f6c79b12327a7ce52c2665e1ef03e5 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 14:38:39 +0100 Subject: [PATCH 29/41] Slight optimization CURA-12232 --- src/FffGcodeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index b310b13c88..a736d1a900 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2067,7 +2067,7 @@ void getBestAngledLinesToSupportPoints(OpenLinesSet& result_lines, const Shape& void integrateSupportingLine(OpenLinesSet& infill_lines, const OpenPolyline& line_to_add) { // Returns the line index and the index of the point within an infill_line, null for no match found. - const auto findMatchingSegment = [&](Point2LL p) -> std::optional> + const auto findMatchingSegment = [&](const Point2LL& p) -> std::optional> { for (size_t i = 0; i < infill_lines.size(); ++i) { From 1ae45ef17a4a76c691840023857256cacf503bfa Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 30 Oct 2024 17:25:59 +0100 Subject: [PATCH 30/41] Fix possible crashes when using bad meshes CURA-12232 --- src/WallToolPaths.cpp | 13 +++++++++++++ src/WallsComputation.cpp | 22 ++++++++-------------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/WallToolPaths.cpp b/src/WallToolPaths.cpp index c34aaf8d69..1773f221c4 100644 --- a/src/WallToolPaths.cpp +++ b/src/WallToolPaths.cpp @@ -436,6 +436,19 @@ const Shape& WallToolPaths::getInnerContour() bool WallToolPaths::removeEmptyToolPaths(std::vector& toolpaths) { + for (VariableWidthLines& toolpath : toolpaths) + { + toolpath.erase( + std::remove_if( + toolpath.begin(), + toolpath.end(), + [](const ExtrusionLine& line) + { + return line.junctions_.empty(); + }), + toolpath.end()); + } + toolpaths.erase( std::remove_if( toolpaths.begin(), diff --git a/src/WallsComputation.cpp b/src/WallsComputation.cpp index 752d1cf0c9..f75c89defa 100644 --- a/src/WallsComputation.cpp +++ b/src/WallsComputation.cpp @@ -104,21 +104,15 @@ void WallsComputation::generateWalls(SliceLayer* layer, SectionType section) // Remove the parts which did not generate a wall. As these parts are too small to print, // and later code can now assume that there is always minimal 1 wall line. - if (settings_.get("wall_line_count") >= 1 && ! settings_.get("fill_outline_gaps")) - { - for (size_t part_idx = 0; part_idx < layer->parts.size(); part_idx++) + bool check_wall_and_spiral = settings_.get("wall_line_count") >= 1 && ! settings_.get("fill_outline_gaps"); + auto iterator_remove = std::remove_if( + layer->parts.begin(), + layer->parts.end(), + [&check_wall_and_spiral](const SliceLayerPart& part) { - if (layer->parts[part_idx].wall_toolpaths.empty() && layer->parts[part_idx].spiral_wall.empty()) - { - if (part_idx != layer->parts.size() - 1) - { // move existing part into part to be deleted - layer->parts[part_idx] = std::move(layer->parts.back()); - } - layer->parts.pop_back(); // always remove last element from array (is more efficient) - part_idx -= 1; // check the part we just moved here - } - } - } + return (check_wall_and_spiral && part.wall_toolpaths.empty() && part.spiral_wall.empty()) || part.outline.empty() || part.print_outline.empty(); + }); + layer->parts.erase(iterator_remove, layer->parts.end()); } void WallsComputation::generateSpiralInsets(SliceLayerPart* part, coord_t line_width_0, coord_t wall_0_inset, bool recompute_outline_based_on_outer_wall) From 69c3e42cd04d3a57e2832dda18f9a2def41f9bd5 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 31 Oct 2024 10:10:16 +0100 Subject: [PATCH 31/41] Apply code cleaning suggestions CURA-12176 --- include/LayerPlan.h | 46 +++++++++++++++++++++++++++------------------ src/LayerPlan.cpp | 30 ++++++++++------------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index b2b207d3be..dd6ac02c83 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -396,6 +396,8 @@ class LayerPlan : public NoCopy * \param spiralize Whether to gradually increase the z height from the normal layer height to the height of the next layer over this polygon * \param flow_ratio The ratio with which to multiply the extrusion amount * \param always_retract Whether to force a retraction when moving to the start of the polygon (used for outer walls) + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addPolygon( const Polygon& polygon, @@ -408,8 +410,7 @@ class LayerPlan : public NoCopy const Ratio& flow_ratio = 1.0_r, bool always_retract = false, bool scarf_seam = false, - bool smooth_speed = false, - bool is_candidate_small_feature = false); + bool smooth_speed = false); /*! * Add polygons to the gcode with optimized order. @@ -438,6 +439,8 @@ class LayerPlan : public NoCopy * \param reverse_order Adds polygons in reverse order. * \param start_near_location Start optimising the path near this location. * If unset, this causes it to start near the last planned location. + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addPolygonsByOptimizer( const Shape& polygons, @@ -535,6 +538,8 @@ class LayerPlan : public NoCopy * polyline). * \param is_reversed Whether to print this wall in reverse direction. * \param is_linked_path Whether the path is a continuation off the previous path + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path */ void addWall( const ExtrusionLine& wall, @@ -844,6 +849,23 @@ class LayerPlan : public NoCopy PrintFeatureType feature, bool update_extrusion_offset = false); + /*! + * \brief Alias for a function definition that adds an extrusion segment + * \param start The start position of the segment + * \param end The end position of the segment + * \param speed_factor The speed factor to be applied when extruding this specific segment (relative to nominal speed for the entire path) + * \param flow_ratio The flow ratio to be applied when extruding this specific segment (relative to nominal flow for the entire path) + * \param line_width_ratio The line width ratio to be applied when extruding this specific segment (relative to nominal line width for the entire path) + * \param distance_to_bridge_start The calculate distance to the next bridge start, which may be irrelevant in some cases + */ + using AddExtrusionSegmentFunction = std::function; + /*! * \brief Add a wall to the gcode with optimized order, but split into pieces in order to facilitate the scarf seam and/or speed gradient. * \tparam PathType The type of path to be processed, either ExtrusionLine or some subclass of Polyline @@ -902,13 +924,7 @@ class LayerPlan : public NoCopy const coord_t decelerate_length, const bool is_scarf_closure, const bool compute_distance_to_bridge_start, - const std::function& func_add_segment); + const AddExtrusionSegmentFunction& func_add_segment); /*! * \brief Add a wall to the gcode with optimized order, possibly adding a scarf seam / speed gradient according to settings @@ -922,8 +938,8 @@ class LayerPlan : public NoCopy * \param is_closed Indicates whether the path is closed (or open) * \param is_reversed Indicates if the path is to be processed backwards * \param is_candidate_small_feature Indicates whether the path should be tested for being treated as a smell feature - * \param scarf_seam Indicates whether we may set a scraf seam to the path - * \param smooth_speed Indicates whether we may set a speed gradient to the path + * \param scarf_seam Indicates whether we may use a scarf seam for the path + * \param smooth_speed Indicates whether we may use a speed gradient for the path * \param func_add_segment The function to be called to actually add an extrusion segment with the given parameters */ template @@ -939,13 +955,7 @@ class LayerPlan : public NoCopy const bool is_candidate_small_feature, const bool scarf_seam, const bool smooth_speed, - const std::function& func_add_segment); + const AddExtrusionSegmentFunction& func_add_segment); /*! * \brief Helper function to calculate the distance from the start of the current wall line to the first bridge segment diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 66723a14bf..7646fbbbee 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -565,10 +565,10 @@ void LayerPlan::addPolygon( const Ratio& flow_ratio, bool always_retract, bool scarf_seam, - bool smooth_speed, - bool is_candidate_small_feature) + bool smooth_speed) { constexpr bool is_closed = true; + constexpr bool is_candidate_small_feature = false; Point2LL p0 = polygon[start_idx]; addTravel(p0, always_retract, config.z_offset); @@ -659,7 +659,7 @@ void LayerPlan::addPolygonsByOptimizer( } orderOptimizer.optimize(); - auto add_polygons + const auto add_polygons = [this, &config, &settings, &wall_0_wipe_dist, &spiralize, &flow_ratio, &always_retract, &scarf_seam, &smooth_speed](const auto& iterator_begin, const auto& iterator_end) { for (auto iterator = iterator_begin; iterator != iterator_end; ++iterator) @@ -1065,13 +1065,7 @@ void LayerPlan::addSplitWall( const coord_t decelerate_length, const bool is_scarf_closure, const bool compute_distance_to_bridge_start, - const std::function& func_add_segment) + const AddExtrusionSegmentFunction& func_add_segment) { coord_t distance_to_bridge_start = 0; // will be updated before each line is processed Point2LL p0 = wall.pointAt(start_idx); @@ -1098,6 +1092,9 @@ void LayerPlan::addSplitWall( if constexpr (std::is_same_v) { + // The bridging functionality has not been designed to work with anything else than ExtrusionLine objects, + // and there is no need to do it otherwise yet. So the compute_distance_to_bridge_start argument will + // just be ignored if using an other PathType (e.g. Polygon) if (compute_distance_to_bridge_start && ! bridge_wall_mask_.empty()) { distance_to_bridge_start = computeDistanceToBridgeStart(wall.getPath(), (wall.size() + start_idx + point_idx * direction - 1) % wall.size(), min_bridge_line_len); @@ -1368,13 +1365,7 @@ void LayerPlan::addWallWithScarfSeam( const bool is_candidate_small_feature, const bool scarf_seam, const bool smooth_speed, - const std::function& func_add_segment) + const AddExtrusionSegmentFunction& func_add_segment) { if (wall.empty()) { @@ -1494,8 +1485,7 @@ void LayerPlan::addWall( wall.inset_idx_ == 0, scarf_seam, smooth_speed, - [this, &settings, &default_config, &roofing_config, &bridge_config, &non_bridge_line_volume]( - const Point3LL& start, + [&](const Point3LL& start, const Point3LL& end, const Ratio& speed_factor, const Ratio& actual_flow_ratio, @@ -2492,7 +2482,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 34af646bd48edde42623cb22b21d3dba11e0d454 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Thu, 31 Oct 2024 09:10:50 +0000 Subject: [PATCH 32/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 7646fbbbee..0420da65e2 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2482,7 +2482,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From eeadea0dfdd1b5e7571f98e3150b7f1c4b5af9c0 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 31 Oct 2024 13:09:32 +0100 Subject: [PATCH 33/41] Fix extrusion commands with Z0 CURA-12244 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 7d7dc3a2d1..9e613fadae 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -2880,7 +2880,7 @@ bool LayerPlan::writePathWithCoasting( prev_pt = path.points[point_idx]; } - gcode.writeExtrusion(start, extrude_speed, path.getExtrusionMM3perMM(), path.config.type); + writeExtrusionRelativeZ(gcode, start, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); sendLineTo(path, start, extrude_speed); } From 3a8682b217e896c1a92185eb400a23a4be543dcf Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Thu, 31 Oct 2024 12:10:24 +0000 Subject: [PATCH 34/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 9e613fadae..c1198d7f20 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -2381,7 +2381,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From dbd79433d9fb347a3a7c4ddaee893b76096ca965 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 1 Nov 2024 11:36:19 +0100 Subject: [PATCH 35/41] Fix outer wall being translated CURA-12251 Instead of always reusing the results of the split segments destination, we now calculate them from the initial wall segment, avoiding some rounding errors being accumulated over time and translating the whole path significantly. --- src/LayerPlan.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index abd8540644..684ed5f2da 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1089,6 +1089,7 @@ void LayerPlan::addSplitWall( const size_t actual_point_index = (wall.size() + start_idx + point_idx * direction) % wall.size(); const Point2LL& p1 = wall.pointAt(actual_point_index); const coord_t w1 = wall.lineWidthAt(actual_point_index); + coord_t segment_processed_distance = 0; if constexpr (std::is_same_v) { @@ -1191,7 +1192,8 @@ void LayerPlan::addSplitWall( // Now take the closest position candidate and make a sub-segment to it const coord_t destination_position = *std::min_element(split_positions.begin(), split_positions.end()); const coord_t length_to_process = destination_position - wall_processed_distance; - Point3LL split_destination = split_origin + normal(line_vector, length_to_process); + const double destination_factor = static_cast(segment_processed_distance + length_to_process) / line_length; + Point3LL split_destination = cura::lerp(p0, p1, destination_factor); double scarf_segment_flow_ratio = 1.0; double scarf_factor_destination = 1.0; // Out of range, scarf is done => 1.0 @@ -1258,6 +1260,7 @@ void LayerPlan::addSplitWall( distance_to_bridge_start); wall_processed_distance = destination_position; + segment_processed_distance += length_to_process; piece_remaining_distance -= length_to_process; split_origin = split_destination; scarf_factor_origin = scarf_factor_destination; @@ -1288,7 +1291,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From 03116d9dd8c950c9b25517c14c91cbcab4d256d6 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 1 Nov 2024 10:36:51 +0000 Subject: [PATCH 36/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 684ed5f2da..ff345c7089 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1291,7 +1291,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From d68cc1160ba3482e4f96068cf007185d9433a81c Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 1 Nov 2024 12:53:44 +0100 Subject: [PATCH 37/41] Fix build, broken with conflict resolving durimng merge CURA-12244 --- src/LayerPlan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 7856d9f393..df174b4fc4 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1288,7 +1288,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -2961,7 +2961,7 @@ bool LayerPlan::writePathWithCoasting( previous_position = path.points[point_idx]; } - writeExtrusionRelativeZ(gcode, start, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); + writeExtrusionRelativeZ(gcode, path_coasting.coasting_start_pos, extrude_speed, path.z_offset, path.getExtrusionMM3perMM(), path.config.type); sendLineTo(path, path_coasting.coasting_start_pos, extrude_speed); } From 71e058c1834450f89be28f5e4c4b93bd05fdb315 Mon Sep 17 00:00:00 2001 From: wawanbreton Date: Fri, 1 Nov 2024 11:54:12 +0000 Subject: [PATCH 38/41] Applied clang-format. --- src/LayerPlan.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index df174b4fc4..e38d98edd7 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1288,7 +1288,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto& path_a, const auto& path_b) + [](const auto&path_a, const auto&path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) From bc50df1bf6d4bacdc2870e5a43f8c9b386c70d13 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 4 Nov 2024 12:57:33 +0100 Subject: [PATCH 39/41] Bump grpc_definitions version for 5.9 CURA-12259 We made some significant changes between 5.8 and 5.9, so now is the time to bump the version number in order to indicate this. --- conandata.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conandata.yml b/conandata.yml index a44c2c758b..cf5986eed1 100644 --- a/conandata.yml +++ b/conandata.yml @@ -5,6 +5,6 @@ requirements: requirements_arcus: - "arcus/5.4.1" requirements_plugins: - - "curaengine_grpc_definitions/0.2.1" + - "curaengine_grpc_definitions/0.3.0" requirements_cura_resources: - "cura_resources/5.9.0-beta.1" From f684de325f5d50d1a48135e06a580bec42c3889c Mon Sep 17 00:00:00 2001 From: HellAholic Date: Tue, 5 Nov 2024 13:05:13 +0000 Subject: [PATCH 40/41] Set conan package version 5.9.0-beta.2 --- conandata.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conandata.yml b/conandata.yml index cf5986eed1..28abea3228 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,4 +1,4 @@ -version: "5.9.0-beta.1" +version: "5.9.0-beta.2" commit: "unknown" requirements: - "scripta/0.1.0@ultimaker/testing" @@ -7,4 +7,4 @@ requirements_arcus: requirements_plugins: - "curaengine_grpc_definitions/0.3.0" requirements_cura_resources: - - "cura_resources/5.9.0-beta.1" + - "cura_resources/5.9.0-beta.2" From 0948be12be131fb28611181290892e3f34bb6ee2 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 5 Nov 2024 15:20:47 +0100 Subject: [PATCH 41/41] Fix dirty print lines and micro-segments CURA-12263 --- src/LayerPlan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index bb2eccf524..6f8fdbdc29 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -1271,6 +1271,7 @@ void LayerPlan::addSplitWall( } p0 = p1; + w0 = w1; } }