From d340e68510dd70f2dee334eb8ece06ab23485b8d Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Thu, 13 Apr 2023 13:23:40 +0200 Subject: [PATCH 1/5] Experimental hole removal for tree support --- include/TreeSupport.h | 3 + src/TreeSupport.cpp | 202 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 3 deletions(-) diff --git a/include/TreeSupport.h b/include/TreeSupport.h index 674b976bb3..e1e4e2a1bd 100644 --- a/include/TreeSupport.h +++ b/include/TreeSupport.h @@ -276,6 +276,9 @@ class TreeSupport const std::map& inverse_tree_order ); + void filterFloatingLines(std::vector& support_layer_storage); + + /*! * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage * diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 8e26b59729..1ac34a0ab9 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -1858,6 +1858,199 @@ void TreeSupport::dropNonGraciousAreas ); } +void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) +{ + const auto t_start = std::chrono::high_resolution_clock::now(); + + const coord_t closing_dist=config.support_line_width*config.support_wall_count; + const coord_t open_close_distance = config.settings.get("fill_outline_gaps") ? config.settings.get("min_feature_size") / 2 - 5 : config.settings.get("min_wall_line_width") / 2 - 5; + const double small_area_length = INT2MM(static_cast(config.support_line_width) / 2); + + std::function reversePolygon = [&](Polygons& poly) + { + for (size_t idx = 0; idx < poly.size(); idx++) + { + poly[idx].reverse(); + } + }; + + + std::vector support_holes(support_layer_storage.size(),Polygons()); + //Extract all holes as polygon objects + cura::parallel_for + ( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + + + support_layer_storage[layer_idx] = config.simplifier.polygon(PolygonUtils::unionManySmall(support_layer_storage[layer_idx].smooth(FUDGE_LENGTH))).offset(-open_close_distance).offset(open_close_distance * 2).offset(-open_close_distance); + support_layer_storage[layer_idx].removeSmallAreas(small_area_length * small_area_length, false); + + std::vector parts = support_layer_storage[layer_idx].sortByNesting(); + + if (parts.size() <= 1) + { + return; + } + + Polygons holes_original; + for (size_t idx = 1; idx < parts.size(); idx++) + { + Polygons area = parts[idx]; + reversePolygon(area); + holes_original.add(area); + } + support_holes[layer_idx] = holes_original; + } + ); + const auto t_union = std::chrono::high_resolution_clock::now(); + + + std::vector> holeparts(support_layer_storage.size()); + //Split all holes into parts + cura::parallel_for + ( + 0, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + for (Polygons hole:support_holes[layer_idx].splitIntoParts()) + { + holeparts[layer_idx].emplace_back(hole); + } + } + ); + std::vector>> hole_rest_map (holeparts.size()); + std::vector> holes_resting_outside (holeparts.size()); + + //Figure out which hole rests on which other hole + cura::parallel_for + ( + 1, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + if (holeparts[layer_idx].empty()) + { + return; + } + + Polygons outer_walls = + TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).tubeShape(closing_dist,0).unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); + + + for (size_t idx = 0; idx < holeparts[layer_idx].size(); idx++) + { + if (!holeparts[layer_idx][idx].intersection(outer_walls).empty()) + { + holes_resting_outside[layer_idx].emplace(idx); + } + else + { + for (size_t idx2 = 0; idx2 < holeparts[layer_idx - 1].size(); idx2++) // todo check if there is a better way to find out which polygons inside a polygons are intersected with, so that instead for each part one, only one intersection has to be done + { + if (! holeparts[layer_idx][idx].intersection(holeparts[layer_idx - 1][idx2]).empty() ) // todo should technically be outline: Check if this is fine either way as it would save an offset + { + hole_rest_map[layer_idx][idx].emplace_back(idx2); + } + } + } + } + } + ); + + const auto t_hole_rest_ordering = std::chrono::high_resolution_clock::now(); + + std::unordered_set removed_holes_by_idx; + std::vector valid_holes(support_holes.size(), Polygons()); + + //Check which holes have to be removed as they do not rest on anything. Only keep holes that have to be removed + for (LayerIndex layer_idx = 1; layer_idx < support_holes.size(); layer_idx++) + { + std::unordered_set next_removed_holes_by_idx; + + for (size_t idx = 0; idx < holeparts[layer_idx].size(); idx++) + { + bool found = false; + if (holes_resting_outside[layer_idx].contains(idx)) + { + found = true; + } + else + { + if(hole_rest_map[layer_idx].contains(idx)){ + for (size_t resting_idx : hole_rest_map[layer_idx][idx]) + { + if (! removed_holes_by_idx.contains(resting_idx)) + { + found = true; + break; + } + } + } + } + if (! found) + { + next_removed_holes_by_idx.emplace(idx); + } + else + { + valid_holes[layer_idx].add(holeparts[layer_idx][idx]); + holeparts[layer_idx][idx] = Polygons(); // all remaining holes will have to be removed later, so removing the hole means it is confirmed valid! + } + } + removed_holes_by_idx = next_removed_holes_by_idx; + } + const auto t_hole_removal_tagging = std::chrono::high_resolution_clock::now(); + + //Check if holes are so close to each other that two lines will be printed directly next to each other, which is assumed stable (as otherwise the simulated support pattern will not work correctly) and remove all remaining, invalid holes + cura::parallel_for + ( + 1, + support_layer_storage.size(), + [&](const LayerIndex layer_idx) + { + if (holeparts[layer_idx].empty()) + { + return; + } + Polygons offset_valids; + + for (Polygons hole : holeparts[layer_idx]) + { + if (! hole.empty()) + { + if (offset_valids.empty()) + { + // todo What is better, having the top support pattern potentially broken (as holes that are required to form lines are removed) or having lines that technically are in the air? + offset_valids = valid_holes[layer_idx].offset(closing_dist*2).unionPolygons(); + } + + if (offset_valids.intersection(hole).empty()) + { + + reversePolygon(hole); + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].unionPolygons(hole); + + } + } + } + } + ); + + const auto t_end = std::chrono::high_resolution_clock::now(); + + const auto dur_union = 0.001 * std::chrono::duration_cast(t_union - t_start).count(); + const auto dur_hole_rest_ordering = 0.001 * std::chrono::duration_cast(t_hole_rest_ordering - t_union).count(); + const auto dur_hole_removal_tagging = 0.001 * std::chrono::duration_cast(t_hole_removal_tagging - t_hole_rest_ordering).count(); + + const auto dur_hole_removal = 0.001 * std::chrono::duration_cast(t_end - t_hole_removal_tagging).count(); + spdlog::info("Time to union areas: {} ms Time to evaluate which hole rest on which other hole: {} ms Time to see which holes are not resting on anything valid: {} ms remove all holes that are invalid and not close enough to a valid hole: {} ms", dur_union,dur_hole_rest_ordering,dur_hole_removal_tagging, dur_hole_removal); + +} + void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& support_layer_storage, std::vector& support_roof_storage, SliceDataStorage& storage) { InterfacePreference interface_pref = config.interface_preference; // InterfacePreference::SUPPORT_LINES_OVERWRITE_INTERFACE; @@ -1871,7 +2064,6 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor support_layer_storage.size(), [&](const LayerIndex layer_idx) { - support_layer_storage[layer_idx] = config.simplifier.polygon(PolygonUtils::unionManySmall(support_layer_storage[layer_idx]).smooth(FUDGE_LENGTH)).getOutsidePolygons(); // ^^^ Most of the time in this function is this union call. It can take a relatively long time when a lot of areas are to be unioned. // Also simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. @@ -2082,14 +2274,18 @@ void TreeSupport::drawAreas(std::vector>& move_bou scripta::log("tree_support_layer_storage", support_layer_storage[layer_idx], SectionType::SUPPORT, layer_idx); } + filterFloatingLines(support_layer_storage); + const auto t_filter = std::chrono::high_resolution_clock::now(); + finalizeInterfaceAndSupportAreas(support_layer_storage, support_roof_storage, storage); const auto t_end = std::chrono::high_resolution_clock::now(); const auto dur_gen_tips = 0.001 * std::chrono::duration_cast(t_generate - t_start).count(); const auto dur_smooth = 0.001 * std::chrono::duration_cast(t_smooth - t_generate).count(); const auto dur_drop = 0.001 * std::chrono::duration_cast(t_drop - t_smooth).count(); - const auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_drop).count(); - spdlog::info("Time used for drawing subfuctions: generateBranchAreas: {} ms smoothBranchAreas: {} ms dropNonGraciousAreas: {} ms finalizeInterfaceAndSupportAreas {} ms", dur_gen_tips, dur_smooth, dur_drop, dur_finalize); + const auto dur_filter = 0.001 * std::chrono::duration_cast(t_filter - t_drop).count(); + const auto dur_finalize = 0.001 * std::chrono::duration_cast(t_end - t_filter).count(); + spdlog::info("Time used for drawing subfuctions: generateBranchAreas: {} ms smoothBranchAreas: {} ms dropNonGraciousAreas: {} ms filterFloatingLines: {} ms finalizeInterfaceAndSupportAreas {} ms", dur_gen_tips, dur_smooth, dur_drop, dur_filter, dur_finalize); } } // namespace cura From e676aab00a71b0296a4da1fd8aa36be1e7a9e556 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Sat, 15 Apr 2023 08:04:32 +0200 Subject: [PATCH 2/5] Improved performance of hole removal (to prevent floating lines inside branches). This includes porting a function from PrusaSlicer that intersects a Polygon with an AABB. Fixed a small bug related to line orientation for simulated support lines. --- include/TreeSupportSettings.h | 22 ++++++++++-- include/utils/polygonUtils.h | 9 +++++ src/TreeSupport.cpp | 59 ++++++++++++++----------------- src/utils/polygonUtils.cpp | 66 +++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index e5d4e6cbc8..faf87dc0e5 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -67,6 +67,8 @@ struct TreeSupportSettings connect_zigzags(mesh_group_settings.get("support_connect_zigzags")), settings(mesh_group_settings), min_feature_size(mesh_group_settings.get("min_feature_size")), + min_wall_line_width(settings.get("min_wall_line_width")), + fill_outline_gaps(settings.get("fill_outline_gaps")), simplifier(Simplify(mesh_group_settings)) { layer_start_bp_radius = (bp_radius - branch_radius) / (branch_radius * diameter_scale_bp_radius); @@ -110,8 +112,12 @@ struct TreeSupportSettings } }; - getInterfaceAngles(support_infill_angles, support_pattern); - getInterfaceAngles(support_roof_angles, roof_pattern); + if(support_infill_angles.empty()) + { + support_infill_angles.push_back(0); + } + + getInterfaceAngles(support_roof_angles, roof_pattern); //todo remove lambda const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE }, @@ -354,6 +360,16 @@ struct TreeSupportSettings */ coord_t min_feature_size; + /*! + * \brief Minimum thickness a wall can have. + */ + coord_t min_wall_line_width; + + /*! + * \brief If areas of min_feature_size are enlarged to min_wall_line_width + */ + bool fill_outline_gaps; + /*! * \brief Simplifier to simplify polygons. */ @@ -403,6 +419,8 @@ struct TreeSupportSettings min_feature_size == other.min_feature_size && // interface_preference should be identical to ensure the tree will correctly interact with the roof. support_rest_preference == other.support_rest_preference && max_radius == other.max_radius && + min_wall_line_width == other.min_wall_line_width && + fill_outline_gaps == other.fill_outline_gaps && // The infill class now wants the settings object and reads a lot of settings, and as the infill class is used to calculate support roof lines for interface-preference. Not all of these may be required to be identical, but as I am not sure, better safe than sorry ( interface_preference == InterfacePreference::INTERFACE_AREA_OVERWRITES_SUPPORT || diff --git a/include/utils/polygonUtils.h b/include/utils/polygonUtils.h index b6c7bc59e3..13786e71c7 100644 --- a/include/utils/polygonUtils.h +++ b/include/utils/polygonUtils.h @@ -610,6 +610,15 @@ class PolygonUtils static Polygons unionManySmall(const Polygons& p); + + /*! + * Intersects a polygon with an AABB. + * \param src The polygon that has to be intersected with an AABB + * \param aabb The AABB with which the polygon that has to be intersected with + * \return A new Polygon that is said intersection + */ + static Polygons clipPolygonWithAABB(const Polygons& src, const AABB& aabb); + private: /*! * Helper function for PolygonUtils::moveInside2: moves a point \p from which was moved onto \p closest_polygon_point towards inside/outside when it's not already inside/outside by enough distance. diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 1ac34a0ab9..f9d6769606 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -1858,12 +1858,13 @@ void TreeSupport::dropNonGraciousAreas ); } + void TreeSupport::filterFloatingLines(std::vector& support_layer_storage) { const auto t_start = std::chrono::high_resolution_clock::now(); const coord_t closing_dist=config.support_line_width*config.support_wall_count; - const coord_t open_close_distance = config.settings.get("fill_outline_gaps") ? config.settings.get("min_feature_size") / 2 - 5 : config.settings.get("min_wall_line_width") / 2 - 5; + const coord_t open_close_distance = config.fill_outline_gaps ? config.min_feature_size/ 2 - 5 : config.min_wall_line_width/ 2 - 5; // based on calculation in WallToolPath const double small_area_length = INT2MM(static_cast(config.support_line_width) / 2); std::function reversePolygon = [&](Polygons& poly) @@ -1896,7 +1897,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora } Polygons holes_original; - for (size_t idx = 1; idx < parts.size(); idx++) + for (const size_t idx : ranges::views::iota(1UL, parts.size())) { Polygons area = parts[idx]; reversePolygon(area); @@ -1905,8 +1906,8 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora support_holes[layer_idx] = holes_original; } ); - const auto t_union = std::chrono::high_resolution_clock::now(); + const auto t_union = std::chrono::high_resolution_clock::now(); std::vector> holeparts(support_layer_storage.size()); //Split all holes into parts @@ -1925,6 +1926,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora std::vector>> hole_rest_map (holeparts.size()); std::vector> holes_resting_outside (holeparts.size()); + //Figure out which hole rests on which other hole cura::parallel_for ( @@ -1938,20 +1940,29 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora } Polygons outer_walls = - TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).tubeShape(closing_dist,0).unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); + TreeSupportUtils::toPolylines(support_layer_storage[layer_idx - 1].getOutsidePolygons()).tubeShape(closing_dist,0);//.unionPolygons(volumes_.getCollision(0, layer_idx - 1, true).offset(-(config.support_line_width+config.xy_min_distance))); + + Polygons holes_below; + for (auto poly: holeparts[layer_idx - 1]) + { + holes_below.add(poly); + } - for (size_t idx = 0; idx < holeparts[layer_idx].size(); idx++) + for (auto [idx, hole] : holeparts[layer_idx] | ranges::views::enumerate) { - if (!holeparts[layer_idx][idx].intersection(outer_walls).empty()) + AABB hole_aabb = AABB(hole); + hole_aabb.expand(EPSILON); + if (!hole.intersection(PolygonUtils::clipPolygonWithAABB(outer_walls,hole_aabb)).empty()) { holes_resting_outside[layer_idx].emplace(idx); } else { - for (size_t idx2 = 0; idx2 < holeparts[layer_idx - 1].size(); idx2++) // todo check if there is a better way to find out which polygons inside a polygons are intersected with, so that instead for each part one, only one intersection has to be done + for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { - if (! holeparts[layer_idx][idx].intersection(holeparts[layer_idx - 1][idx2]).empty() ) // todo should technically be outline: Check if this is fine either way as it would save an offset + + if (AABB(hole).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_rest_map[layer_idx][idx].emplace_back(idx2); } @@ -1965,13 +1976,12 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora std::unordered_set removed_holes_by_idx; std::vector valid_holes(support_holes.size(), Polygons()); - //Check which holes have to be removed as they do not rest on anything. Only keep holes that have to be removed - for (LayerIndex layer_idx = 1; layer_idx < support_holes.size(); layer_idx++) + for (const size_t layer_idx : ranges::views::iota(1UL, support_holes.size())) { std::unordered_set next_removed_holes_by_idx; - for (size_t idx = 0; idx < holeparts[layer_idx].size(); idx++) + for (auto [idx, hole] : holeparts[layer_idx] | ranges::views::enumerate) { bool found = false; if (holes_resting_outside[layer_idx].contains(idx)) @@ -1997,7 +2007,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora } else { - valid_holes[layer_idx].add(holeparts[layer_idx][idx]); + valid_holes[layer_idx].add(hole); holeparts[layer_idx][idx] = Polygons(); // all remaining holes will have to be removed later, so removing the hole means it is confirmed valid! } } @@ -2016,27 +2026,10 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora { return; } - Polygons offset_valids; - - for (Polygons hole : holeparts[layer_idx]) - { - if (! hole.empty()) - { - if (offset_valids.empty()) - { - // todo What is better, having the top support pattern potentially broken (as holes that are required to form lines are removed) or having lines that technically are in the air? - offset_valids = valid_holes[layer_idx].offset(closing_dist*2).unionPolygons(); - } - - if (offset_valids.intersection(hole).empty()) - { - - reversePolygon(hole); - support_layer_storage[layer_idx] = support_layer_storage[layer_idx].unionPolygons(hole); - } - } - } + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].getOutsidePolygons(); + reversePolygon(valid_holes[layer_idx]); + support_layer_storage[layer_idx].add(valid_holes[layer_idx]); } ); @@ -2047,7 +2040,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora const auto dur_hole_removal_tagging = 0.001 * std::chrono::duration_cast(t_hole_removal_tagging - t_hole_rest_ordering).count(); const auto dur_hole_removal = 0.001 * std::chrono::duration_cast(t_end - t_hole_removal_tagging).count(); - spdlog::info("Time to union areas: {} ms Time to evaluate which hole rest on which other hole: {} ms Time to see which holes are not resting on anything valid: {} ms remove all holes that are invalid and not close enough to a valid hole: {} ms", dur_union,dur_hole_rest_ordering,dur_hole_removal_tagging, dur_hole_removal); + spdlog::debug("Time to union areas: {} ms Time to evaluate which hole rest on which other hole: {} ms Time to see which holes are not resting on anything valid: {} ms remove all holes that are invalid and not close enough to a valid hole: {} ms", dur_union,dur_hole_rest_ordering,dur_hole_removal_tagging, dur_hole_removal); } diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index e678d80360..8135507834 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -1504,4 +1504,70 @@ Polygons PolygonUtils::unionManySmall(const Polygons& p) return unionManySmall(a).unionPolygons(unionManySmall(b)); } +Polygons PolygonUtils::clipPolygonWithAABB(const Polygons& src, const AABB& aabb) +{ + Polygons out; + out.reserve(src.size()); + for (const auto path : src) + { + Polygon poly; + + const size_t cnt = path.size(); + if (cnt < 3) + { + return Polygons(); + } + + enum class Side + { + Left = 1, + Right = 2, + Top = 4, + Bottom = 8 + }; + + auto sides = [aabb](const Point& p) { return int(p.X < aabb.min.X) * int(Side::Left) + int(p.X > aabb.max.X) * int(Side::Right) + int(p.Y < aabb.min.Y) * int(Side::Bottom) + int(p.Y > aabb.max.Y) * int(Side::Top); }; + + int sides_prev = sides(path.back()); + int sides_this = sides(path.front()); + const size_t last = cnt - 1; + + for (size_t i = 0; i < last; ++i) + { + int sides_next = sides(path[i + 1]); + if ( // This point is inside. Take it. + sides_this == 0 || + // Either this point is outside and previous or next is inside, or + // the edge possibly cuts corner of the bounding box. + (sides_prev & sides_this & sides_next) == 0) + { + poly.add(path[i]); + sides_prev = sides_this; + } + else + { + // All the three points (this, prev, next) are outside at the same side. + // Ignore this point. + } + sides_this = sides_next; + } + + // Never produce just a single point output polygon. + if (! poly.empty()) + { + int sides_next = sides(poly.front()); + if (sides_this == 0 || // The last point is inside. Take it. + (sides_prev & sides_this & sides_next) == 0) // Either this point is outside and previous or next is inside, or the edge possibly cuts corner of the bounding box. + + poly.add(path.back()); + } + + if (! poly.empty()) + { + out.add(poly); + } + } + return out; +} + } // namespace cura From c3bc96628cf9e8b8d1b68b8a1877c2ddafeb3147 Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Sun, 16 Apr 2023 22:53:58 +0200 Subject: [PATCH 3/5] Reworked and improved tree support tip handling for large areas when interface is disabled. --- include/SupportInfillPart.h | 4 +- include/TreeSupport.h | 14 ++- include/TreeSupportElement.h | 5 + include/TreeSupportSettings.h | 2 +- include/TreeSupportTipGenerator.h | 32 +++-- include/TreeSupportUtils.h | 20 +++- include/utils/polygon.h | 1 + src/FffGcodeWriter.cpp | 5 +- src/SupportInfillPart.cpp | 3 +- src/TreeSupport.cpp | 21 +++- src/TreeSupportTipGenerator.cpp | 186 +++++++++++++++++++++--------- src/support.cpp | 2 +- 12 files changed, 213 insertions(+), 82 deletions(-) diff --git a/include/SupportInfillPart.h b/include/SupportInfillPart.h index 8fae3f2309..2a1849ab55 100644 --- a/include/SupportInfillPart.h +++ b/include/SupportInfillPart.h @@ -33,7 +33,9 @@ class SupportInfillPart // for infill_areas[x][n], x means the density level and n means the thickness std::vector wall_toolpaths; //!< Any walls go here, not in the areas, where they could be combined vertically (don't combine walls). Binned by inset_idx. - SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate = 0); + coord_t custom_line_distance; + + SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate = 0, coord_t custom_line_distance = 0 ); const Polygons& getInfillArea() const; }; diff --git a/include/TreeSupport.h b/include/TreeSupport.h index e1e4e2a1bd..32f67cd074 100644 --- a/include/TreeSupport.h +++ b/include/TreeSupport.h @@ -32,6 +32,8 @@ constexpr auto TREE_PROGRESS_GENERATE_BRANCH_AREAS = TREE_PROGRESS_DRAW_AREAS / constexpr auto TREE_PROGRESS_SMOOTH_BRANCH_AREAS = TREE_PROGRESS_DRAW_AREAS / 3; constexpr auto TREE_PROGRESS_FINALIZE_BRANCH_AREAS = TREE_PROGRESS_DRAW_AREAS / 3; +constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA = 100; +constexpr auto SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS = 1; constexpr auto SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT = false; constexpr auto SUPPORT_TREE_ONLY_GRACIOUS_TO_MODEL = false; constexpr auto SUPPORT_TREE_AVOID_SUPPORT_BLOCKER = true; @@ -276,8 +278,8 @@ class TreeSupport const std::map& inverse_tree_order ); - void filterFloatingLines(std::vector& support_layer_storage); + void filterFloatingLines(std::vector& support_layer_storage); /*! * \brief Generates Support Floor, ensures Support Roof can not cut of branches, and saves the branches as support to storage @@ -298,12 +300,18 @@ class TreeSupport /*! * \brief Settings with the indexes of meshes that use these settings. - * */ std::vector>> grouped_meshes; - std::vector additional_required_support_area; //todo doku + /*! + * \brief Areas that should have been support roof, but where the roof settings would not allow any lines to be generated. + */ + std::vector additional_required_support_area; + /*! + * \brief A representation of already placed lines. Required for subtracting from new support areas. + */ + std::vector placed_support_lines_support_areas; /*! * \brief Generator for model collision, avoidance and internal guide volumes. diff --git a/include/TreeSupportElement.h b/include/TreeSupportElement.h index 570cfbfbfd..237e22baa2 100644 --- a/include/TreeSupportElement.h +++ b/include/TreeSupportElement.h @@ -375,6 +375,11 @@ struct TreeSupportElement */ Polygons influence_area_limit_area; + /*! + * \brief Additional locations that the tip should reach + */ + std::vector additional_ovalization_targets; + bool operator==(const TreeSupportElement& other) const { diff --git a/include/TreeSupportSettings.h b/include/TreeSupportSettings.h index faf87dc0e5..06a6292230 100644 --- a/include/TreeSupportSettings.h +++ b/include/TreeSupportSettings.h @@ -117,7 +117,7 @@ struct TreeSupportSettings support_infill_angles.push_back(0); } - getInterfaceAngles(support_roof_angles, roof_pattern); //todo remove lambda + getInterfaceAngles(support_roof_angles, roof_pattern); const std::unordered_map interface_map = { { "support_area_overwrite_interface_area", InterfacePreference::SUPPORT_AREA_OVERWRITES_INTERFACE }, diff --git a/include/TreeSupportTipGenerator.h b/include/TreeSupportTipGenerator.h index 580f692c80..16985e90dc 100644 --- a/include/TreeSupportTipGenerator.h +++ b/include/TreeSupportTipGenerator.h @@ -43,7 +43,7 @@ class TreeSupportTipGenerator * \return All lines of the \p polylines object, with information for each point regarding in which avoidance it is currently valid in. */ - void generateTips(SliceDataStorage& storage,const SliceMeshStorage& mesh ,std::vector>& move_bounds, std::vector& additional_support_areas); + void generateTips(SliceDataStorage& storage,const SliceMeshStorage& mesh ,std::vector>& move_bounds, std::vector& additional_support_areas, std::vector& placed_support_lines_support_areas); private: @@ -103,9 +103,10 @@ class TreeSupportTipGenerator * \param input[in] The lines on which evenly spaced points should be placed. * \param distance[in] The distance the points should be from each other. * \param min_points[in] The amount of points that have to be placed. If not enough can be placed the distance will be reduced to place this many points. + * \param enforce_distance[in] If points should not be added if they are closer than distance to other points. * \return A Polygons object containing the evenly spaced points. Does not represent an area, more a collection of points on lines. */ - Polygons ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points) const; + Polygons ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points, bool enforce_distance) const; /*! * \brief Creates a valid CrossInfillProvider @@ -142,7 +143,7 @@ class TreeSupportTipGenerator * \param roof[in] Whether the tip supports a roof. * \param skip_ovalisation[in] Whether the tip may be ovalized when drawn later. */ - void addPointAsInfluenceArea(std::vector>& move_bounds, std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation); + void addPointAsInfluenceArea(std::vector>& move_bounds, std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation, std::vector additional_ovalization_targets = std::vector()); /*! @@ -154,7 +155,7 @@ class TreeSupportTipGenerator * \param supports_roof[in] Whether the tip supports a roof. * \param dont_move_until[in] Until which dtt the branch should not move if possible. */ - void addLinesAsInfluenceAreas(std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until); + void addLinesAsInfluenceAreas(std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until, bool connect_points); /*! * \brief Remove tips that should not have been added in the first place. @@ -164,9 +165,14 @@ class TreeSupportTipGenerator */ void removeUselessAddedPoints(std::vector>& move_bounds,SliceDataStorage& storage, std::vector& additional_support_areas); + + /*! + * \brief If large areas should be supported by a roof out of regular support lines. + */ + bool use_fake_roof; + /*! * \brief Generator for model collision, avoidance and internal guide volumes. - * */ TreeModelVolumes& volumes_; @@ -175,10 +181,6 @@ class TreeSupportTipGenerator */ TreeSupportSettings config; - /*! - * \brief Distance between support roof lines. Is required for generating roof patterns. - */ - const coord_t support_roof_line_distance; /*! * \brief Minimum area an overhang has to have to become a roof. @@ -205,6 +207,12 @@ class TreeSupportTipGenerator */ const coord_t support_tree_branch_distance; + /*! + * \brief Distance between support roof lines. Is required for generating roof patterns. + */ + const coord_t support_roof_line_distance; + + /*! * \brief Amount of offset to each overhang for support with regular branches (as opposed to roof). */ @@ -260,6 +268,10 @@ class TreeSupportTipGenerator */ const bool force_minimum_roof_area = SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT; + /*! + * \brief Distance between branches when the branches support a support pattern + */ + coord_t support_supporting_branch_distance; /*! * \brief Required to generate cross infill patterns @@ -282,6 +294,8 @@ class TreeSupportTipGenerator std::vector roof_tips_drawn; + + std::mutex critical_move_bounds; std::mutex critical_roof_tips; diff --git a/include/TreeSupportUtils.h b/include/TreeSupportUtils.h index 36219ce7a7..7a72316cb9 100644 --- a/include/TreeSupportUtils.h +++ b/include/TreeSupportUtils.h @@ -91,7 +91,7 @@ class TreeSupportUtils * \param layer_idx[in] The current layer index. * \param support_infill_distance[in] The distance that should be between the infill lines. * \param cross_fill_provider[in] A SierpinskiFillProvider required for cross infill. - * + * \param include_walls[in] If the result should also contain walls, or only the infill. * \return A Polygons object that represents the resulting infill lines. */ [[nodiscard]] static Polygons generateSupportInfillLines(const Polygons& area,const TreeSupportSettings& config, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider, bool include_walls) @@ -324,6 +324,24 @@ class TreeSupportUtils return result; } + + [[nodiscard]]static VariableWidthLines polyLineToVWL(const Polygons& polylines, coord_t line_width) + { + VariableWidthLines result; + for (auto path: polylines) + { + ExtrusionLine vwl_line(1,true); + + for(Point p: path) + { + vwl_line.emplace_back(p,line_width,1); + } + result.emplace_back(vwl_line); + } + return result; + } + + }; } //namespace cura diff --git a/include/utils/polygon.h b/include/utils/polygon.h index 1e22a61654..8bd2303429 100644 --- a/include/utils/polygon.h +++ b/include/utils/polygon.h @@ -987,6 +987,7 @@ class Polygons return ret; } + /*! * Intersect polylines with this area Polygons object. * diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 14f9164bd8..bbc83c8a63 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -2801,13 +2801,12 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer } const unsigned int density_factor = 2 << density_idx; // == pow(2, density_idx + 1) - int support_line_distance_here = default_support_line_distance * density_factor; // the highest density infill combines with the next to create a grid with density_factor 1 + int support_line_distance_here = (part.custom_line_distance > 0 ? part.custom_line_distance : default_support_line_distance * density_factor); // the highest density infill combines with the next to create a grid with density_factor 1 const int support_shift = support_line_distance_here / 2; - if (density_idx == max_density_idx || support_pattern == EFillMethod::CROSS || support_pattern == EFillMethod::CROSS_3D) + if (part.custom_line_distance == 0 && (density_idx == max_density_idx || support_pattern == EFillMethod::CROSS || support_pattern == EFillMethod::CROSS_3D)) { support_line_distance_here /= 2; } - const Polygons& area = part.infill_area_per_combine_per_density[density_idx][combine_idx]; constexpr size_t wall_count = 0; // Walls are generated somewhere else, so their layers aren't vertically combined. diff --git a/src/SupportInfillPart.cpp b/src/SupportInfillPart.cpp index 24105fb42a..76344af468 100644 --- a/src/SupportInfillPart.cpp +++ b/src/SupportInfillPart.cpp @@ -7,11 +7,12 @@ using namespace cura; -SupportInfillPart::SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate) +SupportInfillPart::SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate, coord_t custom_line_distance) : outline(outline) , outline_boundary_box(outline) , support_line_width(support_line_width) , inset_count_to_generate(inset_count_to_generate) +, custom_line_distance(custom_line_distance) { infill_area_per_combine_per_density.clear(); } diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index f9d6769606..1d9a621477 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -92,6 +92,9 @@ TreeSupport::TreeSupport(const SliceDataStorage& storage) { mesh.first.setActualZ(known_z); } + + placed_support_lines_support_areas = std::vector(storage.support.supportLayers.size(),Polygons()); + } void TreeSupport::generateSupportAreas(SliceDataStorage& storage) @@ -235,10 +238,8 @@ void TreeSupport::precalculate(const SliceDataStorage& storage, std::vector>& move_bounds, SliceDataStorage& storage) { - - TreeSupportTipGenerator tip_gen(storage,mesh,volumes_); - tip_gen.generateTips(storage,mesh,move_bounds,additional_required_support_area); - + TreeSupportTipGenerator tip_gen(storage, mesh, volumes_); + tip_gen.generateTips(storage, mesh, move_bounds, additional_required_support_area, placed_support_lines_support_areas); } void TreeSupport::mergeHelper @@ -1605,6 +1606,13 @@ void TreeSupport::generateBranchAreas(std::vectoruse_min_xy_dist; } + + for (Point target: elem->additional_ovalization_targets) + { + Point movement = (target - elem->result_on_layer); + movement_directions.emplace_back(movement, std::max(radius, config.support_line_width)); + } + } coord_t max_speed_sqd = 0; @@ -2033,6 +2041,8 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora } ); + + const auto t_end = std::chrono::high_resolution_clock::now(); const auto dur_union = 0.001 * std::chrono::duration_cast(t_union - t_start).count(); @@ -2057,8 +2067,7 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor support_layer_storage.size(), [&](const LayerIndex layer_idx) { - // ^^^ Most of the time in this function is this union call. It can take a relatively long time when a lot of areas are to be unioned. - // Also simplify a bit, to ensure the output does not contain outrageous amounts of vertices. Should not be necessary, just a precaution. + support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(placed_support_lines_support_areas[layer_idx]); // Subtract support lines of the branches from the roof storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(support_roof_storage[layer_idx]); diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 05ee13a5cb..5e2f1600fc 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -19,20 +19,23 @@ #include #include + + namespace cura { TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage, const SliceMeshStorage& mesh, TreeModelVolumes& volumes_s): config(mesh.settings), - support_roof_line_distance(mesh.settings.get("support_roof_line_distance")), - minimum_roof_area(mesh.settings.get("minimum_roof_area")), + use_fake_roof(!mesh.settings.get("support_roof_enable")), + minimum_roof_area(!use_fake_roof? mesh.settings.get("minimum_roof_area"): SUPPORT_TREE_MINIMUM_FAKE_ROOF_AREA), minimum_support_area(mesh.settings.get("minimum_support_area")), - support_roof_layers(mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), config.layer_height) : 0), + support_roof_layers(mesh.settings.get("support_roof_enable") ? round_divide(mesh.settings.get("support_roof_height"), config.layer_height) : use_fake_roof ? SUPPORT_TREE_MINIMUM_FAKE_ROOF_LAYERS : 0), connect_length((config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) + std::max(2 * config.min_radius - 1.0 * config.support_line_width, 0.0)), support_tree_branch_distance((config.support_pattern == EFillMethod::TRIANGLES ? 3 : (config.support_pattern == EFillMethod::GRID ? 2 : 1)) * connect_length), + support_roof_line_distance( use_fake_roof ? (config.support_pattern == EFillMethod::TRIANGLES ? 3 : (config.support_pattern == EFillMethod::GRID ? 2 : 1)) * (config.support_line_width * 100 / mesh.settings.get("support_tree_top_rate")) : mesh.settings.get("support_roof_line_distance")), //todo propper support_outset(mesh.settings.get("support_offset")), - roof_outset(mesh.settings.get("support_roof_offset")), - force_tip_to_roof((config.min_radius * config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && support_roof_layers), + roof_outset(use_fake_roof ? support_outset: mesh.settings.get("support_roof_offset")), + force_tip_to_roof((config.min_radius * config.min_radius * M_PI > minimum_roof_area * (1000 * 1000)) && support_roof_layers && !use_fake_roof), support_tree_limit_branch_reach(mesh.settings.get("support_tree_limit_branch_reach")), support_tree_branch_reach_limit(support_tree_limit_branch_reach ? mesh.settings.get("support_tree_branch_reach_limit") : 0), z_distance_delta(std::min(config.z_distance_top_layers+1,mesh.overhang_areas.size())), @@ -41,7 +44,8 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage already_inserted(mesh.overhang_areas.size()), support_roof_drawn(mesh.overhang_areas.size(),Polygons()), roof_tips_drawn(mesh.overhang_areas.size(),Polygons()), - volumes_(volumes_s) + volumes_(volumes_s), + force_minimum_roof_area(use_fake_roof || SUPPORT_TREE_MINIMUM_ROOF_AREA_HARD_LIMIT) { const double support_overhang_angle = mesh.settings.get("support_angle"); @@ -60,6 +64,23 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage known_z[z] = layer.printZ; } config.setActualZ(known_z); + + + size_t dtt_when_tips_can_merge = 1; + + if(config.branch_radius * config.diameter_angle_scale_factor < 2 * config.maximum_move_distance_slow) + { + while(2 * config.maximum_move_distance_slow * dtt_when_tips_can_merge < config.getRadius(dtt_when_tips_can_merge)) + { + dtt_when_tips_can_merge++; + } + } + else + { + dtt_when_tips_can_merge = config.tip_layers; // arbitrary default for when there is no guarantee that the while loop above will terminate + } + support_supporting_branch_distance = 2 * config.getRadius(dtt_when_tips_can_merge) + FUDGE_LENGTH; + } @@ -186,7 +207,7 @@ std::pair, std::vector>>, std::vector>>>(keep, set_free); } -Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points) const +Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& input, coord_t distance, size_t min_points, bool enforce_distance) const { Polygons result; for (auto part : input) @@ -237,10 +258,10 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& line.clear(); Point current_point = part[0]; line.add(part[0]); - if (min_points > 1 || vSize2(part[0] - part[optimal_end_index]) > (current_distance * current_distance)) - { - line.add(part[optimal_end_index]); - } + + bool should_add_endpoint = min_points > 1 || vSize2(part[0] - part[optimal_end_index]) > (current_distance * current_distance); + bool added_endpoint = !should_add_endpoint; // If no endpoint should be added all endpoints are already added. + size_t current_index = 0; GivenDistPoint next_point; coord_t next_distance = current_distance; @@ -248,15 +269,27 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& // (Regarding the while-loop) The input are lines, that means that the line from the last to the first vertex does not have to exist, so exclude all points that are on this line! while (PolygonUtils::getNextPointWithDistance(current_point, next_distance, part, current_index, 0, next_point) && next_point.pos < coord_t(part.size()) - 1) { + if (! added_endpoint && next_point.pos >= optimal_end_index) + { + current_index = optimal_end_index; + current_point = part[optimal_end_index]; + added_endpoint = true; + line.add(part[optimal_end_index]); + continue; + } + // Not every point that is distance away, is valid, as it may be much closer to another point. This is especially the case when the overhang is very thin. // So this ensures that the points are actually a certain distance from each other. // This assurance is only made on a per polygon basis, as different but close polygon may not be able to use support below the other polygon. coord_t min_distance_to_existing_point_sqd = std::numeric_limits::max(); - for (Point p : line) + if(enforce_distance) { - min_distance_to_existing_point_sqd = std::min(min_distance_to_existing_point_sqd, vSize2(p - next_point.location)); + for (Point p : line) + { + min_distance_to_existing_point_sqd = std::min(min_distance_to_existing_point_sqd, vSize2(p - next_point.location)); + } } - if (min_distance_to_existing_point_sqd >= (current_distance * current_distance)) + if (!enforce_distance || min_distance_to_existing_point_sqd >= (current_distance * current_distance)) { // viable point was found. Add to possible result. line.add(next_point.location); @@ -284,6 +317,12 @@ Polygons TreeSupportTipGenerator::ensureMaximumDistancePolyline(const Polygons& current_index = next_point.pos; } } + + if (! added_endpoint) + { + line.add(part[optimal_end_index]); + } + current_distance *= 0.9; } } @@ -513,7 +552,7 @@ void TreeSupportTipGenerator::calculateRoofAreas(const cura::SliceMeshStorage& m } -void TreeSupportTipGenerator::addPointAsInfluenceArea(std::vector>& move_bounds, std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation) +void TreeSupportTipGenerator::addPointAsInfluenceArea(std::vector>& move_bounds, std::pair p, size_t dtt, LayerIndex insert_layer, size_t dont_move_until, bool roof, bool skip_ovalisation, std::vector additional_ovalization_targets) { const bool to_bp = p.second == LineStatus::TO_BP || p.second == LineStatus::TO_BP_SAFE; const bool gracious = to_bp || p.second == LineStatus::TO_MODEL_GRACIOUS || p.second == LineStatus::TO_MODEL_GRACIOUS_SAFE; @@ -554,13 +593,19 @@ void TreeSupportTipGenerator::addPointAsInfluenceArea(std::vectorarea = new Polygons(area); + + for (Point p : additional_ovalization_targets) + { + elem->additional_ovalization_targets.emplace_back(p); + } + move_bounds[insert_layer].emplace(elem); } } } -void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until) +void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector>& move_bounds, std::vector lines, size_t roof_tip_layers, LayerIndex insert_layer_idx, bool supports_roof, size_t dont_move_until, bool connect_points) { // Add tip area as roof (happens when minimum roof area > minimum tip area) if possible. This is required as there is no guarantee that if support_roof_wall_count == 0 that a certain roof area will actually have lines. size_t dtt_roof_tip = 0; @@ -629,9 +674,21 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector EPSILON; - for (auto point_data : line) + const bool disable_ovalization = !connect_points && config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0 && line.size() > EPSILON; + for (auto [idx, point_data] : line | ranges::views::enumerate) { + std::vector additional_ovalization_targets; + if (connect_points && config.getRadius(1) < 1.5 * config.support_line_width) // If the radius is to large then the ovalization would cause the area to float in the air. + { + if (idx != 0) + { + additional_ovalization_targets.emplace_back(line[idx - 1].first); + } + if (idx != line.size() - 1) + { + additional_ovalization_targets.emplace_back(line[idx + 1].first); + } + } addPointAsInfluenceArea ( move_bounds, @@ -639,7 +696,8 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector dtt_roof_tip ? dont_move_until - dtt_roof_tip : 0, - dtt_roof_tip != 0 || supports_roof, disable_ovalistation + dtt_roof_tip != 0 || supports_roof, disable_ovalization, + additional_ovalization_targets ); } } @@ -648,6 +706,7 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector>& move_bounds,SliceDataStorage& storage, std::vector& additional_support_areas) { + cura::parallel_for ( 0, @@ -658,8 +717,8 @@ void TreeSupportTipGenerator::removeUselessAddedPoints(std::vector to_be_removed; - Polygons roof_on_layer_above = storage.support.supportLayers[layer_idx+1].support_roof.unionPolygons(layer_idx+1 < storage.support.supportLayers.size() ? additional_support_areas[layer_idx+1]:Polygons()); - Polygons roof_on_layer = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(layer_idx < storage.support.supportLayers.size() ? additional_support_areas[layer_idx]:Polygons()); + Polygons roof_on_layer_above = use_fake_roof ? support_roof_drawn[layer_idx+1] : storage.support.supportLayers[layer_idx+1].support_roof.unionPolygons(additional_support_areas[layer_idx+1]); + Polygons roof_on_layer = use_fake_roof ? support_roof_drawn[layer_idx] : storage.support.supportLayers[layer_idx].support_roof.unionPolygons(additional_support_areas[layer_idx]); for (TreeSupportElement* elem : move_bounds[layer_idx]) { @@ -694,8 +753,10 @@ void TreeSupportTipGenerator::removeUselessAddedPoints(std::vector>& move_bounds, std::vector& additional_support_areas) +void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const SliceMeshStorage& mesh, std::vector>& move_bounds, std::vector& additional_support_areas, std::vector& placed_support_lines_support_areas) { + std::vector> new_tips(move_bounds.size()); + const coord_t circle_length_to_half_linewidth_change = config.min_radius < config.support_line_width ? config.min_radius / 2 : sqrt(square(config.min_radius) - square(config.min_radius - config.support_line_width / 2)); // ^^^ As r*r=x*x+y*y (circle equation): If a circle with center at (0,0) the top most point is at (0,r) as in y=r. This calculates how far one has to move on the x-axis so that y=r-support_line_width/2. // In other words how far does one need to move on the x-axis to be support_line_width/2 away from the circle line. As a circle is round this length is identical for every axis as long as the 90� angle between both remains. @@ -727,11 +788,10 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice std::function generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) { - return TreeSupportUtils::generateSupportInfillLines(area, config, roof, layer_idx, roof ? support_roof_line_distance : support_tree_branch_distance, cross_fill_provider, roof); + return TreeSupportUtils::generateSupportInfillLines(area, config, roof && !use_fake_roof, layer_idx, roof ? support_roof_line_distance : support_tree_branch_distance, cross_fill_provider, roof && !use_fake_roof); }; - std::vector> overhang_processing; // ^^^ Every overhang has saved if a roof should be generated for it. // This can NOT be done in the for loop as an area may NOT have a roof even if it is larger than the minimum_roof_area when it is only larger because of the support horizontal expansion and it would not have a roof if the overhang is offset by support roof horizontal expansion instead. @@ -783,7 +843,7 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice { std::vector overhang_lines; - Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang_part, false, layer_idx), config.min_radius, 1); + Polygons polylines = ensureMaximumDistancePolyline(generateLines(remaining_overhang_part, false, layer_idx), config.min_radius, 1, false); // ^^^ Support_line_width to form a line here as otherwise most will be unsupported. // Technically this violates branch distance, but not only is this the only reasonable choice, // but it ensures consistent behavior as some infill patterns generate each line segment as its own polyline part causing a similar line forming behavior. @@ -791,7 +851,7 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice if (polylines.pointCount() <= 3) { // Add the outer wall to ensure it is correct supported instead. - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(remaining_overhang_part), connect_length, 3); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(remaining_overhang_part), connect_length, 3, true); } for (auto line : polylines) @@ -827,7 +887,7 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice std::vector fresh_valid_points = convertLinesToInternal(convertInternalToLines(split.second), layer_idx - lag_ctr); // ^^^ Set all now valid lines to their correct LineStatus. Easiest way is to just discard Avoidance information for each point and evaluate them again. - addLinesAsInfluenceAreas(move_bounds,fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, support_roof_layers); + addLinesAsInfluenceAreas(new_tips,fresh_valid_points, (force_tip_to_roof && lag_ctr <= support_roof_layers) ? support_roof_layers : 0, layer_idx - lag_ctr, false, support_roof_layers, false); } } } @@ -846,11 +906,14 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice const size_t min_support_points = std::max(coord_t(1), std::min(coord_t(EPSILON), overhang_outset.polygonLength() / connect_length)); std::vector overhang_lines; + bool only_lines = true; + // The tip positions are determined here. + // todo can cause inconsistent support density if a line exactly aligns with the model Polygons polylines = ensureMaximumDistancePolyline ( - generateLines(overhang_outset, roof_allowed_for_this_part, layer_idx + roof_allowed_for_this_part), !roof_allowed_for_this_part ? config.min_radius / 2 : connect_length, 1 + generateLines(overhang_outset, roof_allowed_for_this_part, layer_idx + roof_allowed_for_this_part), !roof_allowed_for_this_part ? config.min_radius * 2 : use_fake_roof ? support_supporting_branch_distance : connect_length , 1, false ); @@ -861,6 +924,7 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice if (polylines.pointCount() <= min_support_points) { + only_lines = false; // Add the outer wall (of the overhang) to ensure it is correct supported instead. // Try placing the support points in a way that they fully support the outer wall, instead of just the with half of the support line width. Polygons reduced_overhang_outset = overhang_outset.offset(-config.support_line_width / 2.2); @@ -868,11 +932,11 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice // (If this is not done it may only be half). This WILL NOT be the case when supporting an angle of about < 60� so there is a fallback, as some support is better than none.) if (! reduced_overhang_outset.empty() && overhang_outset.difference(reduced_overhang_outset.offset(std::max(config.support_line_width, connect_length))).area() < 1) { - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(reduced_overhang_outset), connect_length, min_support_points); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(reduced_overhang_outset), connect_length, min_support_points, true); } else { - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(overhang_outset), connect_length, min_support_points); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(overhang_outset), connect_length, min_support_points, true); } } @@ -886,7 +950,7 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice if(overhang_lines.empty()) //some error handling and logging { Polygons enlarged_overhang_outset = overhang_outset.offset(config.getRadius(0)+FUDGE_LENGTH/2,ClipperLib::jtRound).difference(relevant_forbidden); - polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(enlarged_overhang_outset), connect_length, min_support_points); + polylines = ensureMaximumDistancePolyline(TreeSupportUtils::toPolylines(enlarged_overhang_outset), connect_length, min_support_points, true); overhang_lines = convertLinesToInternal(polylines, layer_idx ); if(!overhang_lines.empty()) @@ -897,55 +961,65 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice { spdlog::warn("Overhang area has no valid tips! Was roof: {} On Layer: {}", roof_allowed_for_this_part,layer_idx); } - } - size_t dont_move_for_layers = support_roof_layers ? (force_tip_to_roof ? support_roof_layers : (roof_allowed_for_this_part?0:support_roof_layers)):0; - addLinesAsInfluenceAreas(move_bounds, overhang_lines, force_tip_to_roof ? support_roof_layers : 0, layer_idx, roof_allowed_for_this_part, dont_move_for_layers); - + size_t dont_move_for_layers = support_roof_layers ? (force_tip_to_roof ? support_roof_layers : (roof_allowed_for_this_part ? 0 : support_roof_layers)) : 0; + addLinesAsInfluenceAreas(new_tips, overhang_lines, force_tip_to_roof ? support_roof_layers : 0, layer_idx, roof_allowed_for_this_part, dont_move_for_layers, only_lines); } } ); - cura::parallel_for ( 0, support_roof_drawn.size(), [&](const LayerIndex layer_idx) { - // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough. - //If there is a roof could have zero lines in its area (as it has no wall), rand a support area would very likely be printed (because there are walls for the support areas), replace non printable roofs with support - if (config.support_wall_count>0 && config.support_roof_wall_count==0) + // Sometimes roofs could be empty as the pattern does not generate lines if the area is narrow enough. + // If there is a roof could have zero lines in its area (as it has no wall), rand a support area would very likely be printed (because there are walls for the support areas), replace non printable roofs with support + if (! use_fake_roof && config.support_wall_count > 0 && config.support_roof_wall_count == 0) + { + for (auto roof_area : support_roof_drawn[layer_idx].unionPolygons(roof_tips_drawn[layer_idx]).splitIntoParts()) { - for (auto roof_area:support_roof_drawn[layer_idx].unionPolygons(roof_tips_drawn[layer_idx]).splitIntoParts()) + // technically there is no guarantee that a drawn roof tip has lines, as it could be unioned with another roof area that has, but this has to be enough hopefully. + if (layer_idx < additional_support_areas.size() && TreeSupportUtils::generateSupportInfillLines(roof_area, config, true, layer_idx, support_roof_line_distance, cross_fill_provider, false).empty()) { - //technically there is no guarantee that a drawn roof tip has lines, as it could be unioned with another roof area that has, but this has to be enough hopefully. - if(layer_idx < additional_support_areas.size() && TreeSupportUtils::generateSupportInfillLines(roof_area, config, true, layer_idx, support_roof_line_distance, cross_fill_provider, true).empty()) - { - additional_support_areas[layer_idx].add(roof_area); - } - else - { - storage.support.supportLayers[layer_idx].support_roof.add(roof_area); - } + additional_support_areas[layer_idx].add(roof_area); } + else + { + storage.support.supportLayers[layer_idx].support_roof.add(roof_area); + } + } - additional_support_areas[layer_idx]= additional_support_areas[layer_idx].unionPolygons(); - storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(); - + additional_support_areas[layer_idx] = additional_support_areas[layer_idx].unionPolygons(); + storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(); + } + else + { + if (use_fake_roof) + { + for (auto part : support_roof_drawn[layer_idx].splitIntoParts()) + { + storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, 0, support_roof_line_distance); + } + placed_support_lines_support_areas[layer_idx].add( + TreeSupportUtils::generateSupportInfillLines(support_roof_drawn[layer_idx], config, false, layer_idx, support_roof_line_distance, cross_fill_provider, false).offsetPolyLine(config.support_line_width / 2)); } else { storage.support.supportLayers[layer_idx].support_roof.add(support_roof_drawn[layer_idx]); storage.support.supportLayers[layer_idx].support_roof = storage.support.supportLayers[layer_idx].support_roof.unionPolygons(roof_tips_drawn[layer_idx]); } + } + }); - } - ); - - removeUselessAddedPoints(move_bounds,storage,additional_support_areas); + removeUselessAddedPoints(new_tips ,storage , additional_support_areas); + for (auto [layer_idx, tips_on_layer] : new_tips | ranges::views::enumerate) + { + move_bounds[layer_idx].insert(tips_on_layer.begin(), tips_on_layer.end()); + } } diff --git a/src/support.cpp b/src/support.cpp index 59d6723623..2fc782c93c 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -222,7 +222,7 @@ void AreaSupport::generateGradualSupport(SliceDataStorage& storage) continue; } // NOTE: This both generates the walls _and_ returns the _actual_ infill area (the one _without_ walls) for use in the rest of the method. - const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, wall_count, wall_width, 0, infill_extruder.settings, layer_nr, SectionType::SUPPORT); + const Polygons infill_area = Infill::generateWallToolPaths(support_infill_part.wall_toolpaths, original_area, support_infill_part.inset_count_to_generate, wall_width, 0, infill_extruder.settings, layer_nr, SectionType::SUPPORT); const AABB& this_part_boundary_box = support_infill_part.outline_boundary_box; // calculate density areas for this island From bb5380a74087d2a00faceb794f43cf56e623d47e Mon Sep 17 00:00:00 2001 From: Thomas Rahm <67757218+ThomasRahm@users.noreply.github.com> Date: Wed, 19 Apr 2023 15:25:35 +0200 Subject: [PATCH 4/5] Improved support of high density roof structures by placing tips to form a lower density structure below it instead of placing tips directly below the rooflines (which could cause tip-areas to merge, causing nothing to be supported) --- include/TreeSupportUtils.h | 5 +++-- src/TreeSupport.cpp | 2 +- src/TreeSupportTipGenerator.cpp | 20 ++++++++++++-------- 3 files changed, 16 insertions(+), 11 deletions(-) diff --git a/include/TreeSupportUtils.h b/include/TreeSupportUtils.h index 7a72316cb9..2f6378dc20 100644 --- a/include/TreeSupportUtils.h +++ b/include/TreeSupportUtils.h @@ -92,14 +92,15 @@ class TreeSupportUtils * \param support_infill_distance[in] The distance that should be between the infill lines. * \param cross_fill_provider[in] A SierpinskiFillProvider required for cross infill. * \param include_walls[in] If the result should also contain walls, or only the infill. + * todo doku * \return A Polygons object that represents the resulting infill lines. */ - [[nodiscard]] static Polygons generateSupportInfillLines(const Polygons& area,const TreeSupportSettings& config, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider, bool include_walls) + [[nodiscard]] static Polygons generateSupportInfillLines(const Polygons& area,const TreeSupportSettings& config, bool roof, LayerIndex layer_idx, coord_t support_infill_distance, SierpinskiFillProvider* cross_fill_provider, bool include_walls, bool generate_support_supporting = false) { Polygons gaps; // As we effectivly use lines to place our supportPoints we may use the Infill class for it, while not made for it, it works perfectly. - const EFillMethod pattern = roof ? config.roof_pattern : config.support_pattern; + const EFillMethod pattern = generate_support_supporting ? EFillMethod::GRID : roof ? config.roof_pattern : config.support_pattern; const bool zig_zaggify_infill = roof ? pattern == EFillMethod::ZIG_ZAG : config.zig_zaggify_support; const bool connect_polygons = false; diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index 1d9a621477..5c7585cfbb 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -1970,7 +1970,7 @@ void TreeSupport::filterFloatingLines(std::vector& support_layer_stora for (auto [idx2, hole2] : holeparts[layer_idx - 1] | ranges::views::enumerate) { - if (AABB(hole).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 + 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_rest_map[layer_idx][idx].emplace_back(idx2); } diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 5e2f1600fc..8f8c5af516 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -66,11 +66,11 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage config.setActualZ(known_z); - size_t dtt_when_tips_can_merge = 1; + coord_t dtt_when_tips_can_merge = 1; if(config.branch_radius * config.diameter_angle_scale_factor < 2 * config.maximum_move_distance_slow) { - while(2 * config.maximum_move_distance_slow * dtt_when_tips_can_merge < config.getRadius(dtt_when_tips_can_merge)) + while((2 * config.maximum_move_distance_slow * dtt_when_tips_can_merge - config.support_line_width) < config.getRadius(dtt_when_tips_can_merge)) { dtt_when_tips_can_merge++; } @@ -79,8 +79,7 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage { dtt_when_tips_can_merge = config.tip_layers; // arbitrary default for when there is no guarantee that the while loop above will terminate } - support_supporting_branch_distance = 2 * config.getRadius(dtt_when_tips_can_merge) + FUDGE_LENGTH; - + support_supporting_branch_distance = 2 * config.getRadius(dtt_when_tips_can_merge) + config.support_line_width + FUDGE_LENGTH; } @@ -673,12 +672,12 @@ void TreeSupportTipGenerator::addLinesAsInfluenceAreas(std::vector EPSILON; + // Ovalisation should be disabled if they may be placed close to each other to prevent tip-areas merging. If the tips has to turn into roof, the area is most likely not large enough for this to cause issues. + const bool disable_ovalization = !connect_points && config.min_radius < 3 * config.support_line_width && roof_tip_layers == 0 && dtt_roof_tip == 0; for (auto [idx, point_data] : line | ranges::views::enumerate) { std::vector additional_ovalization_targets; - if (connect_points && config.getRadius(1) < 1.5 * config.support_line_width) // If the radius is to large then the ovalization would cause the area to float in the air. + if (connect_points) // If the radius is to large then the ovalization would cause the area to float in the air. { if (idx != 0) { @@ -788,7 +787,12 @@ void TreeSupportTipGenerator::generateTips(SliceDataStorage& storage,const Slice std::function generateLines = [&](const Polygons& area, bool roof, LayerIndex layer_idx) { - return TreeSupportUtils::generateSupportInfillLines(area, config, roof && !use_fake_roof, layer_idx, roof ? support_roof_line_distance : support_tree_branch_distance, cross_fill_provider, roof && !use_fake_roof); + + coord_t upper_line_distance = support_supporting_branch_distance; + coord_t line_distance = std::max(roof ? support_roof_line_distance : support_tree_branch_distance, upper_line_distance ); + + + return TreeSupportUtils::generateSupportInfillLines(area, config, roof && !use_fake_roof, layer_idx, line_distance , cross_fill_provider, roof && !use_fake_roof, line_distance == upper_line_distance); }; From 177b12c9767f544c8ab34457ea2371472af7a430 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 20 Apr 2023 16:41:54 +0200 Subject: [PATCH 5/5] Fix division by zero CURA-10465 --- src/TreeSupportTipGenerator.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 05ee13a5cb..fd72adddde 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -46,10 +46,18 @@ TreeSupportTipGenerator::TreeSupportTipGenerator(const SliceDataStorage& storage const double support_overhang_angle = mesh.settings.get("support_angle"); const coord_t max_overhang_speed = (support_overhang_angle < TAU / 4) ? (coord_t)(tan(support_overhang_angle) * config.layer_height) : std::numeric_limits::max(); - max_overhang_insert_lag = std::max((size_t)round_up_divide(config.xy_distance, max_overhang_speed / 2), 2 * config.z_distance_top_layers); - // ^^^ Cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. - // Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. - // The 2*z_distance_delta is only a catch for when the support angle is very high. + + if (max_overhang_speed == 0) + { + max_overhang_insert_lag = std::numeric_limits::max(); + } + else + { + max_overhang_insert_lag = std::max((size_t)round_up_divide(config.xy_distance, max_overhang_speed / 2), 2 * config.z_distance_top_layers); + // ^^^ Cap for how much layer below the overhang a new support point may be added, as other than with regular support every new inserted point may cause extra material and time cost. + // Could also be an user setting or differently calculated. Idea is that if an overhang does not turn valid in double the amount of layers a slope of support angle would take to travel xy_distance, nothing reasonable will come from it. + // The 2*z_distance_delta is only a catch for when the support angle is very high. + } cross_fill_provider = generateCrossFillProvider(mesh, support_tree_branch_distance, config.support_line_width);