diff --git a/.clang-format b/.clang-format index 5d90caa166..e5f6fc2257 100644 --- a/.clang-format +++ b/.clang-format @@ -59,11 +59,11 @@ ForEachMacros: IncludeBlocks: Regroup IncludeCategories: - Priority: 2 - Regex: ^<(scripta|spdlog|range|fmt|Arcus|agrpc|grpc|boost)/ + Regex: '^<(scripta|spdlog|range|fmt|Arcus|agrpc|grpc|boost)/.*' - Priority: 3 - Regex: ^(<|"(gtest|gmock|isl|json)/) + Regex: '^((<|")(gtest|gmock|isl|json)/)' - Priority: 1 - Regex: .* + Regex: '^<.*' IncludeIsMainRegex: (Test)?$ IndentCaseLabels: false IndentWidth: 4 diff --git a/conanfile.py b/conanfile.py index dea1860300..b498c3b84d 100644 --- a/conanfile.py +++ b/conanfile.py @@ -40,7 +40,7 @@ class CuraEngineConan(ConanFile): def set_version(self): if not self.version: - self.version = "5.5.0-beta.1" + self.version = "5.6.0-alpha" def export_sources(self): copy(self, "CMakeLists.txt", self.recipe_folder, self.export_sources_folder) diff --git a/include/FffGcodeWriter.h b/include/FffGcodeWriter.h index 61363895fb..c38beba261 100644 --- a/include/FffGcodeWriter.h +++ b/include/FffGcodeWriter.h @@ -4,6 +4,9 @@ #ifndef GCODE_WRITER_H #define GCODE_WRITER_H +#include +#include + #include "ExtruderUse.h" #include "FanSpeedLayerTime.h" #include "LayerPlanBuffer.h" @@ -13,9 +16,6 @@ #include "utils/ExtrusionLine.h" //Processing variable-width paths. #include "utils/NoCopy.h" -#include -#include - namespace cura { @@ -570,8 +570,7 @@ class FffGcodeWriter : public NoCopy const Ratio skin_density, const bool monotonic, bool& added_something, - double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, - const bool is_bridge_skin = false) const; + double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT) const; /*! * see if we can avoid printing a lines or zig zag style skin part in multiple segments by moving to @@ -636,10 +635,12 @@ class FffGcodeWriter : public NoCopy * layer. * * \param[in] storage Where the slice data is stored. + * \param[in] support_roof_outlines which polygons to generate roofs for -- originally split-up because of fractional (layer-height) layers + * \param[in] current_roof_config config to be used -- most importantly, support has slightly different configs for fractional (layer-height) layers * \param gcodeLayer The initial planning of the g-code of the layer. * \return Whether any support skin was added to the layer plan. */ - bool addSupportRoofsToGCode(const SliceDataStorage& storage, LayerPlan& gcodeLayer) const; + bool addSupportRoofsToGCode(const SliceDataStorage& storage, const Polygons& support_roof_outlines, const GCodePathConfig& current_roof_config, LayerPlan& gcode_layer) const; /*! * Add the support bottoms to the layer plan \p gcodeLayer of the current diff --git a/include/GCodePathConfig.h b/include/GCodePathConfig.h index 1ee2003195..4531b650aa 100644 --- a/include/GCodePathConfig.h +++ b/include/GCodePathConfig.h @@ -18,6 +18,7 @@ namespace cura */ struct GCodePathConfig { + coord_t z_offset{}; // -#include - #include "InsetOrderOptimizer.h" // for makeOrderIncludeTransitive #include "PathOrdering.h" #include "pathPlanning/CombPath.h" //To calculate the combing distance if we want to use combing. @@ -16,12 +13,17 @@ #include "utils/linearAlg2D.h" //To find the angle of corners to hide seams. #include "utils/polygonUtils.h" #include "utils/views/dfs.h" -#include -#include + #include #include -#include #include +#include +#include +#include +#include +#include + +#include namespace cura { @@ -99,10 +101,17 @@ class PathOrderOptimizer * it into a polygon. * \param combing_boundary Boundary to avoid when making travel moves. */ - PathOrderOptimizer(const Point start_point, const ZSeamConfig seam_config = ZSeamConfig(), const bool detect_loops = false, const Polygons* combing_boundary = nullptr, const bool reverse_direction = false, const std::unordered_multimap& order_requirements = no_order_requirements, const bool group_outer_walls = false) + PathOrderOptimizer( + const Point start_point, + const ZSeamConfig seam_config = ZSeamConfig(), + const bool detect_loops = false, + const Polygons* combing_boundary = nullptr, + const bool reverse_direction = false, + const std::unordered_multimap& order_requirements = no_order_requirements, + const bool group_outer_walls = false) : start_point(start_point) , seam_config(seam_config) - , combing_boundary((combing_boundary != nullptr && !combing_boundary->empty()) ? combing_boundary : nullptr) + , combing_boundary((combing_boundary != nullptr && ! combing_boundary->empty()) ? combing_boundary : nullptr) , detect_loops(detect_loops) , reverse_direction(reverse_direction) , order_requirements(&order_requirements) @@ -136,72 +145,72 @@ class PathOrderOptimizer * This reorders the \ref paths field and fills their starting vertices and * directions. */ - void optimize() + void optimize(bool precompute_start = true) { - if(paths.empty()) + if (paths.empty()) { return; } - //Get the vertex data and store it in the paths. - for(auto& path : paths) + // Get the vertex data and store it in the paths. + for (auto& path : paths) { path.converted = path.getVertexData(); vertices_to_paths.emplace(path.vertices, &path); } - //If necessary, check polylines to see if they are actually polygons. - if(detect_loops) + // If necessary, check polylines to see if they are actually polygons. + if (detect_loops) { - for(auto& path : paths) + for (auto& path : paths) { - if(!path.is_closed) + if (! path.is_closed) { - //If we want to detect chains, first check if some of the polylines are secretly polygons. - path.is_closed = isLoopingPolyline(path); //If it is, we'll set the seam position correctly later. + // If we want to detect chains, first check if some of the polylines are secretly polygons. + path.is_closed = isLoopingPolyline(path); // If it is, we'll set the seam position correctly later. } } } - - //Add all vertices to a bucket grid so that we can find nearby endpoints quickly. + + // Add all vertices to a bucket grid so that we can find nearby endpoints quickly. const coord_t snap_radius = 10_mu; // 0.01mm grid cells. Chaining only needs to consider polylines which are next to each other. SparsePointGridInclusive line_bucket_grid(snap_radius); - for(const auto& [i, path]: paths | ranges::views::enumerate) + for (const auto& [i, path] : paths | ranges::views::enumerate) { if (path.converted->empty()) { continue; } - if(path.is_closed) + if (path.is_closed) { - for(const Point& point : *path.converted) + for (const Point& point : *path.converted) { - line_bucket_grid.insert(point, i); //Store by index so that we can also mark them down in the `picked` vector. + line_bucket_grid.insert(point, i); // Store by index so that we can also mark them down in the `picked` vector. } } - else //For polylines, only insert the endpoints. Those are the only places we can start from so the only relevant vertices to be near to. + else // For polylines, only insert the endpoints. Those are the only places we can start from so the only relevant vertices to be near to. { line_bucket_grid.insert(path.converted->front(), i); line_bucket_grid.insert(path.converted->back(), i); } } - //For some Z seam types the start position can be pre-computed. - //This is faster since we don't need to re-compute the start position at each step then. - const bool precompute_start = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; - if(precompute_start) + // For some Z seam types the start position can be pre-computed. + // This is faster since we don't need to re-compute the start position at each step then. + precompute_start &= seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; + if (precompute_start) { - for(auto& path : paths) + for (auto& path : paths) { - if(!path.is_closed || path.converted->empty()) + if (! path.is_closed || path.converted->empty()) { - continue; //Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. + continue; // Can't pre-compute the seam for open polylines since they're at the endpoint nearest to the current position. } path.start_vertex = findStartLocation(path, seam_config.pos); } } - std::vector optimized_order; //To store our result in. At the end we'll std::swap. + std::vector optimized_order; // To store our result in. At the end we'll std::swap. if (order_requirements->empty()) { @@ -213,9 +222,9 @@ class PathOrderOptimizer } - if(reverse_direction && order_requirements->empty()) + if (reverse_direction && order_requirements->empty()) { - std::vector reversed = reverseOrderPaths(optimized_order); //Reverse-insert the optimized order, to invert the ordering. + std::vector reversed = reverseOrderPaths(optimized_order); // Reverse-insert the optimized order, to invert the ordering. std::swap(reversed, paths); } else @@ -225,6 +234,7 @@ class PathOrderOptimizer combing_grid.reset(); } + protected: /*! * If \ref detect_loops is enabled, endpoints of polylines that are closer @@ -276,18 +286,24 @@ class PathOrderOptimizer std::vector getOptimizedOrder(SparsePointGridInclusive line_bucket_grid, size_t snap_radius) { - std::vector optimized_order; //To store our result in. + std::vector optimized_order; // To store our result in. Point current_position = start_point; - std::unordered_map picked(paths.size()); //Fixed size boolean flag for whether each path is already in the optimized vector. + std::unordered_map picked(paths.size()); // Fixed size boolean flag for whether each path is already in the optimized vector. - auto isPicked = [&picked](OrderablePath* c) { return picked[c]; }; - auto notPicked = [&picked](OrderablePath* c) { return !picked[c]; }; + auto isPicked = [&picked](OrderablePath* c) + { + return picked[c]; + }; + auto notPicked = [&picked](OrderablePath* c) + { + return ! picked[c]; + }; - while(optimized_order.size() < paths.size()) + while (optimized_order.size() < paths.size()) { - //Use bucket grid to find paths within snap_radius + // Use bucket grid to find paths within snap_radius std::vector nearby_candidates; for (const auto i : line_bucket_grid.getNearbyVals(current_position, snap_radius)) { @@ -296,14 +312,14 @@ class PathOrderOptimizer std::vector available_candidates; available_candidates.reserve(nearby_candidates.size()); - for(auto candidate : nearby_candidates | ranges::views::filter(notPicked)) + for (auto candidate : nearby_candidates | ranges::views::filter(notPicked)) { available_candidates.push_back(candidate); } - if(available_candidates.empty()) // We need to broaden our search through all candidates + if (available_candidates.empty()) // We need to broaden our search through all candidates { - for(auto path : paths | ranges::views::addressof | ranges::views::filter(notPicked)) + for (auto path : paths | ranges::views::addressof | ranges::views::filter(notPicked)) { available_candidates.push_back(path); } @@ -315,15 +331,15 @@ class PathOrderOptimizer optimized_order.push_back(*best_path); picked[best_path] = true; - if(!best_path->converted->empty()) //If all paths were empty, the best path is still empty. We don't upate the current position then. + if (! best_path->converted->empty()) // If all paths were empty, the best path is still empty. We don't upate the current position then. { - if(best_path->is_closed) + if (best_path->is_closed) { - current_position = (*best_path->converted)[best_path->start_vertex]; //We end where we started. + current_position = (*best_path->converted)[best_path->start_vertex]; // We end where we started. } else { - //Pick the other end from where we started. + // Pick the other end from where we started. current_position = best_path->start_vertex == 0 ? best_path->converted->back() : best_path->converted->front(); } } @@ -332,9 +348,10 @@ class PathOrderOptimizer return optimized_order; } - std::vector getOptimizerOrderWithConstraints(SparsePointGridInclusive line_bucket_grid, size_t snap_radius, const std::unordered_multimap& order_requirements) + std::vector + getOptimizerOrderWithConstraints(SparsePointGridInclusive line_bucket_grid, size_t snap_radius, const std::unordered_multimap& order_requirements) { - std::vector optimized_order; //To store our result in. + std::vector optimized_order; // To store our result in. // initialize the roots set with all possible nodes std::unordered_set roots; @@ -358,8 +375,8 @@ class PathOrderOptimizer std::unordered_set visited; Point current_position = start_point; - std::function(const Path, const std::unordered_multimap&)> get_neighbours = - [current_position, this](const Path current_node, const std::unordered_multimap& graph) + std::function(const Path, const std::unordered_multimap&)> get_neighbours + = [current_position, this](const Path current_node, const std::unordered_multimap& graph) { std::vector order; // Output order to traverse neighbors @@ -382,13 +399,13 @@ class PathOrderOptimizer // update local_current_position auto path = vertices_to_paths[best_candidate]; - if(path->is_closed) + if (path->is_closed) { - local_current_position = (*path->converted)[path->start_vertex]; //We end where we started. + local_current_position = (*path->converted)[path->start_vertex]; // We end where we started. } else { - //Pick the other end from where we started. + // Pick the other end from where we started. local_current_position = path->start_vertex == 0 ? path->converted->back() : path->converted->front(); } } @@ -396,34 +413,33 @@ class PathOrderOptimizer return order; }; - const std::function handle_node = - [¤t_position, &optimized_order, this] - (const Path current_node, const std::nullptr_t _state) + const std::function handle_node + = [¤t_position, &optimized_order, this](const Path current_node, const std::nullptr_t _state) + { + // We should make map from node <-> path for this stuff + for (auto& path : paths) { - // We should make map from node <-> path for this stuff - for (auto& path : paths) + if (path.vertices == current_node) { - if (path.vertices == current_node) + if (path.is_closed) + { + current_position = (*path.converted)[path.start_vertex]; // We end where we started. + } + else { - if(path.is_closed) - { - current_position = (*path.converted)[path.start_vertex]; //We end where we started. - } - else - { - //Pick the other end from where we started. - current_position = path.start_vertex == 0 ? path.converted->back() : path.converted->front(); - } - - // Add to optimized order - optimized_order.push_back(path); - - break; + // Pick the other end from where we started. + current_position = path.start_vertex == 0 ? path.converted->back() : path.converted->front(); } + + // Add to optimized order + optimized_order.push_back(path); + + break; } + } - return nullptr; - }; + return nullptr; + }; if (group_outer_walls) { @@ -482,7 +498,7 @@ class PathOrderOptimizer } else { - while (!roots.empty()) + while (! roots.empty()) { Path root = findClosestPathVertices(current_position, roots); roots.erase(root); @@ -497,13 +513,13 @@ class PathOrderOptimizer std::vector reverseOrderPaths(std::vector pathsOrderPaths) { std::vector reversed; - //Don't replace with swap, assign or insert. They require functions that we can't implement for all template arguments for Path. + // Don't replace with swap, assign or insert. They require functions that we can't implement for all template arguments for Path. reversed.reserve(pathsOrderPaths.size()); - for(auto& path: pathsOrderPaths | ranges::views::reverse) + for (auto& path : pathsOrderPaths | ranges::views::reverse) { reversed.push_back(path); - reversed.back().backwards = !reversed.back().backwards; - if(!reversed.back().is_closed) + reversed.back().backwards = ! reversed.back().backwards; + if (! reversed.back().is_closed) { reversed.back().start_vertex = reversed.back().converted->size() - 1 - reversed.back().start_vertex; } @@ -530,33 +546,35 @@ class PathOrderOptimizer coord_t best_distance2 = std::numeric_limits::max(); OrderablePath* best_candidate = 0; - for(OrderablePath* path : candidate_paths) + for (OrderablePath* path : candidate_paths) { - if(path->converted->empty()) //No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. + if (path->converted->empty()) // No vertices in the path. Can't find the start position then or really plan it in. Put that at the end. { - if(best_distance2 == std::numeric_limits::max()) + if (best_distance2 == std::numeric_limits::max()) { best_candidate = path; } continue; } - const bool precompute_start = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; - if(!path->is_closed || !precompute_start) //Find the start location unless we've already precomputed it. + const bool precompute_start + = seam_config.type == EZSeamType::RANDOM || seam_config.type == EZSeamType::USER_SPECIFIED || seam_config.type == EZSeamType::SHARPEST_CORNER; + if (! path->is_closed || ! precompute_start) // Find the start location unless we've already precomputed it. { path->start_vertex = findStartLocation(*path, start_position); - if(!path->is_closed) //Open polylines start at vertex 0 or vertex N-1. Indicate that they should be reversed if they start at N-1. + if (! path->is_closed) // Open polylines start at vertex 0 or vertex N-1. Indicate that they should be reversed if they start at N-1. { path->backwards = path->start_vertex > 0; } } const Point candidate_position = (*path->converted)[path->start_vertex]; coord_t distance2 = getDirectDistance(start_position, candidate_position); - if(distance2 < best_distance2 && combing_boundary) //If direct distance is longer than best combing distance, the combing distance can never be better, so only compute combing if necessary. + if (distance2 < best_distance2 + && combing_boundary) // If direct distance is longer than best combing distance, the combing distance can never be better, so only compute combing if necessary. { distance2 = getCombingDistance(start_position, candidate_position); } - if(distance2 < best_distance2) //Closer than the best candidate so far. + if (distance2 < best_distance2) // Closer than the best candidate so far. { best_candidate = path; best_distance2 = distance2; @@ -589,47 +607,59 @@ class PathOrderOptimizer * applicable. * \param is_closed Whether the polygon is closed (a polygon) or not * (a polyline). If the path is not closed, it will choose between the two - * endpoints rather than + * endpoints rather than * \return An index to a vertex in that path where printing must start. */ size_t findStartLocation(const OrderablePath& path, const Point& target_pos) { - if(!path.is_closed) + if (! path.is_closed) { - //For polylines, the seam settings are not applicable. Simply choose the position closest to target_pos then. - const coord_t back_distance = (combing_boundary == nullptr) - ? getDirectDistance(path.converted->back(), target_pos) - : getCombingDistance(path.converted->back(), target_pos); - if(back_distance < getDirectDistance(path.converted->front(), target_pos) || (combing_boundary && back_distance < getCombingDistance(path.converted->front(), target_pos))) //Lazy or: Only compute combing distance if direct distance is closer. + // For polylines, the seam settings are not applicable. Simply choose the position closest to target_pos then. + const coord_t back_distance + = (combing_boundary == nullptr) ? getDirectDistance(path.converted->back(), target_pos) : getCombingDistance(path.converted->back(), target_pos); + if (back_distance < getDirectDistance(path.converted->front(), target_pos) + || (combing_boundary + && back_distance < getCombingDistance(path.converted->front(), target_pos))) // Lazy or: Only compute combing distance if direct distance is closer. { - return path.converted->size() - 1; //Back end is closer. + return path.converted->size() - 1; // Back end is closer. } else { - return 0; //Front end is closer. + return 0; // Front end is closer. } } - //Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. + // Rest of the function only deals with (closed) polygons. We need to be able to find the seam location of those polygons. - if(seam_config.type == EZSeamType::RANDOM) + if (seam_config.type == EZSeamType::RANDOM) { size_t vert = getRandomPointInPolygon(*path.converted); return vert; } + // Precompute segments lengths because we are going to need them multiple times + std::vector segments_sizes(path.converted->size()); + coord_t total_length = 0; + for (const auto& [i, here] : **path.converted | ranges::views::enumerate) + { + const Point& next = (*path.converted)[(i + 1) % path.converted->size()]; + const coord_t segment_size = vSize(next - here); + segments_sizes[i] = segment_size; + total_length += segment_size; + } + size_t best_i; float best_score = std::numeric_limits::infinity(); - for(const auto& [i, here]: **path.converted | ranges::views::enumerate) + for (const auto& [i, here] : **path.converted | ranges::views::drop_last(1) | ranges::views::enumerate) { - //For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. - //For SHARPEST_CORNER, use a fixed starting score of 0. - const coord_t distance = (combing_boundary == nullptr) - ? getDirectDistance(here, target_pos) - : getCombingDistance(here, target_pos); - const float score_distance = (seam_config.type == EZSeamType::SHARPEST_CORNER && seam_config.corner_pref != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) ? MM2INT(10) : vSize2(here - target_pos); - - float corner_angle = cornerAngle(path, i); + // For most seam types, the shortest distance matters. Not for SHARPEST_CORNER though. + // For SHARPEST_CORNER, use a fixed starting score of 0. + const coord_t distance = (combing_boundary == nullptr) ? getDirectDistance(here, target_pos) : getCombingDistance(here, target_pos); + const float score_distance = (seam_config.type == EZSeamType::SHARPEST_CORNER && seam_config.corner_pref != EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE) + ? MM2INT(10) + : vSize2(here - target_pos); + + float corner_angle = cornerAngle(path, i, segments_sizes, total_length); // angles < 0 are concave (left turning) // angles > 0 are convex (right turning) @@ -646,34 +676,30 @@ class PathOrderOptimizer // so the user has some control over where the seam will lie. // the divisor here may need adjusting to obtain the best results (TBD) - corner_shift = score_distance / 10; + corner_shift = score_distance / 50; } float score = score_distance; - switch(seam_config.corner_pref) + switch (seam_config.corner_pref) { default: case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_INNER: - if(corner_angle < 0) // Indeed a concave corner? Give it some advantage over other corners. More advantage for sharper corners. - { - score -= (-corner_angle + 1.0) * corner_shift; - } + // Give advantage to concave corners. More advantage for sharper corners. + score += corner_angle * corner_shift; break; case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_OUTER: - if(corner_angle > 0) // Indeed a convex corner? - { - score -= (corner_angle + 1.0) * corner_shift; - } + // Give advantage to convex corners. More advantage for sharper corners. + score -= corner_angle * corner_shift; break; case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_ANY: - score -= std::abs(corner_angle) * corner_shift; //Still give sharper corners more advantage. + score -= std::abs(corner_angle) * corner_shift; // Still give sharper corners more advantage. break; case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE: break; - case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: //Give sharper corners some advantage, but sharper concave corners even more. + case EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_WEIGHTED: // Give sharper corners some advantage, but sharper concave corners even more. { float score_corner = std::abs(corner_angle) * corner_shift; - if(corner_angle < 0) //Concave corner. + if (corner_angle < 0) // Concave corner. { score_corner *= 2; } @@ -682,8 +708,8 @@ class PathOrderOptimizer } } - constexpr float EPSILON = 25.0; - if(std::abs(best_score - score) <= EPSILON) + constexpr float EPSILON = 5.0; + if (std::abs(best_score - score) <= EPSILON) { // add breaker for two candidate starting location with similar score // if we don't do this then we (can) get an un-even seam @@ -691,13 +717,13 @@ class PathOrderOptimizer // if x-coord for both points are equal then break ties by // favouring points with lower y-coord const Point& best_point = (*path.converted)[best_i]; - if(std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) + if (std::abs(here.Y - best_point.Y) <= EPSILON ? best_point.X < here.X : best_point.Y < here.Y) { best_score = std::min(best_score, score); best_i = i; } } - else if(score < best_score) + else if (score < best_score) { best_i = i; best_score = score; @@ -708,82 +734,81 @@ class PathOrderOptimizer } /*! - * 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 all points within angle_query_distance of the query point. Angles closer - * to the current point are weighted more towards the total angle then points further away. - * The formula for the angle weight is: 1 - (distance_to_query / angle_query_distance)^fall_off_strength - * \param path The vertex data of a path - * \param i index of the query point - * \param angle_query_distance query range (default to 0.1mm) - * \param fall_off_strength fall of strength of the angle weight - * \return sum of angles of all points p in range i - angle_query_distance < p < i + angle_query_distance - */ - float cornerAngle(const OrderablePath& path, int i, const coord_t angle_query_distance = 100, const float fall_off_strength = 0.5) + * 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 Point findNeighbourPoint(const OrderablePath& path, int here, coord_t distance, const std::vector& segments_sizes) { - // If the edge length becomes too small we cannot accurately calculate the angle - // define a minimum edge length, so we don't get deviant values in the angle calculations - constexpr coord_t min_edge_length = 10; - constexpr coord_t min_edge_length2 = min_edge_length * min_edge_length; - - const int offset_index = i % path.converted->size(); - Point here = (*path.converted)[offset_index]; - - const std::function find_neighbour_point = [&offset_index, &path](const int direction, const Point& here) + 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) { - int offset_index_ = offset_index; - Point neighbour; - do - { - offset_index_ = (offset_index_ + path.converted->size() + direction) % path.converted->size(); - neighbour = (*path.converted)[offset_index_]; - } - while (vSize2(here - neighbour) < min_edge_length2 && offset_index_ != offset_index); // find previous point that is at least min_edge_length units away from here - return neighbour; - }; + actual_delta += direction; + segment_size = segments_sizes[(here + actual_delta + size_delta + path.converted->size()) % path.converted->size()]; + travelled_distance += segment_size; + } - const std::function iterate_to_previous_point = [&find_neighbour_point](Point& previous_, Point& here_, Point& next_) - { - const auto dist = vSize(here_ - next_); - next_ = here_; - here_ = previous_; - previous_ = find_neighbour_point(-1, here_); - return dist; - }; - Point previous = find_neighbour_point(-1, here); + const Point& next_pos = (*path.converted)[(here + actual_delta + path.converted->size()) % path.converted->size()]; - const std::function iterate_to_next_point = [&find_neighbour_point](Point& previous_, Point& here_, Point& next_) + if (travelled_distance > distance) [[likely]] { - const auto dist = vSize(here_ - previous_); - previous_ = here_; - here_ = next_; - next_ = find_neighbour_point(1, here_); - return dist; - }; - Point next = find_neighbour_point(1, here); - - float corner_angle = LinearAlg2D::getAngleLeft(previous, here, next) - M_PI; - - for (const auto& iterate_func : {iterate_to_previous_point, iterate_to_next_point}) + // 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 Point& prev_pos = (*path.converted)[prev]; + + const Point vector = next_pos - prev_pos; + const Point unit_vector = (vector * 1000) / segment_size; + const Point vector_delta = unit_vector * (segment_size - (travelled_distance - distance)); + return prev_pos + vector_delta / 1000; + } + else { - Point next_ = next; - Point here_ = here; - Point previous_ = previous; - for - ( - coord_t distance_to_query = iterate_func(previous_, here_, next_); - distance_to_query < angle_query_distance && here_ != here; - distance_to_query += iterate_func(previous_, here_, next_) - ) - { - // angles further away from the query point are weighted less - const float angle_weight = 1.0 - pow(distance_to_query / angle_query_distance, fall_off_strength); - corner_angle += (LinearAlg2D::getAngleLeft(previous_, here_, next_) - M_PI) * angle_weight; - } + // 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 float 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 Point& here = (*path.converted)[i]; + const Point next = findNeighbourPoint(path, i, bounded_distance, segments_sizes); + const Point previous = findNeighbourPoint(path, i, -bounded_distance, segments_sizes); + + float angle = LinearAlg2D::getAngleLeft(previous, here, next) - M_PI; - return corner_angle / M_PI; // Limit angle between -1 and 1. + return angle / M_PI; } /*! @@ -810,11 +835,11 @@ class PathOrderOptimizer */ coord_t getCombingDistance(const Point& a, const Point& b) { - if(!PolygonUtils::polygonCollidesWithLineSegment(*combing_boundary, a, b)) + if (! PolygonUtils::polygonCollidesWithLineSegment(*combing_boundary, a, b)) { - return getDirectDistance(a, b); //No collision with any line. Just compute the direct distance then. + return getDirectDistance(a, b); // No collision with any line. Just compute the direct distance then. } - if(paths.size() > 100) + if (paths.size() > 100) { /* If we have many paths to optimize the order for, this combing calculation can become very expensive. Instead, penalize travels @@ -822,13 +847,13 @@ class PathOrderOptimizer return getDirectDistance(a, b) * 5; } - if(combing_grid == nullptr) + if (combing_grid == nullptr) { - constexpr coord_t grid_size = 2000; //2mm grid cells. Smaller will use more memory, but reduce chance of unnecessary collision checks. + constexpr coord_t grid_size = 2000; // 2mm grid cells. Smaller will use more memory, but reduce chance of unnecessary collision checks. combing_grid = PolygonUtils::createLocToLineGrid(*combing_boundary, grid_size); } - CombPath comb_path; //Output variable. + CombPath comb_path; // Output variable. constexpr coord_t rounding_error = -25; constexpr coord_t tiny_travel_threshold = 0; constexpr bool fail_on_unavoidable_obstacles = false; @@ -836,12 +861,12 @@ class PathOrderOptimizer coord_t sum = 0; Point last_point = a; - for(const Point& point : comb_path) + for (const Point& point : comb_path) { sum += vSize(point - last_point); last_point = point; } - return sum * sum; //Squared distance, for fair comparison with direct distance. + return sum * sum; // Squared distance, for fair comparison with direct distance. } /*! @@ -856,7 +881,7 @@ class PathOrderOptimizer bool isLoopingPolyline(const OrderablePath& path) { - if(path.converted->empty()) + if (path.converted->empty()) { return false; } @@ -867,6 +892,6 @@ class PathOrderOptimizer template const std::unordered_multimap PathOrderOptimizer::no_order_requirements; -} //namespace cura +} // namespace cura -#endif //PATHORDEROPTIMIZER_H +#endif // PATHORDEROPTIMIZER_H diff --git a/include/PrimeTower.h b/include/PrimeTower.h index 273e6c7816..3ea546efa6 100644 --- a/include/PrimeTower.h +++ b/include/PrimeTower.h @@ -4,14 +4,15 @@ #ifndef PRIME_TOWER_H #define PRIME_TOWER_H +#include +#include + #include "ExtruderUse.h" #include "settings/EnumSettings.h" +#include "settings/types/LayerIndex.h" #include "utils/polygon.h" // Polygons #include "utils/polygonUtils.h" -#include -#include - namespace cura { @@ -27,12 +28,10 @@ class LayerPlan; class PrimeTower { private: - struct ExtrusionMoves - { - Polygons polygons; - Polygons lines; - }; - unsigned int extruder_count; //!< Number of extruders + using MovesByExtruder = std::vector; + using MovesByLayer = std::vector; + + size_t extruder_count; //!< Number of extruders bool wipe_from_middle; //!< Whether to wipe on the inside of the hollow prime tower Point middle; //!< The middle of the prime tower @@ -42,16 +41,18 @@ class PrimeTower std::vector prime_tower_start_locations; //!< The differernt locations where to pre-wipe the active nozzle const unsigned int number_of_prime_tower_start_locations = 21; //!< The required size of \ref PrimeTower::wipe_locations - std::vector pattern_per_extruder; //!< For each extruder the pattern to print on all layers of the prime tower. - std::vector pattern_per_extruder_layer0; //!< For each extruder the pattern to print on the first layer - std::map> + MovesByExtruder prime_moves; //!< For each extruder, the moves to be processed for actual priming. + std::map> sparse_pattern_per_extruders; //!< For each extruders combination, and for each actual extruder, the pattern to print on all layers where extruders are actually useless. + MovesByLayer base_extra_moves; //!< For each layer and each extruder, the extra moves to be processed for better adhesion/strength + + Polygons outer_poly; //!< The outline of the outermost prime tower. + std::vector outer_poly_base; //!< The outline of the layers having extra width for the base public: bool enabled; //!< Whether the prime tower is enabled. bool would_have_actual_tower; //!< Whether there is an actual tower. bool multiple_extruders_on_first_layer; //!< Whether multiple extruders are allowed on the first layer of the prime tower (e.g. when a raft is there) - Polygons outer_poly; //!< The outline of the outermost prime tower. /* * In which order, from outside to inside, will we be printing the prime @@ -75,6 +76,13 @@ class PrimeTower */ void checkUsed(const SliceDataStorage& storage); + /*! + * Generate the prime tower area to be used on each layer + * + * Fills \ref PrimeTower::inner_poly and sets \ref PrimeTower::middle + */ + void generateGroundpoly(); + /*! * Generate the area where the prime tower should be. */ @@ -104,16 +112,22 @@ class PrimeTower */ void subtractFromSupport(SliceDataStorage& storage); -private: /*! - * Generate the prime tower area to be used on each layer + * Get the outer polygon for the given layer, which may be the priming polygon only, or a larger polygon for layers with a base * - * Fills \ref PrimeTower::inner_poly and sets \ref PrimeTower::middle + * \param[in] layer_nr The index of the layer + * \return The outer polygon for the prime tower at the given layer */ - void generateGroundpoly(); + const Polygons& getOuterPoly(const LayerIndex& layer_nr) const; /*! - * \see WipeTower::generatePaths + * Get the outer polygon for the very first layer, which may be the priming polygon only, or a larger polygon if there is a base + */ + const Polygons& getGroundPoly() const; + +private: + /*! + * \see PrimeTower::generatePaths * * Generate the extrude paths for each extruder on even and odd layers * Fill the ground poly with dense infill. diff --git a/include/SkirtBrim.h b/include/SkirtBrim.h index e7deb028a6..896e4aac45 100644 --- a/include/SkirtBrim.h +++ b/include/SkirtBrim.h @@ -4,13 +4,13 @@ #ifndef SKIRT_BRIM_H #define SKIRT_BRIM_H +#include + #include "ExtruderTrain.h" #include "settings/EnumSettings.h" #include "sliceDataStorage.h" #include "utils/Coord_t.h" -#include - namespace cura { @@ -114,15 +114,6 @@ class SkirtBrim */ std::vector generateBrimOffsetPlan(std::vector& starting_outlines); - /*! - * In case that the models have skirt 'adhesion', but the prime tower has a brim, the covered areas are different. - * - * Since the output of this function will need to be handled differently than the rest of the adhesion lines, have a separate function. - * Specifically, for skirt an additional 'approximate convex hull' is applied to the initial 'covered area', which is detrimental to brim. - * \return An ordered list of offsets of the prime-tower to perform in the order in which they are to be performed. - */ - std::vector generatePrimeTowerBrimForSkirtAdhesionOffsetPlan(); - /*! * Generate the primary skirt/brim of the one skirt_brim_extruder or of all extruders simultaneously. * diff --git a/include/SupportInfillPart.h b/include/SupportInfillPart.h index b22fdaef5a..1767847cb7 100644 --- a/include/SupportInfillPart.h +++ b/include/SupportInfillPart.h @@ -4,12 +4,12 @@ #ifndef SUPPORT_INFILL_PART_H #define SUPPORT_INFILL_PART_H +#include + #include "utils/AABB.h" #include "utils/ExtrusionLine.h" #include "utils/polygon.h" -#include - namespace cura { @@ -34,8 +34,9 @@ class SupportInfillPart 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. coord_t custom_line_distance; + bool use_fractional_config; //!< Request to use the configuration used to fill a partial layer height here, instead of the normal full layer height configuration. - SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate = 0, coord_t custom_line_distance = 0); + SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, bool use_fractional_config, int inset_count_to_generate = 0, coord_t custom_line_distance = 0); const Polygons& getInfillArea() const; }; diff --git a/include/infill.h b/include/infill.h index 40ca7fbd59..289a0850bb 100644 --- a/include/infill.h +++ b/include/infill.h @@ -206,8 +206,7 @@ class Infill const std::shared_ptr& cross_fill_provider = nullptr, const std::shared_ptr& lightning_layer = nullptr, const SliceMeshStorage* mesh = nullptr, - const Polygons& prevent_small_exposed_to_air = Polygons(), - const bool is_bridge_skin = false); + const Polygons& prevent_small_exposed_to_air = Polygons()); /*! * Generate the wall toolpaths of an infill area. It will return the inner contour and set the inner-contour. @@ -219,7 +218,6 @@ class Infill * \param line_width [in] The optimum wall line width of the walls * \param infill_overlap [in] The overlap of the infill * \param settings [in] A settings storage to use for generating variable-width walls. - * \param is_bridge_skin [in] Setting to filter out the extra skin walls while bridging * \return The inner contour of the wall toolpaths */ static Polygons generateWallToolPaths( @@ -230,8 +228,7 @@ class Infill const coord_t infill_overlap, const Settings& settings, int layer_idx, - SectionType section_type, - const bool is_bridge_skin = false); + SectionType section_type); private: /*! diff --git a/include/pathPlanning/GCodePath.h b/include/pathPlanning/GCodePath.h index 7bbd56c709..da4efd30aa 100644 --- a/include/pathPlanning/GCodePath.h +++ b/include/pathPlanning/GCodePath.h @@ -4,6 +4,9 @@ #ifndef PATH_PLANNING_G_CODE_PATH_H #define PATH_PLANNING_G_CODE_PATH_H +#include +#include + #include "GCodePathConfig.h" #include "SpaceFillType.h" #include "TimeMaterialEstimates.h" @@ -11,9 +14,6 @@ #include "sliceDataStorage.h" #include "utils/IntPoint.h" -#include -#include - namespace cura { @@ -29,6 +29,7 @@ namespace cura */ struct GCodePath { + coord_t z_offset{}; // mesh; //!< Which mesh this path belongs to, if any. If it's not part of any mesh, the mesh should be nullptr; SpaceFillType space_fill_type{}; //!< The type of space filling of which this path is a part diff --git a/include/settings/MeshPathConfigs.h b/include/settings/MeshPathConfigs.h index 43457fbc8c..61c993b9a8 100644 --- a/include/settings/MeshPathConfigs.h +++ b/include/settings/MeshPathConfigs.h @@ -15,6 +15,8 @@ struct MeshPathConfigs { GCodePathConfig inset0_config{}; GCodePathConfig insetX_config{}; + GCodePathConfig inset0_roofing_config{}; + GCodePathConfig insetX_roofing_config{}; GCodePathConfig bridge_inset0_config{}; GCodePathConfig bridge_insetX_config{}; GCodePathConfig skin_config{}; diff --git a/include/settings/PathConfigStorage.h b/include/settings/PathConfigStorage.h index e7bd54773a..feafea23bf 100644 --- a/include/settings/PathConfigStorage.h +++ b/include/settings/PathConfigStorage.h @@ -4,14 +4,14 @@ #ifndef SETTINGS_PATH_CONFIGS_H #define SETTINGS_PATH_CONFIGS_H +#include + #include "GCodePathConfig.h" #include "pathPlanning/SpeedDerivatives.h" #include "settings/MeshPathConfigs.h" #include "settings/types/LayerIndex.h" #include "utils/Coord_t.h" -#include - namespace cura { @@ -48,7 +48,9 @@ class PathConfigStorage std::vector prime_tower_config_per_extruder; //!< Configuration for the prime tower per extruder. std::vector support_infill_config; //!< The config used to print the normal support, rather than the support interface + std::vector support_fractional_infill_config; //!< The config used to print the normal support on fractional layer-height parts. GCodePathConfig support_roof_config; //!< The config used to print the dense roofs of support. + GCodePathConfig support_fractional_roof_config; //!< The config used to print the dense roofs of support on fractional layer-height parts. GCodePathConfig support_bottom_config; //!< The config to use to print the dense bottoms of support std::vector mesh_configs; //!< For each meash the config for all its feature types diff --git a/include/sliceDataStorage.h b/include/sliceDataStorage.h index 9b0661ed36..6db1cd605a 100644 --- a/include/sliceDataStorage.h +++ b/include/sliceDataStorage.h @@ -4,6 +4,10 @@ #ifndef SLICE_DATA_STORAGE_H #define SLICE_DATA_STORAGE_H +#include +#include +#include + #include "PrimeTower.h" #include "RetractionConfig.h" #include "SupportInfillPart.h" @@ -18,10 +22,6 @@ #include "utils/NoCopy.h" #include "utils/polygon.h" -#include -#include -#include - // libArachne #include "utils/ExtrusionLine.h" @@ -212,6 +212,9 @@ class SupportLayer std::vector support_infill_parts; //!< a list of support infill parts Polygons support_bottom; //!< Piece of support below the support and above the model. This must not overlap with any of the support_infill_parts or support_roof. Polygons support_roof; //!< Piece of support above the support and below the model. This must not overlap with any of the support_infill_parts or support_bottom. + // NOTE: This is _all_ of the support_roof, and as such, overlaps with support_fractional_roof! + Polygons support_fractional_roof; //!< If the support distance is not exactly a multiple of the layer height, + // the first part of support just underneath the model needs to be printed at a fracional layer height. Polygons support_mesh_drop_down; //!< Areas from support meshes which should be supported by more support Polygons support_mesh; //!< Areas from support meshes which should NOT be supported by more support Polygons anti_overhang; //!< Areas where no overhang should be detected. @@ -223,6 +226,24 @@ class SupportLayer * \param exclude_polygons_boundary_box The boundary box for the polygons to exclude */ void excludeAreasFromSupportInfillAreas(const Polygons& exclude_polygons, const AABB& exclude_polygons_boundary_box); + + /* Fill up the infill parts for the support with the given support polygons. The support polygons will be split into parts. This also takes into account fractional-height + * support layers. + * + * \param layer_nr Current layer index. + * \param support_fill_per_layer All of the (infill) support (since the layer above might be needed). + * \param support_line_width Line width of the support extrusions. + * \param wall_line_count Wall-line count around the fill. + * \param grow_layer_above (optional, default to 0) In cases where support shrinks per layer up, an appropriate offset may be nescesary. + * \param unionAll (optional, default to false) Wether to 'union all' for the split into parts bit. + */ + void fillInfillParts( + const LayerIndex layer_nr, + const std::vector& support_fill_per_layer, + const coord_t support_line_width, + const coord_t wall_line_count, + const coord_t grow_layer_above = 0, + const bool unionAll = false); }; class SupportStorage @@ -333,8 +354,6 @@ class SliceDataStorage : public NoCopy std::vector skirt_brim[MAX_EXTRUDERS]; //!< Skirt/brim polygons per extruder, ordered from inner to outer polygons. Polygons support_brim; //!< brim lines for support, going from the edge of the support inward. \note Not ordered by inset. Polygons raftOutline; // Storage for the outline of the raft. Will be filled with lines when the GCode is generated. - Polygons primeRaftOutline; // ... the raft underneath the prime-tower will have to be printed first, if there is one. (When the raft has top layers with a different extruder - // for example.) int max_print_height_second_to_last_extruder; //!< Used in multi-extrusion: the layer number beyond which all models are printed with the same extruder std::vector max_print_height_per_extruder; //!< For each extruder the highest layer number at which it is used. diff --git a/include/utils/polygonUtils.h b/include/utils/polygonUtils.h index fd3c51f3a8..d35d646729 100644 --- a/include/utils/polygonUtils.h +++ b/include/utils/polygonUtils.h @@ -4,16 +4,16 @@ #ifndef UTILS_POLYGON_UTILS_H #define UTILS_POLYGON_UTILS_H -#include "PolygonsPointIndex.h" -#include "SparseLineGrid.h" -#include "SparsePointGridInclusive.h" -#include "polygon.h" - #include // function #include #include // unique_ptr #include +#include "PolygonsPointIndex.h" +#include "SparseLineGrid.h" +#include "SparsePointGridInclusive.h" +#include "polygon.h" + namespace cura { @@ -689,6 +689,26 @@ class PolygonUtils */ static Polygons clipPolygonWithAABB(const Polygons& src, const AABB& aabb); + /*! + * Generate a few outset polygons around the given base, according to the given line width + * + * \param inner_poly The inner polygon to start generating the outset from + * \param count The number of outer polygons to add + * \param line_width The actual line width to distance the polygons from each other (and from the base) + * \return The generated outset polygons + */ + static Polygons generateOutset(const Polygons& inner_poly, size_t count, coord_t line_width); + + /*! + * Generate inset polygons inside the given base, until there is no space left, according to the given line width + * + * \param outer_poly The outer polygon to start generating the inset from + * \param line_width The actual line width to distance the polygons from each other (and from the base) + * \param initial_inset The inset distance to be added to the first generated polygon + * \return The generated inset polygons + */ + static Polygons generateInset(const Polygons& outer_poly, coord_t line_width, coord_t initial_inset = 0); + 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 diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 41f4408cf5..414eac2a8d 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3,6 +3,19 @@ #include "FffGcodeWriter.h" +#include +#include // numeric_limits +#include +#include +#include +#include + +#include +#include +#include +#include +#include + #include "Application.h" #include "ExtruderTrain.h" #include "FffProcessor.h" @@ -21,16 +34,6 @@ #include "utils/math.h" #include "utils/orderOptimizer.h" -#include -#include - -#include -#include // numeric_limits -#include -#include -#include -#include - namespace cura { @@ -554,6 +557,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const size_t base_extruder_nr = mesh_group_settings.get("raft_base_extruder_nr").extruder_nr; const size_t interface_extruder_nr = mesh_group_settings.get("raft_interface_extruder_nr").extruder_nr; const size_t surface_extruder_nr = mesh_group_settings.get("raft_surface_extruder_nr").extruder_nr; + const size_t prime_tower_extruder_nr = storage.primeTower.extruder_order.front(); coord_t z = 0; const LayerIndex initial_raft_layer_nr = -Raft::getTotalExtraLayers(); @@ -595,8 +599,6 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) = *new LayerPlan(storage, layer_nr, z, layer_height, base_extruder_nr, fan_speed_layer_time_settings_per_extruder_raft_base, comb_offset, line_width, avoid_distance); gcode_layer.setIsInside(true); - gcode_layer.setExtruder(base_extruder_nr); - Application::getInstance().communication->sendLayerComplete(layer_nr, z, layer_height); Polygons raftLines; @@ -609,6 +611,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const size_t wall_line_count = base_settings.get("raft_base_wall_count"); const coord_t small_area_width = 0; // A raft never has a small region due to the large horizontal expansion. const coord_t line_spacing = base_settings.get("raft_base_line_spacing"); + const coord_t line_spacing_prime_tower = base_settings.get("prime_tower_raft_base_line_spacing"); const Point& infill_origin = Point(); constexpr bool skip_stitching = false; constexpr bool connected_zigzags = false; @@ -619,22 +622,39 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) const coord_t max_resolution = base_settings.get("meshfix_maximum_resolution"); const coord_t max_deviation = base_settings.get("meshfix_maximum_deviation"); - std::vector raft_outline_paths; - if (storage.primeRaftOutline.area() > 0) + struct ParameterizedRaftPath + { + coord_t line_spacing; + Polygons outline; + }; + + std::vector raft_outline_paths; + raft_outline_paths.emplace_back(ParameterizedRaftPath{ line_spacing, storage.raftOutline }); + if (storage.primeTower.enabled) { - raft_outline_paths.emplace_back(storage.primeRaftOutline); + const Polygons& raft_outline_prime_tower = storage.primeTower.getOuterPoly(layer_nr); + if (line_spacing_prime_tower == line_spacing) + { + // Base layer is shared with prime tower base + raft_outline_paths.front().outline = raft_outline_paths.front().outline.unionPolygons(raft_outline_prime_tower); + } + else + { + // Prime tower has a different line spacing, print them separately + raft_outline_paths.front().outline = raft_outline_paths.front().outline.difference(raft_outline_prime_tower); + raft_outline_paths.emplace_back(ParameterizedRaftPath{ line_spacing_prime_tower, raft_outline_prime_tower }); + } } - raft_outline_paths.emplace_back(storage.raftOutline); - for (const Polygons& raft_outline_path : raft_outline_paths) + for (const ParameterizedRaftPath& raft_outline_path : raft_outline_paths) { Infill infill_comp( EFillMethod::LINES, zig_zaggify_infill, connect_polygons, - raft_outline_path, + raft_outline_path.outline, gcode_layer.configs_storage.raft_base_config.getLineWidth(), - line_spacing, + raft_outline_path.line_spacing, fill_overlap, infill_multiplier, fill_angle, @@ -697,6 +717,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) for (LayerIndex raft_interface_layer = 1; static_cast(raft_interface_layer) <= num_interface_layers; ++raft_interface_layer) { // raft interface layer + bool prime_tower_added_on_this_layer = ! storage.primeTower.enabled; const LayerIndex layer_nr = initial_raft_layer_nr + raft_interface_layer; z += interface_layer_height; @@ -720,22 +741,27 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) comb_offset, interface_line_width, interface_avoid_distance); - gcode_layer.setIsInside(true); - current_extruder_nr = interface_extruder_nr; + if (! prime_tower_added_on_this_layer && current_extruder_nr == prime_tower_extruder_nr) + { + addPrimeTower(storage, gcode_layer, current_extruder_nr); + prime_tower_added_on_this_layer = true; + } + + gcode_layer.setIsInside(true); + if (interface_extruder_nr != current_extruder_nr) + { + setExtruder_addPrime(storage, gcode_layer, interface_extruder_nr); + current_extruder_nr = interface_extruder_nr; + } Application::getInstance().communication->sendLayerComplete(layer_nr, z, interface_layer_height); - std::vector raft_outline_paths; + Polygons raft_outline_path; const coord_t small_offset = gcode_layer.configs_storage.raft_interface_config.getLineWidth() / 2; // Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type. - if (storage.primeRaftOutline.area() > 0) - { - raft_outline_paths.emplace_back(storage.primeRaftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. - } - raft_outline_paths.emplace_back(storage.raftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. + raft_outline_path = storage.raftOutline.offset(-small_offset); + raft_outline_path = Simplify(interface_settings).polygon(raft_outline_path); // Remove those micron-movements. const coord_t infill_outline_width = gcode_layer.configs_storage.raft_interface_config.getLineWidth(); Polygons raft_lines; AngleDegrees fill_angle = (num_surface_layers + num_interface_layers - raft_interface_layer) % 2 ? 45 : 135; // 90 degrees rotated from the first top layer. @@ -752,38 +778,47 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr int zag_skip_count = 0; constexpr coord_t pocket_size = 0; - for (const Polygons& raft_outline_path : raft_outline_paths) + if (storage.primeTower.enabled) { - Infill infill_comp( - EFillMethod::ZIG_ZAG, - zig_zaggify_infill, - connect_polygons, - raft_outline_path, - infill_outline_width, - interface_line_spacing, - fill_overlap, - infill_multiplier, - fill_angle, - z, - extra_infill_shift, - interface_max_resolution, - interface_max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - std::vector raft_paths; // Should remain empty, since we have no walls. - infill_comp.generate(raft_paths, raft_polygons, raft_lines, interface_settings, layer_nr, SectionType::ADHESION); - gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_interface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); + // Interface layer excludes prime tower base + raft_outline_path = raft_outline_path.difference(storage.primeTower.getOuterPoly(layer_nr)); + } - raft_polygons.clear(); - raft_lines.clear(); + Infill infill_comp( + EFillMethod::ZIG_ZAG, + zig_zaggify_infill, + connect_polygons, + raft_outline_path, + infill_outline_width, + interface_line_spacing, + fill_overlap, + infill_multiplier, + fill_angle, + z, + extra_infill_shift, + interface_max_resolution, + interface_max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + std::vector raft_paths; // Should remain empty, since we have no walls. + infill_comp.generate(raft_paths, raft_polygons, raft_lines, interface_settings, layer_nr, SectionType::ADHESION); + gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_interface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); + + raft_polygons.clear(); + raft_lines.clear(); + + if (! prime_tower_added_on_this_layer) + { + setExtruder_addPrime(storage, gcode_layer, prime_tower_extruder_nr); + current_extruder_nr = prime_tower_extruder_nr; } layer_plan_buffer.handle(gcode_layer, gcode); @@ -800,6 +835,7 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) for (LayerIndex raft_surface_layer = 1; static_cast(raft_surface_layer) <= num_surface_layers; raft_surface_layer++) { // raft surface layers + bool prime_tower_added_on_this_layer = ! storage.primeTower.enabled; const LayerIndex layer_nr = initial_raft_layer_nr + 1 + num_interface_layers + raft_surface_layer - 1; // +1: 1 base layer z += surface_layer_height; @@ -823,23 +859,28 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) comb_offset, surface_line_width, surface_avoid_distance); + + if (! prime_tower_added_on_this_layer && current_extruder_nr == prime_tower_extruder_nr) + { + addPrimeTower(storage, gcode_layer, current_extruder_nr); + prime_tower_added_on_this_layer = true; + } + gcode_layer.setIsInside(true); // make sure that we are using the correct extruder to print raft - current_extruder_nr = surface_extruder_nr; - + if (current_extruder_nr != surface_extruder_nr) + { + setExtruder_addPrime(storage, gcode_layer, surface_extruder_nr); + current_extruder_nr = surface_extruder_nr; + } Application::getInstance().communication->sendLayerComplete(layer_nr, z, surface_layer_height); - std::vector raft_outline_paths; + Polygons raft_outline_path; const coord_t small_offset = gcode_layer.configs_storage.raft_interface_config.getLineWidth() / 2; // Do this manually because of micron-movement created in corners when insetting a polygon that was offset with round joint type. - if (storage.primeRaftOutline.area() > 0) - { - raft_outline_paths.emplace_back(storage.primeRaftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. - } - raft_outline_paths.emplace_back(storage.raftOutline.offset(-small_offset)); - raft_outline_paths.back() = Simplify(interface_settings).polygon(raft_outline_paths.back()); // Remove those micron-movements. + raft_outline_path = storage.raftOutline.offset(-small_offset); + raft_outline_path = Simplify(interface_settings).polygon(raft_outline_path); // Remove those micron-movements. const coord_t infill_outline_width = gcode_layer.configs_storage.raft_interface_config.getLineWidth(); Polygons raft_lines; AngleDegrees fill_angle @@ -857,38 +898,47 @@ void FffGcodeWriter::processRaft(const SliceDataStorage& storage) constexpr size_t zag_skip_count = 0; constexpr coord_t pocket_size = 0; - for (const Polygons& raft_outline_path : raft_outline_paths) + if (storage.primeTower.enabled) { - Infill infill_comp( - EFillMethod::ZIG_ZAG, - zig_zaggify_infill, - connect_polygons, - raft_outline_path, - infill_outline_width, - surface_line_spacing, - fill_overlap, - infill_multiplier, - fill_angle, - z, - extra_infill_shift, - surface_max_resolution, - surface_max_deviation, - wall_line_count, - small_area_width, - infill_origin, - skip_stitching, - fill_gaps, - connected_zigzags, - use_endpieces, - skip_some_zags, - zag_skip_count, - pocket_size); - std::vector raft_paths; // Should remain empty, since we have no walls. - infill_comp.generate(raft_paths, raft_polygons, raft_lines, surface_settings, layer_nr, SectionType::ADHESION); - gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_surface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); + // Surface layers exclude prime tower base + raft_outline_path = raft_outline_path.difference(storage.primeTower.getOuterPoly(layer_nr)); + } - raft_polygons.clear(); - raft_lines.clear(); + Infill infill_comp( + EFillMethod::ZIG_ZAG, + zig_zaggify_infill, + connect_polygons, + raft_outline_path, + infill_outline_width, + surface_line_spacing, + fill_overlap, + infill_multiplier, + fill_angle, + z, + extra_infill_shift, + surface_max_resolution, + surface_max_deviation, + wall_line_count, + small_area_width, + infill_origin, + skip_stitching, + fill_gaps, + connected_zigzags, + use_endpieces, + skip_some_zags, + zag_skip_count, + pocket_size); + std::vector raft_paths; // Should remain empty, since we have no walls. + infill_comp.generate(raft_paths, raft_polygons, raft_lines, surface_settings, layer_nr, SectionType::ADHESION); + gcode_layer.addLinesByOptimizer(raft_lines, gcode_layer.configs_storage.raft_surface_config, SpaceFillType::Lines, false, 0, 1.0, last_planned_position); + + raft_polygons.clear(); + raft_lines.clear(); + + if (! prime_tower_added_on_this_layer) + { + setExtruder_addPrime(storage, gcode_layer, prime_tower_extruder_nr); + current_extruder_nr = prime_tower_extruder_nr; } layer_plan_buffer.handle(gcode_layer, gcode); @@ -1011,7 +1061,7 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn // later in the print a prime tower is needed. // - prime tower is already printed this layer (only applicable for more than 2 extruders). // The setExtruder_addPrime takes care of this. - if (extruder_nr != extruder_order.front().extruder_nr || extruder_order.size() == 1) + if (extruder_nr != extruder_order.front() || (extruder_order.size() == 1 && layer_nr >= 0) || extruder_nr == 0) { setExtruder_addPrime(storage, gcode_layer, extruder_nr); } @@ -1042,7 +1092,7 @@ LayerPlan& FffGcodeWriter::processLayer(const SliceDataStorage& storage, LayerIn // Always print a prime tower before switching extruder. Unless: // - The prime tower is already printed this layer (setExtruder_addPrime takes care of this). // - this is the last extruder of the layer, since the next layer will start with the same extruder. - if (extruder_nr != extruder_order.back().extruder_nr) + if (extruder_nr != extruder_order.back() && layer_nr >= 0) { setExtruder_addPrime(storage, gcode_layer, extruder_nr); } @@ -1231,9 +1281,10 @@ void FffGcodeWriter::processSkirtBrim(const SliceDataStorage& storage, LayerPlan // Add the support brim after the skirt_brim to gcode_layer + // Support brim is only added in layer 0 // For support brim we don't care about the order, because support doesn't need to be accurate. const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - if (extruder_nr == mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr) + if ((layer_nr == 0) && (extruder_nr == mesh_group_settings.get("support_extruder_nr_layer_0").extruder_nr)) { total_line_count += storage.support_brim.size(); Polygons support_brim_lines = storage.support_brim; @@ -1352,7 +1403,9 @@ std::vector // Make a temp list with the potential ordered extruders std::vector ordered_extruders; ordered_extruders.push_back(start_extruder); - for (size_t extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) + + // The outermost prime tower extruder is always used if there is a prime tower, apart on layers with negative index (e.g. for the raft) + if (mesh_group_settings.get("prime_tower_enable") && /*layer_nr >= 0 &&*/ layer_nr <= storage.max_print_height_second_to_last_extruder) { if (extruder_nr != start_extruder) { @@ -1548,7 +1601,7 @@ void FffGcodeWriter::addMeshLayerToGCode( { part_order_optimizer.addPolygon(&part); } - part_order_optimizer.optimize(); + part_order_optimizer.optimize(false); for (const PathOrdering& path : part_order_optimizer.paths) { addMeshPartToGCode(storage, mesh, extruder_nr, mesh_config, *path.vertices, gcode_layer); @@ -2343,7 +2396,7 @@ bool FffGcodeWriter::processInsets( if (mesh_group_settings.get("support_enable")) { const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height) + 1; + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; const int support_layer_nr = gcode_layer.getLayerNr() - z_distance_top_layers; if (support_layer_nr > 0) @@ -2447,6 +2500,101 @@ bool FffGcodeWriter::processInsets( } else { + // for layers that (partially) do not have any layers above we apply the roofing configuration + auto use_roofing_config = [&part, &mesh, &gcode_layer]() + { + const auto getOutlineOnLayer = [mesh](const SliceLayerPart& part_here, const LayerIndex layer2_nr) -> Polygons + { + Polygons result; + if (layer2_nr >= static_cast(mesh.layers.size())) + { + return result; + } + const SliceLayer& layer2 = mesh.layers[layer2_nr]; + for (const SliceLayerPart& part2 : layer2.parts) + { + if (part_here.boundaryBox.hit(part2.boundaryBox)) + { + result.add(part2.outline); + } + } + return result; + }; + + const auto filled_area_above = [&getOutlineOnLayer, &part, &mesh, &gcode_layer]() -> Polygons + { + const size_t roofing_layer_count = std::min(mesh.settings.get("roofing_layer_count"), mesh.settings.get("top_layers")); + const bool no_small_gaps_heuristic = mesh.settings.get("skin_no_small_gaps_heuristic"); + const int layer_nr = gcode_layer.getLayerNr(); + auto filled_area_above = getOutlineOnLayer(part, layer_nr + roofing_layer_count); + if (! no_small_gaps_heuristic) + { + for (int layer_nr_above = layer_nr + 1; layer_nr_above < layer_nr + roofing_layer_count; layer_nr_above++) + { + Polygons outlines_above = getOutlineOnLayer(part, layer_nr_above); + filled_area_above = filled_area_above.intersection(outlines_above); + } + } + if (layer_nr > 0) + { + // if the skin has air below it then cutting it into regions could cause a region + // to be wholely or partly above air and it may not be printable so restrict + // the regions that have air above (the visible regions) to not include any area that + // has air below (fixes https://github.com/Ultimaker/Cura/issues/2656) + + // set air_below to the skin area for the current layer that has air below it + Polygons air_below = getOutlineOnLayer(part, layer_nr).difference(getOutlineOnLayer(part, layer_nr - 1)); + + if (! air_below.empty()) + { + // add the polygons that have air below to the no air above polygons + filled_area_above = filled_area_above.unionPolygons(air_below); + } + } + + return filled_area_above; + }(); + + if (filled_area_above.empty()) + { + return true; + } + + const auto point_view = ranges::views::transform( + [](auto extrusion_junction) + { + return extrusion_junction.p; + }); + + for (const auto& path : part.wall_toolpaths) + { + for (const auto& wall : path) + { + for (const auto& p : wall | point_view) + { + if (! filled_area_above.inside(p)) + { + return true; + } + } + + for (const auto& window : wall | point_view | ranges::views::sliding(2)) + { + auto p0 = window[0]; + auto p1 = window[1]; + if (PolygonUtils::polygonCollidesWithLineSegment(filled_area_above, p0, p1)) + { + return true; + } + } + } + } + return false; + }(); + + const GCodePathConfig& inset0_config = use_roofing_config ? mesh_config.inset0_roofing_config : mesh_config.inset0_config; + const GCodePathConfig& insetX_config = use_roofing_config ? mesh_config.insetX_roofing_config : mesh_config.insetX_config; + // Main case: Optimize the insets with the InsetOrderOptimizer. const coord_t wall_x_wipe_dist = 0; const ZSeamConfig z_seam_config( @@ -2460,8 +2608,8 @@ bool FffGcodeWriter::processInsets( gcode_layer, mesh.settings, extruder_nr, - mesh_config.inset0_config, - mesh_config.insetX_config, + inset0_config, + insetX_config, mesh_config.bridge_inset0_config, mesh_config.bridge_insetX_config, mesh.settings.get("travel_retract_before_outer_wall"), @@ -2652,7 +2800,7 @@ void FffGcodeWriter::processTopBottom( { const coord_t layer_height = mesh_config.inset0_config.getLayerThickness(); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height) + 1; + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; support_layer_nr = layer_nr - z_distance_top_layers; } @@ -2783,8 +2931,7 @@ void FffGcodeWriter::processTopBottom( skin_density, monotonic, added_something, - fan_speed, - is_bridge_skin); + fan_speed); } void FffGcodeWriter::processSkinPrintFeature( @@ -2801,8 +2948,7 @@ void FffGcodeWriter::processSkinPrintFeature( const Ratio skin_density, const bool monotonic, bool& added_something, - double fan_speed, - const bool is_bridge_skin) const + double fan_speed) const { Polygons skin_polygons; Polygons skin_lines; @@ -2862,8 +3008,7 @@ void FffGcodeWriter::processSkinPrintFeature( nullptr, nullptr, nullptr, - small_areas_on_surface ? Polygons() : exposed_to_air, - is_bridge_skin); + small_areas_on_surface ? Polygons() : exposed_to_air); // add paths if (! skin_polygons.empty() || ! skin_lines.empty() || ! skin_paths.empty()) @@ -2889,10 +3034,10 @@ void FffGcodeWriter::processSkinPrintFeature( gcode_layer, mesh.settings, extruder_nr, - mesh_config.skin_config, - mesh_config.skin_config, - mesh_config.skin_config, - mesh_config.skin_config, + config, + config, + config, + config, retract_before_outer_wall, wipe_dist, wipe_dist, @@ -3019,13 +3164,21 @@ bool FffGcodeWriter::addSupportToGCode(const SliceDataStorage& storage, LayerPla return support_added; } + if (extruder_nr == support_roof_extruder_nr) + { + support_added |= addSupportRoofsToGCode(storage, support_layer.support_fractional_roof, gcode_layer.configs_storage.support_fractional_roof_config, gcode_layer); + } if (extruder_nr == support_infill_extruder_nr) { support_added |= processSupportInfill(storage, gcode_layer); } if (extruder_nr == support_roof_extruder_nr) { - support_added |= addSupportRoofsToGCode(storage, gcode_layer); + support_added |= addSupportRoofsToGCode( + storage, + support_layer.support_roof.difference(support_layer.support_fractional_roof), + gcode_layer.configs_storage.support_roof_config, + gcode_layer); } if (extruder_nr == support_bottom_extruder_nr) { @@ -3101,14 +3254,15 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer const auto zag_skip_count = infill_extruder.settings.get("support_zag_skip_count"); // create a list of outlines and use PathOrderOptimizer to optimize the travel move + PathOrderOptimizer island_order_optimizer_initial(gcode_layer.getLastPlannedPositionOrStartingPosition()); PathOrderOptimizer island_order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition()); for (const SupportInfillPart& part : support_layer.support_infill_parts) { - island_order_optimizer.addPolygon(&part); + (part.use_fractional_config ? island_order_optimizer_initial : island_order_optimizer).addPolygon(&part); } + island_order_optimizer_initial.optimize(); island_order_optimizer.optimize(); - const auto support_brim_line_count = infill_extruder.settings.get("support_brim_line_count"); const auto support_connect_zigzags = infill_extruder.settings.get("support_connect_zigzags"); const auto support_structure = infill_extruder.settings.get("support_structure"); const Point infill_origin; @@ -3119,9 +3273,10 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer bool need_travel_to_end_of_last_spiral = true; // Print the thicker infill lines first. (double or more layer thickness, infill combined with previous layers) - for (const PathOrdering& path : island_order_optimizer.paths) + for (const PathOrdering& path : ranges::views::concat(island_order_optimizer_initial.paths, island_order_optimizer.paths)) { const SupportInfillPart& part = *path.vertices; + const auto& configs = part.use_fractional_config ? gcode_layer.configs_storage.support_fractional_infill_config : gcode_layer.configs_storage.support_infill_config; // always process the wall overlap if walls are generated const int current_support_infill_overlap = (part.inset_count_to_generate > 0) ? default_support_infill_overlap : 0; @@ -3131,7 +3286,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer if (! wall_toolpaths.empty()) { - const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0]; + const GCodePathConfig& config = configs[0]; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); @@ -3185,7 +3340,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer { support_line_distance_here /= 2; } - const Polygons& area = part.infill_area_per_combine_per_density[density_idx][combine_idx]; + const Polygons& area = Simplify(infill_extruder.settings).polygon(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. const coord_t small_area_width = 0; @@ -3201,7 +3356,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer current_support_infill_overlap - (density_idx == max_density_idx ? 0 : wall_line_count * support_line_width), infill_multiplier, support_infill_angle, - gcode_layer.z, + gcode_layer.z + configs[combine_idx].z_offset, support_shift, max_resolution, max_deviation, @@ -3265,7 +3420,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer gcode_layer.addPolygonsByOptimizer( support_polygons, - gcode_layer.configs_storage.support_infill_config[combine_idx], + configs[combine_idx], z_seam_config, wall_0_wipe_dist, spiralize, @@ -3286,7 +3441,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer gcode_layer.addLinesByOptimizer( support_lines, - gcode_layer.configs_storage.support_infill_config[combine_idx], + configs[combine_idx], (support_pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines, enable_travel_optimization, wipe_dist, @@ -3302,7 +3457,7 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer // If not, the pattern may still generate gap filling (if it's connected infill or zigzag). We still want to print those. if (wall_line_count == 0 || ! wall_toolpaths_here.empty()) { - const GCodePathConfig& config = gcode_layer.configs_storage.support_infill_config[0]; + const GCodePathConfig& config = configs[0]; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; constexpr coord_t simplify_curvature = 0; @@ -3337,7 +3492,11 @@ bool FffGcodeWriter::processSupportInfill(const SliceDataStorage& storage, Layer } -bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, LayerPlan& gcode_layer) const +bool FffGcodeWriter::addSupportRoofsToGCode( + const SliceDataStorage& storage, + const Polygons& support_roof_outlines, + const GCodePathConfig& current_roof_config, + LayerPlan& gcode_layer) const { const SupportLayer& support_layer = storage.support.supportLayers[std::max(LayerIndex{ 0 }, gcode_layer.getLayerNr())]; @@ -3383,7 +3542,7 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay support_roof_line_distance *= roof_extruder.settings.get("initial_layer_line_width_factor"); } - Polygons infill_outline = support_layer.support_roof; + Polygons infill_outline = support_roof_outlines; Polygons wall; // make sure there is a wall if this is on the first layer if (gcode_layer.getLayerNr() == 0) @@ -3391,18 +3550,19 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay wall = support_layer.support_roof.offset(-support_roof_line_width / 2); infill_outline = wall.offset(-support_roof_line_width / 2); } + infill_outline = Simplify(roof_extruder.settings).polygon(infill_outline); Infill roof_computation( pattern, zig_zaggify_infill, connect_polygons, infill_outline, - gcode_layer.configs_storage.support_roof_config.getLineWidth(), + current_roof_config.getLineWidth(), support_roof_line_distance, support_roof_overlap, infill_multiplier, fill_angle, - gcode_layer.z, + gcode_layer.z + current_roof_config.z_offset, extra_infill_shift, max_resolution, max_deviation, @@ -3427,17 +3587,17 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay gcode_layer.setIsInside(false); // going to print stuff outside print object, i.e. support if (gcode_layer.getLayerNr() == 0) { - gcode_layer.addPolygonsByOptimizer(wall, gcode_layer.configs_storage.support_roof_config); + gcode_layer.addPolygonsByOptimizer(wall, current_roof_config); } if (! roof_polygons.empty()) { constexpr bool force_comb_retract = false; gcode_layer.addTravel(roof_polygons[0][0], force_comb_retract); - gcode_layer.addPolygonsByOptimizer(roof_polygons, gcode_layer.configs_storage.support_roof_config); + gcode_layer.addPolygonsByOptimizer(roof_polygons, current_roof_config); } if (! roof_paths.empty()) { - const GCodePathConfig& config = gcode_layer.configs_storage.support_roof_config; + const GCodePathConfig& config = current_roof_config; constexpr bool retract_before_outer_wall = false; constexpr coord_t wipe_dist = 0; const ZSeamConfig z_seam_config(EZSeamType::SHORTEST, gcode_layer.getLastPlannedPositionOrStartingPosition(), EZSeamCornerPrefType::Z_SEAM_CORNER_PREF_NONE, false); @@ -3461,10 +3621,7 @@ bool FffGcodeWriter::addSupportRoofsToGCode(const SliceDataStorage& storage, Lay roof_paths); wall_orderer.addToLayer(); } - gcode_layer.addLinesByOptimizer( - roof_lines, - gcode_layer.configs_storage.support_roof_config, - (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); + gcode_layer.addLinesByOptimizer(roof_lines, current_roof_config, (pattern == EFillMethod::ZIG_ZAG) ? SpaceFillType::PolyLines : SpaceFillType::Lines); return true; } @@ -3584,8 +3741,6 @@ bool FffGcodeWriter::addSupportBottomsToGCode(const SliceDataStorage& storage, L void FffGcodeWriter::setExtruder_addPrime(const SliceDataStorage& storage, LayerPlan& gcode_layer, const size_t extruder_nr) const { - const size_t outermost_prime_tower_extruder = storage.primeTower.extruder_order[0]; - const size_t previous_extruder = gcode_layer.getExtruder(); const bool extruder_changed = gcode_layer.setExtruder(extruder_nr); diff --git a/src/FffPolygonGenerator.cpp b/src/FffPolygonGenerator.cpp index 2848dcbb88..e8614b6faf 100644 --- a/src/FffPolygonGenerator.cpp +++ b/src/FffPolygonGenerator.cpp @@ -1,14 +1,14 @@ // Copyright (c) 2023 UltiMaker // CuraEngine is released under the terms of the AGPLv3 or higher -#include - #include #include #include // ifstream.good() #include // multimap (ordered map allowing duplicate keys) #include +#include + // Code smell: Order of the includes is important here, probably due to some forward declarations which might be masking some undefined behaviours // clang-format off #include "Application.h" @@ -983,7 +983,7 @@ void FffPolygonGenerator::processOozeShield(SliceDataStorage& storage) } for (LayerIndex layer_nr = 0; layer_nr <= storage.max_print_height_second_to_last_extruder; layer_nr++) { - storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].difference(storage.primeTower.outer_poly.offset(max_line_width / 2)); + storage.oozeShield[layer_nr] = storage.oozeShield[layer_nr].difference(storage.primeTower.getOuterPoly(layer_nr).offset(max_line_width / 2)); } } } @@ -1033,7 +1033,7 @@ void FffPolygonGenerator::processDraftShield(SliceDataStorage& storage) max_line_width = std::max(max_line_width, extruders[extruder_nr].settings.get("skirt_brim_line_width")); } } - storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.primeTower.outer_poly.offset(max_line_width / 2)); + storage.draft_protection_shield = storage.draft_protection_shield.difference(storage.primeTower.getGroundPoly().offset(max_line_width / 2)); } } diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 4a283c147e..52305a9521 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -3,6 +3,15 @@ #include "LayerPlan.h" +#include +#include +#include +#include + +#include +#include +#include + #include "Application.h" //To communicate layer view data. #include "ExtruderTrain.h" #include "PathOrderMonotonic.h" //Monotonic ordering of skin lines. @@ -20,15 +29,6 @@ #include "utils/polygonUtils.h" #include "utils/section_type.h" -#include -#include -#include - -#include -#include -#include -#include - namespace cura { @@ -38,19 +38,22 @@ constexpr int MINIMUM_SQUARED_LINE_LENGTH = MINIMUM_LINE_LENGTH * MINIMUM_LINE_L GCodePath* LayerPlan::getLatestPathWithConfig( const GCodePathConfig& config, - SpaceFillType space_fill_type, + const SpaceFillType space_fill_type, + const coord_t z_offset, const Ratio flow, const Ratio width_factor, - bool spiralize, + const bool spiralize, const Ratio speed_factor) { std::vector& paths = extruder_plans.back().paths; if (paths.size() > 0 && paths.back().config == config && ! paths.back().done && paths.back().flow == flow && paths.back().width_factor == width_factor - && paths.back().speed_factor == speed_factor && paths.back().mesh == current_mesh) // spiralize can only change when a travel path is in between + && paths.back().speed_factor == speed_factor && paths.back().z_offset == z_offset + && paths.back().mesh == current_mesh) // spiralize can only change when a travel path is in between { return &paths.back(); } - paths.emplace_back(GCodePath{ .config = config, + paths.emplace_back(GCodePath{ .z_offset = z_offset, + .config = config, .mesh = current_mesh, .space_fill_type = space_fill_type, .flow = flow, @@ -326,14 +329,14 @@ std::optional> LayerPlan::getFirstTravelDestinationState( return ret; } -GCodePath& LayerPlan::addTravel(const Point p, const bool force_retract) +GCodePath& LayerPlan::addTravel(const Point& p, const bool force_retract, const coord_t z_offset) { const GCodePathConfig& travel_config = configs_storage.travel_config_per_extruder[getExtruder()]; const RetractionConfig& retraction_config = current_mesh ? current_mesh->retraction_wipe_config.retraction_config : storage.retraction_wipe_config_per_extruder[getExtruder()].retraction_config; - GCodePath* path = getLatestPathWithConfig(travel_config, SpaceFillType::None); + GCodePath* path = getLatestPathWithConfig(travel_config, SpaceFillType::None, z_offset); bool combed = false; @@ -478,7 +481,7 @@ GCodePath& LayerPlan::addTravel(const Point p, const bool force_retract) return ret; } -GCodePath& LayerPlan::addTravel_simple(Point p, GCodePath* path) +GCodePath& LayerPlan::addTravel_simple(const Point& p, GCodePath* path) { bool is_first_travel_of_layer = ! static_cast(last_planned_position); if (is_first_travel_of_layer) @@ -506,16 +509,16 @@ void LayerPlan::planPrime(const float& prime_blob_wipe_length) } void LayerPlan::addExtrusionMove( - Point p, + const Point p, const GCodePathConfig& config, - SpaceFillType space_fill_type, + const SpaceFillType space_fill_type, const Ratio& flow, const Ratio width_factor, - bool spiralize, - Ratio speed_factor, - double fan_speed) + const bool spiralize, + const Ratio speed_factor, + const double fan_speed) { - GCodePath* path = getLatestPathWithConfig(config, space_fill_type, flow, width_factor, spiralize, speed_factor); + GCodePath* path = getLatestPathWithConfig(config, space_fill_type, config.z_offset, flow, width_factor, spiralize, speed_factor); path->points.push_back(p); path->setFanSpeed(fan_speed); if (! static_cast(first_extrusion_acc_jerk)) @@ -537,7 +540,7 @@ void LayerPlan::addPolygon( { constexpr Ratio width_ratio = 1.0_r; // Not printed with variable line width. Point p0 = polygon[start_idx]; - addTravel(p0, always_retract); + 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++) { @@ -1250,7 +1253,7 @@ void LayerPlan::addLinesInGivenOrder( } else { - addTravel(start); + addTravel(start, false, config.z_offset); } Point p0 = start; @@ -1947,7 +1950,12 @@ void LayerPlan::writeGCode(GCodeExport& gcode) gcode.writeRetraction(retraction_config->retraction_config); } - if (! path.retract && path.config.isTravelPath() && path.points.size() == 1 && path.points[0] == gcode.getPositionXY() && z == gcode.getPositionZ()) + if (z > 0) + { + gcode.setZ(z + path.z_offset); + } + + if (! path.retract && path.config.isTravelPath() && path.points.size() == 1 && path.points[0] == gcode.getPositionXY() && (z + path.z_offset) == gcode.getPositionZ()) { // ignore travel moves to the current location to avoid needless change of acceleration/jerk continue; @@ -2071,7 +2079,7 @@ void LayerPlan::writeGCode(GCodeExport& gcode) // Prevent the final travel(s) from resetting to the 'previous' layer height. gcode.setZ(final_travel_z); } - for (unsigned int point_idx = 0; point_idx < path.points.size() - 1; point_idx++) + for (size_t point_idx = 0; point_idx + 1 < path.points.size(); point_idx++) { gcode.writeTravel(path.points[point_idx], speed); } diff --git a/src/LayerPlanBuffer.cpp b/src/LayerPlanBuffer.cpp index 69f858d6ca..48437cb56c 100644 --- a/src/LayerPlanBuffer.cpp +++ b/src/LayerPlanBuffer.cpp @@ -3,6 +3,8 @@ #include "LayerPlanBuffer.h" +#include + #include "Application.h" //To flush g-code through the communication channel. #include "ExtruderTrain.h" #include "FffProcessor.h" @@ -11,8 +13,6 @@ #include "communication/Communication.h" //To flush g-code through the communication channel. #include "gcodeExport.h" -#include - namespace cura { @@ -429,7 +429,7 @@ void LayerPlanBuffer::insertFinalPrintTempCommand(std::vector& ex return; } - assert((time_window >= 0 || last_extruder_plan.estimates.material == 0) && "Time window should always be positive if we actually extrude"); + assert((time_window >= -0.001 || last_extruder_plan.estimates.material == 0) && "Time window should always be positive if we actually extrude"); // ,layer change . // : ,precool command ,layer change . diff --git a/src/PrimeTower.cpp b/src/PrimeTower.cpp index 2d65bd58a6..7c634c4651 100644 --- a/src/PrimeTower.cpp +++ b/src/PrimeTower.cpp @@ -3,6 +3,11 @@ #include "PrimeTower.h" +#include +#include + +#include + #include "Application.h" //To get settings. #include "ExtruderTrain.h" #include "LayerPlan.h" @@ -14,11 +19,6 @@ #include "raft.h" #include "sliceDataStorage.h" -#include - -#include -#include - #define CIRCLE_RESOLUTION 32 // The number of vertices in each circle. #define ARC_RESOLUTION 4 // The number of segments in each arc of a wheel @@ -50,6 +50,7 @@ PrimeTower::PrimeTower() enabled = method != PrimeTowerMethod::NONE && scene.current_mesh_group->settings.get("prime_tower_min_volume") > 10 && scene.current_mesh_group->settings.get("prime_tower_size") > 10; + would_have_actual_tower = enabled; // Assume so for now. extruder_count = scene.extruders.size(); @@ -88,7 +89,13 @@ void PrimeTower::checkUsed(const SliceDataStorage& storage) void PrimeTower::generateGroundpoly() { - const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; + if (! enabled) + { + return; + } + + const Scene& scene = Application::getInstance().current_slice->scene; + const Settings& mesh_group_settings = scene.current_mesh_group->settings; const coord_t tower_size = mesh_group_settings.get("prime_tower_size"); const coord_t x = mesh_group_settings.get("prime_tower_position_x"); @@ -123,8 +130,15 @@ void PrimeTower::generatePaths_denseInfill(std::vector& cumulative_inse const Settings& mesh_group_settings = scene.current_mesh_group->settings; const coord_t layer_height = mesh_group_settings.get("layer_height"); const PrimeTowerMethod method = mesh_group_settings.get("prime_tower_mode"); - pattern_per_extruder.resize(extruder_count); - pattern_per_extruder_layer0.resize(extruder_count); + const bool base_enabled = mesh_group_settings.get("prime_tower_brim_enable"); + const coord_t base_extra_radius = scene.settings.get("prime_tower_base_size"); + const bool has_raft = mesh_group_settings.get("adhesion_type") == EPlatformAdhesion::RAFT; + const coord_t base_height = std::max(scene.settings.get("prime_tower_base_height"), has_raft ? layer_height : 0); + const double base_curve_magnitude = mesh_group_settings.get("prime_tower_base_curve_magnitude"); + const coord_t line_width = scene.extruders[extruder_order.front()].settings.get("prime_tower_line_width"); + + prime_moves.resize(extruder_count); + base_extra_moves.resize(extruder_count); coord_t cumulative_inset = 0; // Each tower shape is going to be printed inside the other. This is the inset we're doing for each extruder. for (size_t extruder_nr : extruder_order) @@ -133,7 +147,7 @@ void PrimeTower::generatePaths_denseInfill(std::vector& cumulative_inse const coord_t required_volume = MM3_2INT(scene.extruders[extruder_nr].settings.get("prime_tower_min_volume")); const Ratio flow = scene.extruders[extruder_nr].settings.get("prime_tower_flow"); coord_t current_volume = 0; - ExtrusionMoves& pattern = pattern_per_extruder[extruder_nr]; + Polygons& pattern = prime_moves[extruder_nr]; // Create the walls of the prime tower. unsigned int wall_nr = 0; @@ -141,7 +155,7 @@ void PrimeTower::generatePaths_denseInfill(std::vector& cumulative_inse { // Create a new polygon with an offset from the outer polygon. Polygons polygons = outer_poly.offset(-cumulative_inset - wall_nr * line_width - line_width / 2); - pattern.polygons.add(polygons); + pattern.add(polygons); current_volume += polygons.polygonLength() * line_width * layer_height * flow; if (polygons.empty()) // Don't continue. We won't ever reach the required volume because it doesn't fit. { @@ -149,29 +163,37 @@ void PrimeTower::generatePaths_denseInfill(std::vector& cumulative_inse } } - // Only the most inside extruder needs to fill the inside of the prime tower - if (extruder_nr != extruder_order.back() || method != PrimeTowerMethod::DEFAULT) + // The most outside extruder is used for the base + if (extruder_nr == extruder_order.front() && (base_enabled || has_raft) && base_extra_radius > 0 && base_height > 0) { - pattern_per_extruder_layer0 = pattern_per_extruder; - } - else - { - // Generate the pattern for the first layer. - coord_t line_width_layer0 = line_width * scene.extruders[extruder_nr].settings.get("initial_layer_line_width_factor"); - ExtrusionMoves& pattern_layer0 = pattern_per_extruder_layer0[extruder_nr]; - - // Generate a concentric infill pattern in the form insets for the prime tower's first layer instead of using - // the infill pattern because the infill pattern tries to connect polygons in different insets which causes the - // first layer of the prime tower to not stick well. - Polygons inset = outer_poly.offset(-cumulative_inset - line_width_layer0 / 2); - while (! inset.empty()) + for (coord_t z = 0; z < base_height; z += layer_height) { - pattern_layer0.polygons.add(inset); - inset = inset.offset(-line_width_layer0); + double brim_radius_factor = std::pow((1.0 - static_cast(z) / base_height), base_curve_magnitude); + coord_t extra_radius = base_extra_radius * brim_radius_factor; + size_t extra_rings = extra_radius / line_width; + if (extra_rings == 0) + { + break; + } + extra_radius = line_width * extra_rings; + outer_poly_base.push_back(outer_poly.offset(extra_radius)); + + base_extra_moves[extruder_nr].push_back(PolygonUtils::generateOutset(outer_poly, extra_rings, line_width)); } } + cumulative_inset += wall_nr * line_width; cumulative_insets.push_back(cumulative_inset); + + // Only the most inside extruder needs to fill the inside of the prime tower + if (extruder_nr == extruder_order.back()) + { + Polygons pattern = PolygonUtils::generateInset(outer_poly, line_width, cumulative_inset); + if (! pattern.empty()) + { + base_extra_moves[extruder_nr].push_back(pattern); + } + } } } @@ -297,7 +319,7 @@ void PrimeTower::addToGcode( } const LayerIndex layer_nr = gcode_layer.getLayerNr(); - if (layer_nr < 0 || layer_nr > storage.max_print_height_second_to_last_extruder + 1) + if (layer_nr > storage.max_print_height_second_to_last_extruder + 1) { return; } @@ -379,13 +401,26 @@ void PrimeTower::addToGcode( void PrimeTower::addToGcode_denseInfill(LayerPlan& gcode_layer, const size_t extruder_nr) const { - const ExtrusionMoves& pattern - = (gcode_layer.getLayerNr() == -static_cast(Raft::getFillerLayerCount())) ? pattern_per_extruder_layer0[extruder_nr] : pattern_per_extruder[extruder_nr]; + const size_t raft_total_extra_layers = Raft::getTotalExtraLayers(); + const bool adhesion_raft = raft_total_extra_layers > 0; + LayerIndex absolute_layer_number = gcode_layer.getLayerNr() + raft_total_extra_layers; - const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr]; + if (! adhesion_raft || absolute_layer_number > 0) + { + // Actual prime pattern + const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr]; + const Polygons& pattern = prime_moves[extruder_nr]; + gcode_layer.addPolygonsByOptimizer(pattern, config); + } - gcode_layer.addPolygonsByOptimizer(pattern.polygons, config); - gcode_layer.addLinesByOptimizer(pattern.lines, config, SpaceFillType::Lines); + const std::vector& pattern_extra_brim = base_extra_moves[extruder_nr]; + if (absolute_layer_number < pattern_extra_brim.size()) + { + // Extra rings for stronger base + const GCodePathConfig& config = gcode_layer.configs_storage.prime_tower_config_per_extruder[extruder_nr]; + const Polygons& pattern = pattern_extra_brim[absolute_layer_number]; + gcode_layer.addPolygonsByOptimizer(pattern, config); + } } void PrimeTower::addToGcode_optimizedInfill(LayerPlan& gcode_layer, const std::vector& extruders_to_prime, const size_t current_extruder) const @@ -504,16 +539,34 @@ std::vector PrimeTower::findExtrudersSparseInfill( void PrimeTower::subtractFromSupport(SliceDataStorage& storage) { - const Polygons outside_polygon = outer_poly.getOutsidePolygons(); - AABB outside_polygon_boundary_box(outside_polygon); for (size_t layer = 0; layer <= (size_t)storage.max_print_height_second_to_last_extruder + 1 && layer < storage.support.supportLayers.size(); layer++) { + const Polygons outside_polygon = getOuterPoly(layer).getOutsidePolygons(); + AABB outside_polygon_boundary_box(outside_polygon); SupportLayer& support_layer = storage.support.supportLayers[layer]; // take the differences of the support infill parts and the prime tower area support_layer.excludeAreasFromSupportInfillAreas(outside_polygon, outside_polygon_boundary_box); } } +const Polygons& PrimeTower::getOuterPoly(const LayerIndex& layer_nr) const +{ + const LayerIndex absolute_layer_nr = layer_nr + Raft::getTotalExtraLayers(); + if (absolute_layer_nr < outer_poly_base.size()) + { + return outer_poly_base[absolute_layer_nr]; + } + else + { + return outer_poly; + } +} + +const Polygons& PrimeTower::getGroundPoly() const +{ + return getOuterPoly(-Raft::getTotalExtraLayers()); +} + void PrimeTower::gotoStartLocation(LayerPlan& gcode_layer, const int extruder_nr) const { int current_start_location_idx = ((((extruder_nr + 1) * gcode_layer.getLayerNr()) % number_of_prime_tower_start_locations) + number_of_prime_tower_start_locations) diff --git a/src/SkeletalTrapezoidationGraph.cpp b/src/SkeletalTrapezoidationGraph.cpp index ed8cc1035d..4657b8ee97 100644 --- a/src/SkeletalTrapezoidationGraph.cpp +++ b/src/SkeletalTrapezoidationGraph.cpp @@ -3,17 +3,18 @@ #include "SkeletalTrapezoidationGraph.h" -#include +#include "utils/linearAlg2D.h" +#include "utils/macros.h" #include -#include "utils/linearAlg2D.h" -#include "utils/macros.h" +#include namespace cura { -STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) : HalfEdge(data) +STHalfEdge::STHalfEdge(SkeletalTrapezoidationEdge data) + : HalfEdge(data) { } @@ -131,7 +132,8 @@ STHalfEdge* STHalfEdge::getNextUnconnected() return result->twin; } -STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) : HalfEdgeNode(data, p) +STHalfEdgeNode::STHalfEdgeNode(SkeletalTrapezoidationJoint data, Point p) + : HalfEdgeNode(data, p) { } @@ -223,7 +225,10 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) } }; - auto should_collapse = [snap_dist](node_t* a, node_t* b) { return shorterThen(a->p - b->p, snap_dist); }; + auto should_collapse = [snap_dist](node_t* a, node_t* b) + { + return shorterThen(a->p - b->p, snap_dist); + }; for (auto edge_it = edges.begin(); edge_it != edges.end();) { @@ -253,10 +258,6 @@ void SkeletalTrapezoidationGraph::collapseSmallEdges(coord_t snap_dist) { edge_from_3->from = quad_mid->from; edge_from_3->twin->to = quad_mid->from; - if (count > 50) - { - std::cerr << edge_from_3->from->p << " - " << edge_from_3->to->p << '\n'; - } if (++count > 1000) { break; diff --git a/src/SkirtBrim.cpp b/src/SkirtBrim.cpp index 6aed96d7fe..4b77b449d3 100644 --- a/src/SkirtBrim.cpp +++ b/src/SkirtBrim.cpp @@ -3,6 +3,8 @@ #include "SkirtBrim.h" +#include + #include "Application.h" #include "ExtruderTrain.h" #include "Slice.h" @@ -13,8 +15,6 @@ #include "utils/PolylineStitcher.h" #include "utils/Simplify.h" //Simplifying the brim/skirt at every inset. -#include - namespace cura { @@ -110,39 +110,16 @@ std::vector SkirtBrim::generateBrimOffsetPlan(std::vector SkirtBrim::generatePrimeTowerBrimForSkirtAdhesionOffsetPlan() -{ - std::vector prime_brim_offsets; - - const Settings& global_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; - const bool prime_tower_brim_enable = global_settings.get("prime_tower_brim_enable"); - if (adhesion_type == EPlatformAdhesion::SKIRT && prime_tower_brim_enable && storage.primeTower.enabled) - { - const int extruder_nr = storage.primeTower.extruder_order[0]; - const ExtruderTrain& extruder = extruders[extruder_nr]; - int line_count = extruder.settings.get("brim_line_count"); - coord_t gap = extruder.settings.get("brim_gap"); - for (int line_idx = 0; line_idx < line_count; line_idx++) - { - const bool is_last = line_idx == line_count - 1; - coord_t offset = gap + line_widths[extruder_nr] / 2 + line_widths[extruder_nr] * line_idx; - prime_brim_offsets.emplace_back(&storage.primeTower.outer_poly, external_polys_only[extruder_nr], offset, offset, line_idx, extruder_nr, is_last); - } - } - - std::sort(prime_brim_offsets.begin(), prime_brim_offsets.end(), OffsetSorter); - return prime_brim_offsets; -} - void SkirtBrim::generate() { std::vector starting_outlines(extruder_count); std::vector all_brim_offsets = generateBrimOffsetPlan(starting_outlines); - std::vector prime_brim_offsets_for_skirt = generatePrimeTowerBrimForSkirtAdhesionOffsetPlan(); constexpr LayerIndex layer_nr = 0; constexpr bool include_support = true; - Polygons covered_area = storage.getLayerOutlines(layer_nr, include_support, /*include_prime_tower*/ true, /*external_polys_only*/ false); + const bool include_prime_tower = adhesion_type == EPlatformAdhesion::SKIRT; + const bool has_prime_tower = storage.primeTower.enabled; + Polygons covered_area = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, /*external_polys_only*/ false); std::vector allowed_areas_per_extruder(extruder_count); for (int extruder_nr = 0; extruder_nr < extruder_count; extruder_nr++) @@ -159,14 +136,11 @@ void SkirtBrim::generate() // so that the brim lines don't overlap with the holes by half the line width allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(getInternalHoleExclusionArea(covered_area, extruder_nr)); } - } - // Note that the brim generated here for the prime-tower is _only_ when the rest if the model uses skirt. - // If everything uses brim to begin with, _including_ the prime-tower, it's not generated here, but along the rest. - if (! prime_brim_offsets_for_skirt.empty()) - { - // Note that his ignores the returned lengths: Less 'correct' in a sense, will be predictable for the user. - generatePrimaryBrim(prime_brim_offsets_for_skirt, covered_area, allowed_areas_per_extruder); + if (has_prime_tower) + { + allowed_areas_per_extruder[extruder_nr] = allowed_areas_per_extruder[extruder_nr].difference(storage.primeTower.getGroundPoly()); + } } // Apply 'approximate convex hull' if the adhesion is skirt _after_ any skirt but also prime-tower-brim adhesion. @@ -376,12 +350,12 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) const int primary_line_count = line_count[reference_extruder_nr]; const bool external_only = adhesion_type == EPlatformAdhesion::SKIRT || external_polys_only[reference_extruder_nr]; // Whether to include holes or not. Skirt doesn't have any holes. + const bool has_prime_tower = storage.primeTower.enabled; const LayerIndex layer_nr = 0; if (adhesion_type == EPlatformAdhesion::SKIRT) { constexpr bool include_support = true; - const bool skirt_around_prime_tower_brim = storage.primeTower.enabled && global_settings.get("prime_tower_brim_enable"); - const bool include_prime_tower = ! skirt_around_prime_tower_brim; // include manually otherwise + const bool include_prime_tower = ! has_prime_tower; // include manually otherwise first_layer_outline = Polygons(); int skirt_height = 0; @@ -403,15 +377,9 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) } } - - if (skirt_around_prime_tower_brim) + if (has_prime_tower) { - const int prime_tower_brim_extruder_nr = storage.primeTower.extruder_order[0]; - const ExtruderTrain& prime_tower_brim_extruder = extruders[prime_tower_brim_extruder_nr]; - int line_count = prime_tower_brim_extruder.settings.get("brim_line_count"); - coord_t tower_gap = prime_tower_brim_extruder.settings.get("brim_gap"); - coord_t brim_width = tower_gap + line_count * line_widths[prime_tower_brim_extruder_nr]; - first_layer_outline = first_layer_outline.unionPolygons(storage.primeTower.outer_poly.offset(brim_width)); + first_layer_outline = first_layer_outline.unionPolygons(storage.primeTower.getGroundPoly()); } Polygons shields; @@ -435,7 +403,7 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) else { // add brim underneath support by removing support where there's brim around the model constexpr bool include_support = false; // Include manually below. - constexpr bool include_prime_tower = false; // Include manually below. + constexpr bool include_prime_tower = false; // Not included. constexpr bool external_outlines_only = false; // Remove manually below. first_layer_outline = storage.getLayerOutlines(layer_nr, include_support, include_prime_tower, external_outlines_only, extruder_nr); first_layer_outline = first_layer_outline.unionPolygons(); // To guard against overlapping outlines, which would produce holes according to the even-odd rule. @@ -480,10 +448,6 @@ Polygons SkirtBrim::getFirstLayerOutline(const int extruder_nr /* = -1 */) first_layer_outline.add(support_layer.support_bottom); first_layer_outline.add(support_layer.support_roof); } - if (storage.primeTower.enabled && global_settings.get("prime_tower_brim_enable") && (extruder_nr == -1 || int(storage.primeTower.extruder_order[0]) == extruder_nr)) - { - first_layer_outline.add(storage.primeTower.outer_poly); // don't remove parts of the prime tower, but make a brim for it - } } constexpr coord_t join_distance = 20; first_layer_outline = first_layer_outline.offset(join_distance).offset(-join_distance); // merge adjacent models into single polygon @@ -667,8 +631,8 @@ void SkirtBrim::generateSupportBrim() } storage.support_brim.add(brim_line); - - const coord_t length = skirt_brim_length + storage.support_brim.polygonLength(); + // In case of adhesion::NONE length of support brim is only the length of the brims formed for the support + const coord_t length = (adhesion_type == EPlatformAdhesion::NONE) ? skirt_brim_length : skirt_brim_length + storage.support_brim.polygonLength(); if (skirt_brim_number + 1 >= line_count && length > 0 && length < minimal_length) // Make brim or skirt have more lines when total length is too small. { line_count++; diff --git a/src/SupportInfillPart.cpp b/src/SupportInfillPart.cpp index b53d50e251..ae0b8cf7c1 100644 --- a/src/SupportInfillPart.cpp +++ b/src/SupportInfillPart.cpp @@ -8,12 +8,13 @@ using namespace cura; -SupportInfillPart::SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, int inset_count_to_generate, coord_t custom_line_distance) +SupportInfillPart::SupportInfillPart(const PolygonsPart& outline, coord_t support_line_width, bool use_fractional_config, 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) + , use_fractional_config(use_fractional_config) { infill_area_per_combine_per_density.clear(); } diff --git a/src/TreeModelVolumes.cpp b/src/TreeModelVolumes.cpp index 6ddcef2c93..4604967baf 100644 --- a/src/TreeModelVolumes.cpp +++ b/src/TreeModelVolumes.cpp @@ -3,6 +3,11 @@ #include "TreeModelVolumes.h" +#include +#include +#include +#include + #include "TreeSupport.h" #include "TreeSupportEnums.h" #include "progress/Progress.h" @@ -10,11 +15,6 @@ #include "utils/ThreadPool.h" #include "utils/algorithm.h" -#include -#include -#include -#include - namespace cura { @@ -150,7 +150,7 @@ TreeModelVolumes::TreeModelVolumes( if (storage.primeTower.enabled) { - anti_overhang_[layer_idx].add(storage.primeTower.outer_poly); + anti_overhang_[layer_idx].add(storage.primeTower.getGroundPoly()); } anti_overhang_[layer_idx] = anti_overhang_[layer_idx].unionPolygons(); }); diff --git a/src/TreeSupport.cpp b/src/TreeSupport.cpp index c226588ebe..bf876ec582 100644 --- a/src/TreeSupport.cpp +++ b/src/TreeSupport.cpp @@ -3,6 +3,20 @@ #include "TreeSupport.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + #include "Application.h" //To get settings. #include "TreeSupportTipGenerator.h" #include "TreeSupportUtils.h" @@ -18,20 +32,6 @@ #include "utils/polygonUtils.h" //For moveInside. #include "utils/section_type.h" -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - namespace cura { @@ -2226,10 +2226,9 @@ void TreeSupport::finalizeInterfaceAndSupportAreas(std::vector& suppor support_layer_storage[layer_idx] = support_layer_storage[layer_idx].difference(floor_layer.offset(10)); // Subtract the support floor from the normal support. } - for (PolygonsPart part : support_layer_storage[layer_idx].splitIntoParts(true)) // Convert every part into a PolygonsPart for the support. - { - storage.support.supportLayers[layer_idx].support_infill_parts.emplace_back(part, config.support_line_width, config.support_wall_count); - } + constexpr bool convert_every_part = true; // Convert every part into a PolygonsPart for the support. + storage.support.supportLayers[layer_idx] + .fillInfillParts(layer_idx, support_layer_storage, config.support_line_width, config.support_wall_count, config.maximum_move_distance, convert_every_part); { std::lock_guard critical_section_progress(critical_sections); diff --git a/src/TreeSupportTipGenerator.cpp b/src/TreeSupportTipGenerator.cpp index 99677f9c09..094a819544 100644 --- a/src/TreeSupportTipGenerator.cpp +++ b/src/TreeSupportTipGenerator.cpp @@ -3,6 +3,17 @@ #include "TreeSupportTipGenerator.h" +#include +#include +#include +#include + +#include +#include +#include +#include +#include + #include "Application.h" //To get settings. #include "TreeSupportUtils.h" #include "infill/SierpinskiFillProvider.h" @@ -13,17 +24,6 @@ #include "utils/math.h" //For round_up_divide and PI. #include "utils/polygonUtils.h" //For moveInside. -#include -#include -#include -#include -#include - -#include -#include -#include -#include - namespace cura { @@ -1164,10 +1164,8 @@ void TreeSupportTipGenerator::generateTips( { 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); - } + storage.support.supportLayers[layer_idx] + .fillInfillParts(layer_idx, support_roof_drawn, config.support_line_width, support_roof_line_distance, config.maximum_move_distance); placed_support_lines_support_areas[layer_idx].add(TreeSupportUtils::generateSupportInfillLines( support_roof_drawn[layer_idx], config, @@ -1186,6 +1184,18 @@ void TreeSupportTipGenerator::generateTips( } }); + cura::parallel_for( + 1, + mesh.overhang_areas.size() - z_distance_delta, + [&](const LayerIndex layer_idx) + { + if (layer_idx > 0) + { + storage.support.supportLayers[layer_idx].support_fractional_roof.add( + storage.support.supportLayers[layer_idx].support_roof.difference(storage.support.supportLayers[layer_idx + 1].support_roof)); + } + }); + removeUselessAddedPoints(new_tips, storage, additional_support_areas); for (auto [layer_idx, tips_on_layer] : new_tips | ranges::views::enumerate) diff --git a/src/gcodeExport.cpp b/src/gcodeExport.cpp index caff200811..2ef08a0474 100644 --- a/src/gcodeExport.cpp +++ b/src/gcodeExport.cpp @@ -266,7 +266,6 @@ std::string GCodeExport::getFileHeader( break; default: prefix << ";FLAVOR:" << flavorToString(flavor) << new_line; - prefix << ";TARGET_MACHINE.NAME:" << transliterate(machine_name) << new_line; prefix << ";TIME:" << ((print_time) ? static_cast(*print_time) : 6666) << new_line; if (flavor == EGCodeFlavor::ULTIGCODE) { @@ -309,6 +308,7 @@ std::string GCodeExport::getFileHeader( prefix << ";MAXX:" << INT2MM(total_bounding_box.max.x) << new_line; prefix << ";MAXY:" << INT2MM(total_bounding_box.max.y) << new_line; prefix << ";MAXZ:" << INT2MM(total_bounding_box.max.z) << new_line; + prefix << ";TARGET_MACHINE.NAME:" << transliterate(machine_name) << new_line; } return prefix.str(); diff --git a/src/infill.cpp b/src/infill.cpp index 9137aa66ca..6da3c25c4f 100644 --- a/src/infill.cpp +++ b/src/infill.cpp @@ -60,14 +60,13 @@ Polygons Infill::generateWallToolPaths( const coord_t infill_overlap, const Settings& settings, int layer_idx, - SectionType section_type, - const bool is_bridge_skin) + SectionType section_type) { outer_contour = outer_contour.offset(infill_overlap); scripta::log("infill_outer_contour", outer_contour, section_type, layer_idx, scripta::CellVDI{ "infill_overlap", infill_overlap }); Polygons inner_contour; - if ((wall_line_count > 0) && (! is_bridge_skin)) + if (wall_line_count > 0) { constexpr coord_t wall_0_inset = 0; // Don't apply any outer wall inset for these. That's just for the outer wall. WallToolPaths wall_toolpaths(outer_contour, line_width, wall_line_count, wall_0_inset, settings, layer_idx, section_type); @@ -91,15 +90,14 @@ void Infill::generate( const std::shared_ptr& cross_fill_provider, const std::shared_ptr& lightning_trees, const SliceMeshStorage* mesh, - const Polygons& prevent_small_exposed_to_air, - const bool is_bridge_skin) + const Polygons& prevent_small_exposed_to_air) { if (outer_contour.empty()) { return; } - inner_contour = generateWallToolPaths(toolpaths, outer_contour, wall_line_count, infill_line_width, infill_overlap, settings, layer_idx, section_type, is_bridge_skin); + inner_contour = generateWallToolPaths(toolpaths, outer_contour, wall_line_count, infill_line_width, infill_overlap, settings, layer_idx, section_type); scripta::log("infill_inner_contour_0", inner_contour, section_type, layer_idx); // It does not make sense to print a pattern in a small region. So the infill region diff --git a/src/raft.cpp b/src/raft.cpp index 96c81be90b..e13836b43f 100644 --- a/src/raft.cpp +++ b/src/raft.cpp @@ -1,15 +1,15 @@ -//Copyright (c) 2022 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2022 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. + +#include "raft.h" #include #include "Application.h" //To get settings. #include "ExtruderTrain.h" -#include "raft.h" #include "Slice.h" -#include "sliceDataStorage.h" -#include "support.h" #include "settings/EnumSettings.h" //For EPlatformAdhesion. +#include "sliceDataStorage.h" #include "utils/math.h" namespace cura @@ -21,24 +21,27 @@ void Raft::generate(SliceDataStorage& storage) const Settings& settings = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("raft_base_extruder_nr").settings; const coord_t distance = settings.get("raft_margin"); constexpr bool include_support = true; - constexpr bool dont_include_prime_tower = false; // Prime tower raft will be handled separately in 'storage.primeRaftOutline'; see below. + constexpr bool dont_include_prime_tower = false; // Prime tower raft will be handled separately in 'storage.primeRaftOutline'; see below. storage.raftOutline = storage.getLayerOutlines(0, include_support, dont_include_prime_tower).offset(distance, ClipperLib::jtRound); const coord_t shield_line_width_layer0 = settings.get("skirt_brim_line_width"); if (storage.draft_protection_shield.size() > 0) { - Polygons draft_shield_raft = storage.draft_protection_shield.offset(shield_line_width_layer0) // start half a line width outside shield - .difference(storage.draft_protection_shield.offset(-distance - shield_line_width_layer0 / 2, ClipperLib::jtRound)); // end distance inside shield + Polygons draft_shield_raft + = storage.draft_protection_shield + .offset(shield_line_width_layer0) // start half a line width outside shield + .difference(storage.draft_protection_shield.offset(-distance - shield_line_width_layer0 / 2, ClipperLib::jtRound)); // end distance inside shield storage.raftOutline = storage.raftOutline.unionPolygons(draft_shield_raft); } if (storage.oozeShield.size() > 0 && storage.oozeShield[0].size() > 0) { const Polygons& ooze_shield = storage.oozeShield[0]; - Polygons ooze_shield_raft = ooze_shield.offset(shield_line_width_layer0) // start half a line width outside shield + Polygons ooze_shield_raft = ooze_shield + .offset(shield_line_width_layer0) // start half a line width outside shield .difference(ooze_shield.offset(-distance - shield_line_width_layer0 / 2, ClipperLib::jtRound)); // end distance inside shield storage.raftOutline = storage.raftOutline.unionPolygons(ooze_shield_raft); } - if(settings.get("raft_remove_inside_corners")) + if (settings.get("raft_remove_inside_corners")) { storage.raftOutline.makeConvex(); } @@ -61,26 +64,17 @@ void Raft::generate(SliceDataStorage& storage) return; } } - - storage.primeRaftOutline = storage.primeTower.outer_poly.offset(distance, ClipperLib::jtRound); - // NOTE: the raft doesn't take the prime tower brim into account, because it's (currently) not being printed when printing a raft - if (settings.get("raft_remove_inside_corners")) - { - storage.primeRaftOutline = storage.primeRaftOutline.unionPolygons(storage.raftOutline); - storage.primeRaftOutline.makeConvex(); - } - storage.primeRaftOutline = storage.primeRaftOutline.difference(storage.raftOutline); // In case of overlaps. } coord_t Raft::getTotalThickness() { - const Settings& mesh_group_settings =Application::getInstance().current_slice->scene.current_mesh_group->settings; + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const ExtruderTrain& base_train = mesh_group_settings.get("raft_base_extruder_nr"); const ExtruderTrain& interface_train = mesh_group_settings.get("raft_interface_extruder_nr"); const ExtruderTrain& surface_train = mesh_group_settings.get("raft_surface_extruder_nr"); return base_train.settings.get("raft_base_thickness") - + interface_train.settings.get("raft_interface_layers") * interface_train.settings.get("raft_interface_thickness") - + surface_train.settings.get("raft_surface_layers") * surface_train.settings.get("raft_surface_thickness"); + + interface_train.settings.get("raft_interface_layers") * interface_train.settings.get("raft_interface_thickness") + + surface_train.settings.get("raft_surface_layers") * surface_train.settings.get("raft_surface_thickness"); } coord_t Raft::getZdiffBetweenRaftAndLayer0() @@ -109,13 +103,14 @@ coord_t Raft::getFillerLayerHeight() const coord_t normal_layer_height = mesh_group_settings.get("layer_height"); return normal_layer_height; } + return round_divide(getZdiffBetweenRaftAndLayer0(), getFillerLayerCount()); } size_t Raft::getTotalExtraLayers() { - const Settings& mesh_group_settings =Application::getInstance().current_slice->scene.current_mesh_group->settings; + const Settings& mesh_group_settings = Application::getInstance().current_slice->scene.current_mesh_group->settings; const ExtruderTrain& base_train = mesh_group_settings.get("raft_base_extruder_nr"); const ExtruderTrain& interface_train = mesh_group_settings.get("raft_interface_extruder_nr"); const ExtruderTrain& surface_train = mesh_group_settings.get("raft_surface_extruder_nr"); @@ -127,4 +122,4 @@ size_t Raft::getTotalExtraLayers() } -}//namespace cura +} // namespace cura diff --git a/src/settings/MeshPathConfigs.cpp b/src/settings/MeshPathConfigs.cpp index 942084a231..d91595ce37 100644 --- a/src/settings/MeshPathConfigs.cpp +++ b/src/settings/MeshPathConfigs.cpp @@ -28,6 +28,26 @@ MeshPathConfigs::MeshPathConfigs(const SliceMeshStorage& mesh, const coord_t lay .speed_derivatives = { .speed = mesh.settings.get("speed_wall_x"), .acceleration = mesh.settings.get("acceleration_wall_x"), .jerk = mesh.settings.get("jerk_wall_x") } } + , inset0_roofing_config{ .type = PrintFeatureType::OuterWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_0") + * line_width_factor_per_extruder[mesh.settings.get("wall_0_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow + = mesh.settings.get("wall_0_material_flow_roofing") * (layer_nr == 0 ? mesh.settings.get("wall_0_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_0_roofing"), + .acceleration = mesh.settings.get("acceleration_wall_0_roofing"), + .jerk = mesh.settings.get("jerk_wall_0_roofing") } } + , insetX_roofing_config{ .type = PrintFeatureType::InnerWall, + .line_width = static_cast( + mesh.settings.get("wall_line_width_x") + * line_width_factor_per_extruder[mesh.settings.get("wall_x_extruder_nr").extruder_nr]), + .layer_thickness = layer_thickness, + .flow + = mesh.settings.get("wall_x_material_flow_roofing") * (layer_nr == 0 ? mesh.settings.get("wall_x_material_flow_layer_0") : Ratio{ 1.0 }), + .speed_derivatives = { .speed = mesh.settings.get("speed_wall_x_roofing"), + .acceleration = mesh.settings.get("acceleration_wall_x_roofing"), + .jerk = mesh.settings.get("jerk_wall_x_roofing") } } , bridge_inset0_config{ .type = PrintFeatureType::OuterWall, .line_width = static_cast( mesh.settings.get("wall_line_width_0") diff --git a/src/settings/PathConfigStorage.cpp b/src/settings/PathConfigStorage.cpp index f6e6d1b703..2ecf94eb70 100644 --- a/src/settings/PathConfigStorage.cpp +++ b/src/settings/PathConfigStorage.cpp @@ -150,6 +150,21 @@ PathConfigStorage::PathConfigStorage(const SliceDataStorage& storage, const Laye { handleInitialLayerSpeedup(storage, layer_nr, initial_speedup_layer_count); } + + const auto layer_height = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height"); + const auto support_top_distance = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("support_top_distance"); + const coord_t leftover_support_distance = support_top_distance % layer_height; + + support_fractional_infill_config = support_infill_config; // copy + for (auto& config : support_fractional_infill_config) + { + config.z_offset = -leftover_support_distance; + config.flow *= Ratio(layer_height - leftover_support_distance, layer_height); + } + + support_fractional_roof_config = support_roof_config; // copy + support_fractional_roof_config.z_offset = -leftover_support_distance; + support_fractional_roof_config.flow *= Ratio(layer_height - leftover_support_distance, layer_height); } void MeshPathConfigs::smoothAllSpeeds(const SpeedDerivatives& first_layer_config, const LayerIndex layer_nr, const LayerIndex max_speed_layer) diff --git a/src/skin.cpp b/src/skin.cpp index 227591a8c4..28fc60c238 100644 --- a/src/skin.cpp +++ b/src/skin.cpp @@ -346,8 +346,6 @@ void SkinInfillAreaComputation::generateRoofingFillAndSkinFill(SliceLayerPart& p */ Polygons SkinInfillAreaComputation::generateFilledAreaAbove(SliceLayerPart& part, size_t roofing_layer_count) { - const size_t wall_idx = std::min(size_t(2), mesh.settings.get("wall_line_count")); - Polygons filled_area_above = getOutlineOnLayer(part, layer_nr + roofing_layer_count); if (! no_small_gaps_heuristic) { diff --git a/src/sliceDataStorage.cpp b/src/sliceDataStorage.cpp index 4533fbb8be..fbd4a5d1a9 100644 --- a/src/sliceDataStorage.cpp +++ b/src/sliceDataStorage.cpp @@ -3,6 +3,8 @@ #include "sliceDataStorage.h" +#include + #include "Application.h" //To get settings. #include "ExtruderTrain.h" #include "FffProcessor.h" //To create a mesh group with if none is provided. @@ -14,8 +16,6 @@ #include "raft.h" #include "utils/math.h" //For PI. -#include - namespace cura { @@ -333,7 +333,7 @@ Polygons { if (primeTower.enabled) { - total.add(primeTower.outer_poly); + total.add(primeTower.getOuterPoly(layer_nr)); } } return total; @@ -705,4 +705,27 @@ void SupportLayer::excludeAreasFromSupportInfillAreas(const Polygons& exclude_po } } +void SupportLayer::fillInfillParts( + const LayerIndex layer_nr, + const std::vector& support_fill_per_layer, + const coord_t support_line_width, + const coord_t wall_line_count, + const coord_t grow_layer_above /*has default 0*/, + const bool unionAll /*has default false*/) +{ + const Polygons& support_this_layer = support_fill_per_layer[layer_nr]; + const Polygons& support_layer_above + = (layer_nr + 1) >= support_fill_per_layer.size() || layer_nr <= 0 ? Polygons() : support_fill_per_layer[layer_nr + 1].offset(grow_layer_above); + const auto all_support_areas_in_layer = { support_this_layer.difference(support_layer_above), support_this_layer.intersection(support_layer_above) }; + bool use_fractional_config = true; + for (auto& support_areas : all_support_areas_in_layer) + { + for (const PolygonsPart& island_outline : support_areas.splitIntoParts(unionAll)) + { + support_infill_parts.emplace_back(island_outline, support_line_width, use_fractional_config, wall_line_count); + } + use_fractional_config = false; + } +} + } // namespace cura diff --git a/src/support.cpp b/src/support.cpp index 188f2b486d..6bae947fa2 100644 --- a/src/support.cpp +++ b/src/support.cpp @@ -3,6 +3,21 @@ #include "support.h" +#include // sqrt, round +#include +#include // ifstream.good() +#include // pair + +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include "Application.h" //To get settings. #include "BoostInterface.hpp" #include "ExtruderTrain.h" @@ -24,21 +39,6 @@ #include "utils/math.h" #include "utils/views/get.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include // sqrt, round -#include -#include // ifstream.good() -#include // pair - namespace cura { @@ -106,7 +106,6 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts( { // The first layer will be printed with a grid pattern wall_line_count_this_layer++; } - assert(storage.support.supportLayers[layer_nr].support_infill_parts.empty() && "support infill part list is supposed to be uninitialized"); const Polygons& global_support_areas = global_support_areas_per_layer[layer_nr]; if (global_support_areas.size() == 0 || layer_nr < min_layer || layer_nr > max_layer) @@ -116,20 +115,14 @@ void AreaSupport::splitGlobalSupportAreasIntoSupportInfillParts( continue; } - std::vector support_islands = global_support_areas.splitIntoParts(); - for (const PolygonsPart& island_outline : support_islands) + coord_t support_line_width_here = support_line_width; + if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") != EPlatformAdhesion::RAFT) { - coord_t support_line_width_here = support_line_width; - if (layer_nr == 0 && mesh_group_settings.get("adhesion_type") != EPlatformAdhesion::RAFT) - { - support_line_width_here *= infill_extruder.settings.get("initial_layer_line_width_factor"); - } - // We don't generate insets and infill area for the parts yet because later the skirt/brim and prime - // tower will remove themselves from the support, so the outlines of the parts can be changed. - SupportInfillPart support_infill_part(island_outline, support_line_width_here, wall_line_count_this_layer); - - storage.support.supportLayers[layer_nr].support_infill_parts.push_back(support_infill_part); + support_line_width_here *= infill_extruder.settings.get("initial_layer_line_width_factor"); } + // We don't generate insets and infill area for the parts yet because later the skirt/brim and prime + // tower will remove themselves from the support, so the outlines of the parts can be changed. + storage.support.supportLayers[layer_nr].fillInfillParts(layer_nr, global_support_areas_per_layer, support_line_width_here, wall_line_count_this_layer); } } @@ -686,28 +679,26 @@ void AreaSupport::generateSupportAreas(SliceDataStorage& storage) } } - for (LayerIndex layer_idx = 0; layer_idx < storage.print_layer_count; layer_idx++) + for (Polygons& support_areas : global_support_areas_per_layer) { - Polygons& support_areas = global_support_areas_per_layer[layer_idx]; support_areas = support_areas.unionPolygons(); } // handle support interface - for (unsigned int mesh_idx = 0; mesh_idx < storage.meshes.size(); mesh_idx++) + for (auto& mesh : storage.meshes) { - SliceMeshStorage& mesh = *storage.meshes[mesh_idx]; - if (mesh.settings.get("infill_mesh") || mesh.settings.get("anti_overhang_mesh")) + if (mesh->settings.get("infill_mesh") || mesh->settings.get("anti_overhang_mesh")) { continue; } - if (mesh.settings.get("support_roof_enable")) + if (mesh->settings.get("support_roof_enable")) { - generateSupportRoof(storage, mesh, global_support_areas_per_layer); + generateSupportRoof(storage, *mesh, global_support_areas_per_layer); } - if (mesh.settings.get("support_bottom_enable")) + if (mesh->settings.get("support_bottom_enable")) { - generateSupportBottom(storage, mesh, global_support_areas_per_layer); + generateSupportBottom(storage, *mesh, global_support_areas_per_layer); } } @@ -794,7 +785,7 @@ void AreaSupport::generateOverhangAreasForMesh(SliceDataStorage& storage, SliceM // Don't generate overhang areas if the Z distance is higher than the objects we're generating support for. const coord_t layer_height = Application::getInstance().current_slice->scene.current_mesh_group->settings.get("layer_height"); const coord_t z_distance_top = mesh.settings.get("support_top_distance"); - const size_t z_distance_top_layers = round_up_divide(z_distance_top, layer_height) + 1; // Support must always be 1 layer below overhang. + const size_t z_distance_top_layers = (z_distance_top / layer_height) + 1; if (z_distance_top_layers + 1 > storage.print_layer_count) { return; @@ -1059,7 +1050,7 @@ void AreaSupport::generateSupportAreasForMesh( // early out const coord_t layer_thickness = mesh_group_settings.get("layer_height"); const coord_t z_distance_top = ((mesh.settings.get("support_roof_enable")) ? roof_settings : infill_settings).get("support_top_distance"); - const size_t layer_z_distance_top = round_up_divide(z_distance_top, layer_thickness) + 1; // support must always be 1 layer below overhang + const size_t layer_z_distance_top = (z_distance_top / layer_thickness) + 1; if (layer_z_distance_top + 1 > layer_count) { return; @@ -1805,7 +1796,7 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh const double minimum_roof_area = mesh.settings.get("minimum_roof_area"); std::vector& support_layers = storage.support.supportLayers; - for (LayerIndex layer_idx = 0; layer_idx < static_cast(support_layers.size() - z_distance_top); layer_idx++) + for (LayerIndex layer_idx = static_cast(support_layers.size() - z_distance_top) - 1; layer_idx >= 0; --layer_idx) { const LayerIndex top_layer_idx_above{ std::min(LayerIndex{ support_layers.size() - 1 }, LayerIndex{ layer_idx + roof_layer_count + z_distance_top }) @@ -1818,15 +1809,17 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh Polygons 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.add(roofs); + if (layer_idx > 0 && layer_idx < support_layers.size() - 1) + { + support_layers[layer_idx].support_fractional_roof.add(roofs.difference(support_layers[layer_idx + 1].support_roof)); + } scripta::log("support_interface_roofs", roofs, SectionType::SUPPORT, layer_idx); } // Remove support in between the support roof and the model. Subtracts the roof polygons from the support polygons on the layers above it. for (auto [layer_idx, support_layer] : support_layers | ranges::views::enumerate | ranges::views::drop(1) | ranges::views::drop_last(z_distance_top)) { - Polygons roof = support_layer.support_roof; - - if (roof.empty()) + if (support_layer.support_roof.empty()) { continue; } @@ -1835,7 +1828,7 @@ void AreaSupport::generateSupportRoof(SliceDataStorage& storage, const SliceMesh int upper = std::min(static_cast(layer_idx + roof_layer_count + z_distance_top + 5), static_cast(global_support_areas_per_layer.size()) - 1); for (Polygons& global_support : global_support_areas_per_layer | ranges::views::slice(lower, upper)) { - global_support = global_support.difference(roof); + global_support = global_support.difference(support_layer.support_roof); } } } diff --git a/src/utils/polygonUtils.cpp b/src/utils/polygonUtils.cpp index 1985a56a35..af44124412 100644 --- a/src/utils/polygonUtils.cpp +++ b/src/utils/polygonUtils.cpp @@ -3,22 +3,22 @@ #include "utils/polygonUtils.h" -#include "infill.h" -#include "utils/SparsePointGridInclusive.h" -#include "utils/linearAlg2D.h" - -#include - #include #include #include #include +#include + +#include "infill.h" +#include "utils/SparsePointGridInclusive.h" +#include "utils/linearAlg2D.h" + #ifdef DEBUG +#include + #include "utils/AABB.h" #include "utils/SVG.h" - -#include #endif namespace cura @@ -1643,4 +1643,32 @@ Polygons PolygonUtils::clipPolygonWithAABB(const Polygons& src, const AABB& aabb return out; } +Polygons PolygonUtils::generateOutset(const Polygons& inner_poly, size_t count, coord_t line_width) +{ + Polygons outset; + + Polygons current_outset; + for (size_t index = 0; index < count; ++index) + { + current_outset = index == 0 ? inner_poly.offset(line_width / 2) : current_outset.offset(line_width); + outset.add(current_outset); + } + + return outset; +} + +Polygons PolygonUtils::generateInset(const Polygons& outer_poly, coord_t line_width, coord_t initial_inset) +{ + Polygons inset; + + Polygons current_inset = outer_poly.offset(-(initial_inset + line_width / 2)); + while (! current_inset.empty()) + { + inset.add(current_inset); + current_inset = current_inset.offset(-line_width); + } + + return inset; +} + } // namespace cura diff --git a/tests/GCodeExportTest.cpp b/tests/GCodeExportTest.cpp index 134a48b503..3fb07c9be9 100644 --- a/tests/GCodeExportTest.cpp +++ b/tests/GCodeExportTest.cpp @@ -2,6 +2,7 @@ // CuraEngine is released under the terms of the AGPLv3 or higher #include "gcodeExport.h" // The unit under test. + #include "Application.h" // To set up a slice with settings. #include "RetractionConfig.h" // For extruder switch tests. #include "Slice.h" // To set up a slice with settings. @@ -9,6 +10,7 @@ #include "arcus/MockCommunication.h" // To prevent calls to any missing Communication class. #include "utils/Coord_t.h" #include "utils/Date.h" // To check the Griffin header. + #include // NOLINTBEGIN(*-magic-numbers) @@ -101,12 +103,13 @@ TEST_F(GCodeExportTest, CommentMultiLine) "You can honestly say\n" "You made on that day\n" "A Chilean chinchilla's chin chilly"); - EXPECT_EQ(std::string(";If you catch a chinchilla in Chile\n" - ";And cut off its beard, willy-nilly\n" - ";You can honestly say\n" - ";You made on that day\n" - ";A Chilean chinchilla's chin chilly\n"), - output.str()) + EXPECT_EQ( + std::string(";If you catch a chinchilla in Chile\n" + ";And cut off its beard, willy-nilly\n" + ";You can honestly say\n" + ";You made on that day\n" + ";A Chilean chinchilla's chin chilly\n"), + output.str()) << "Each line must be preceded by a semicolon."; } @@ -115,10 +118,11 @@ TEST_F(GCodeExportTest, CommentMultiple) gcode.writeComment("Thunderbolt and lightning"); gcode.writeComment("Very very frightening me"); gcode.writeComment(" - Galileo (1638)"); - EXPECT_EQ(std::string(";Thunderbolt and lightning\n" - ";Very very frightening me\n" - "; - Galileo (1638)\n"), - output.str()) + EXPECT_EQ( + std::string(";Thunderbolt and lightning\n" + ";Very very frightening me\n" + "; - Galileo (1638)\n"), + output.str()) << "Semicolon before each line, and newline in between."; } @@ -328,9 +332,10 @@ TEST_F(GCodeExportTest, HeaderUltiGCode) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:UltiGCode\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n;TIME:1337\n;MATERIAL:100\n;MATERIAL2:200\n;NOZZLE_DIAMETER:0.4\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;" - "MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:UltiGCode\n;TIME:1337\n;MATERIAL:100\n;MATERIAL2:200\n;NOZZLE_DIAMETER:0.4\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;" + "MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } TEST_F(GCodeExportTest, HeaderRepRap) @@ -347,9 +352,10 @@ TEST_F(GCodeExportTest, HeaderRepRap) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:RepRap\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " - "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:RepRap\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " + "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } TEST_F(GCodeExportTest, HeaderMarlin) @@ -366,9 +372,10 @@ TEST_F(GCodeExportTest, HeaderMarlin) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:Marlin\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " - "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:Marlin\n;TIME:1337\n;Filament used: 0.02m, 0.05m\n;Layer height: " + "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } TEST_F(GCodeExportTest, HeaderMarlinVolumetric) @@ -383,9 +390,10 @@ TEST_F(GCodeExportTest, HeaderMarlinVolumetric) std::string result = gcode.getFileHeader(extruder_is_used, &print_time, filament_used); - EXPECT_EQ(result, - ";FLAVOR:Marlin(Volumetric)\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n;TIME:1337\n;Filament used: 100mm3, 200mm3\n;Layer height: " - "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n"); + EXPECT_EQ( + result, + ";FLAVOR:Marlin(Volumetric)\n;TIME:1337\n;Filament used: 100mm3, 200mm3\n;Layer height: " + "0.123\n;MINX:0\n;MINY:0\n;MINZ:0\n;MAXX:1\n;MAXY:1\n;MAXZ:1\n;TARGET_MACHINE.NAME:Your favourite 3D printer\n"); } /* @@ -405,8 +413,9 @@ TEST_F(GCodeExportTest, EVsMmVolumetric) "area of the filament to convert the volume to a length."; constexpr double mm_input = 33.0; - EXPECT_EQ(gcode.mmToE(mm_input), mm_input * filament_area) << "Since the input mm is linear but the E output must be volumetric, we need to multiply by the cross-sectional area to convert " - "length to volume."; + EXPECT_EQ(gcode.mmToE(mm_input), mm_input * filament_area) + << "Since the input mm is linear but the E output must be volumetric, we need to multiply by the cross-sectional area to convert " + "length to volume."; constexpr double e_input = 100.0; EXPECT_EQ(gcode.eToMm3(e_input, 0), e_input) << "Since the E is volumetric and mm3 is also volumetric, the output needs to be the same."; @@ -431,8 +440,9 @@ TEST_F(GCodeExportTest, EVsMmLinear) } constexpr double mm3_input = 33.0; - EXPECT_EQ(gcode.mm3ToE(mm3_input), mm3_input / filament_area) << "Since the input mm3 is volumetric but the E output must be linear, we need to divide by the cross-sectional area to convert " - "volume to length."; + EXPECT_EQ(gcode.mm3ToE(mm3_input), mm3_input / filament_area) + << "Since the input mm3 is volumetric but the E output must be linear, we need to divide by the cross-sectional area to convert " + "volume to length."; constexpr double e_input = 100.0; EXPECT_EQ(gcode.eToMm3(e_input, 0), e_input * filament_area) << "Since the input E is linear but the output must be volumetric, we " @@ -492,7 +502,7 @@ TEST_F(GCodeExportTest, WriteZHopStartCustomSpeed) Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); // 60mm/min. gcode.current_layer_z = 2000; constexpr coord_t hop_height = 3000; - constexpr Velocity speed { 4.0 }; // 240 mm/min. + constexpr Velocity speed{ 4.0 }; // 240 mm/min. gcode.writeZhopStart(hop_height, speed); EXPECT_EQ(std::string("G1 F240 Z5\n"), output.str()) << "Custom provided speed should be used."; } @@ -520,7 +530,7 @@ TEST_F(GCodeExportTest, WriteZHopEndCustomSpeed) Application::getInstance().current_slice->scene.extruders[gcode.current_extruder].settings.add("speed_z_hop", "1"); gcode.current_layer_z = 2000; gcode.is_z_hopped = 3000; - constexpr Velocity speed { 4.0 }; // 240 mm/min. + constexpr Velocity speed{ 4.0 }; // 240 mm/min. gcode.writeZhopEnd(speed); EXPECT_EQ(std::string("G1 F240 Z2\n"), output.str()) << "Custom provided speed should be used."; } diff --git a/tests/LayerPlanTest.cpp b/tests/LayerPlanTest.cpp index adc5c888db..5a1f848889 100644 --- a/tests/LayerPlanTest.cpp +++ b/tests/LayerPlanTest.cpp @@ -169,6 +169,7 @@ class LayerPlanTest : public testing::Test settings->add("support_roof_extruder_nr", "0"); settings->add("support_roof_line_width", "0.404"); settings->add("support_roof_material_flow", "104"); + settings->add("support_top_distance", "200"); settings->add("wall_line_count", "3"); settings->add("wall_line_width_x", "0.3"); settings->add("wall_line_width_0", "0.301");