From 20090e8a5a4b19c7672f83bc5f0d1cce02887b56 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Tue, 10 Dec 2024 15:24:44 +0100 Subject: [PATCH 1/6] Generate multiple overhang areas for different speeds CURA-11966 --- include/LayerPlan.h | 15 +++++++++++---- src/FffGcodeWriter.cpp | 27 ++++++++++++++++++++------ src/LayerPlan.cpp | 40 +++++++++++++++++++++------------------ src/settings/Settings.cpp | 18 +++++++++++++++++- 4 files changed, 71 insertions(+), 29 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 47cea1aa19..a4fcc1fc0a 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -60,6 +60,12 @@ class LayerPlan : public NoCopy #endif public: + struct OverhangMask + { + Shape mask; + Ratio speed_ratio; + }; + const PathConfigStorage configs_storage_; //!< The line configs for this layer for each feature type const coord_t z_; coord_t final_travel_z_; @@ -115,7 +121,8 @@ class LayerPlan : public NoCopy Comb* comb_; coord_t comb_move_inside_distance_; //!< Whenever using the minimum boundary for combing it tries to move the coordinates inside by this distance after calculating the combing. Shape bridge_wall_mask_; //!< The regions of a layer part that are not supported, used for bridging - Shape overhang_mask_; //!< The regions of a layer part where the walls overhang + std::vector + overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most overhanging. Shape seam_overhang_mask_; //!< The regions of a layer part where the walls overhang, specifically as defined for the seam Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air @@ -295,11 +302,11 @@ class LayerPlan : public NoCopy void setBridgeWallMask(const Shape& polys); /*! - * Set overhang_mask. + * Set overhang_masks. * - * \param polys The overhung areas of the part currently being processed that will require modified print settings + * \param masks The overhung areas of the part currently being processed that will require modified print settings */ - void setOverhangMask(const Shape& polys); + void setOverhangMasks(const std::vector& masks); /*! * Set seam_overhang_mask. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 76b3d1b719..e164cb475a 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3082,10 +3082,6 @@ bool FffGcodeWriter::processInsets( const auto get_overhang_region = [&](const AngleDegrees overhang_angle) -> Shape { - if (overhang_angle >= 90) - { - return Shape(); // keep empty to disable overhang detection - } // the overhang mask is set to the area of the current part's outline minus the region that is considered to be supported // the supported region is made up of those areas that really are supported by either model or support on the layer below // expanded to take into account the overhang angle, the greater the overhang angle, the larger the supported area is @@ -3093,7 +3089,26 @@ bool FffGcodeWriter::processInsets( const coord_t overhang_width = layer_height * std::tan(overhang_angle / (180 / std::numbers::pi)); return part.outline.offset(-half_outer_wall_width).difference(outlines_below.offset(10 + overhang_width - half_outer_wall_width)).offset(10); }; - gcode_layer.setOverhangMask(get_overhang_region(mesh.settings.get("wall_overhang_angle"))); + + // Build overhang masks for all the overhang speeds + std::vector overhang_masks; + const auto overhang_speed_factors = mesh.settings.get>("wall_overhang_speed_factors"); + const size_t overhang_angles_count = overhang_speed_factors.size(); + if (overhang_angles_count > 0) + { + const auto wall_overhang_angle = mesh.settings.get("wall_overhang_angle"); + if (wall_overhang_angle < 90.0) + { + const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast(overhang_angles_count); + for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index) + { + const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast(angle_index) * overhang_step; + overhang_masks.emplace_back(get_overhang_region(actual_wall_overhang_angle), overhang_speed_factors[angle_index]); + } + } + } + gcode_layer.setOverhangMasks(overhang_masks); + gcode_layer.setSeamOverhangMask(get_overhang_region(mesh.settings.get("seam_overhang_angle"))); const auto roofing_mask_fn = [&]() -> Shape @@ -3125,7 +3140,7 @@ bool FffGcodeWriter::processInsets( // clear to disable use of bridging settings gcode_layer.setBridgeWallMask(Shape()); // clear to disable overhang detection - gcode_layer.setOverhangMask(Shape()); + gcode_layer.setOverhangMasks({}); // clear to disable overhang detection gcode_layer.setSeamOverhangMask(Shape()); // clear to disable use of roofing settings diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 39f3e1b510..170bcfa18b 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -57,14 +57,15 @@ GCodePath* LayerPlan::getLatestPathWithConfig( { return &paths.back(); } - paths.emplace_back(GCodePath{ .z_offset = z_offset, - .config = config, - .mesh = current_mesh_, - .space_fill_type = space_fill_type, - .flow = flow, - .width_factor = width_factor, - .spiralize = spiralize, - .speed_factor = speed_factor }); + paths.emplace_back( + GCodePath{ .z_offset = z_offset, + .config = config, + .mesh = current_mesh_, + .space_fill_type = space_fill_type, + .flow = flow, + .width_factor = width_factor, + .spiralize = spiralize, + .speed_factor = speed_factor }); GCodePath* ret = &paths.back(); ret->skip_agressive_merge_hint = mode_skip_agressive_merge_; @@ -724,14 +725,15 @@ void LayerPlan::addWallLine( double distance_to_bridge_start, const bool travel_to_z) { - const coord_t min_line_len = 5; // we ignore lines less than 5um long - const double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length - const double acceleration_factor = 0.75; // must be < 1, the larger the value, the slower the acceleration - const bool spiralize = false; + constexpr coord_t min_line_len = 5; // we ignore lines less than 5um long + constexpr double acceleration_segment_len = MM2INT(1); // accelerate using segments of this length + constexpr double acceleration_factor = 0.75; // must be < 1, the larger the value, the slower the acceleration + constexpr bool spiralize = false; const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); const Ratio bridge_wall_coast = settings.get("bridge_wall_coast"); const Ratio overhang_speed_factor = settings.get("wall_overhang_speed_factor"); + const auto overhang_speed_factors = settings.get>("wall_overhang_speed_factors"); Point3LL cur_point = p0; @@ -1322,7 +1324,7 @@ std::vector for (const auto& reversed_chunk : paths | ranges::views::enumerate | ranges::views::reverse | ranges::views::chunk_by( - [](const auto&path_a, const auto&path_b) + [](const auto& path_a, const auto& path_b) { return (! std::get<1>(path_a).isTravelPath()) || std::get<1>(path_b).isTravelPath(); })) @@ -1948,9 +1950,11 @@ void LayerPlan::addLinesInGivenOrder( bool LayerPlan::segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const { - const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() }; - const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment }); - return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5; + // const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() }; + // const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment }); + // return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5; + + return false; } void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed) @@ -3154,9 +3158,9 @@ void LayerPlan::setBridgeWallMask(const Shape& polys) bridge_wall_mask_ = polys; } -void LayerPlan::setOverhangMask(const Shape& polys) +void LayerPlan::setOverhangMasks(const std::vector& masks) { - overhang_mask_ = polys; + overhang_masks_ = masks; } void LayerPlan::setSeamOverhangMask(const Shape& polys) diff --git a/src/settings/Settings.cpp b/src/settings/Settings.cpp index 4e39ef8555..4904e2e13c 100644 --- a/src/settings/Settings.cpp +++ b/src/settings/Settings.cpp @@ -183,7 +183,7 @@ Acceleration Settings::get(const std::string& key) const template<> Ratio Settings::get(const std::string& key) const { - return get(key) / 100.0; // The settings are all in percentages, but we need to interpret them as radians. + return get(key) / 100.0; // The settings are all in percentages, but we need to interpret them as ratios. } template<> @@ -787,6 +787,22 @@ std::vector Settings::get>(const std::string& key) c return result; } +template<> +std::vector Settings::get>(const std::string& key) const +{ + auto values_double = get>(key); + + // The settings are all in percentages, but we need to interpret them as ratios. + std::vector result; + result.reserve(values_double.size()); + for (const auto& value_double : values_double) + { + result.emplace_back(value_double / 100.0); + } + + return result; +} + template<> std::vector Settings::get>(const std::string& key) const { From afd9a27e64e52b14b6147dc984654e072cae8ff6 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Wed, 11 Dec 2024 19:43:06 +0100 Subject: [PATCH 2/6] Basically working gradual overhang speed CURA-11966 --- doc/gradual_overhang_speed.svg | 857 +++++++++++++++++++++++++++++++++ include/LayerPlan.h | 24 +- include/geometry/Shape.h | 8 + src/FffGcodeWriter.cpp | 53 +- src/LayerPlan.cpp | 141 +++++- src/geometry/Shape.cpp | 19 + 6 files changed, 1061 insertions(+), 41 deletions(-) create mode 100644 doc/gradual_overhang_speed.svg diff --git a/doc/gradual_overhang_speed.svg b/doc/gradual_overhang_speed.svg new file mode 100644 index 0000000000..cec3bfbd83 --- /dev/null +++ b/doc/gradual_overhang_speed.svg @@ -0,0 +1,857 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100% + 75% + 50% + 25% + 0-60° + 60-75° + 75-90° + Threshold Angle 60° + Speeds [75, 50, 25] + + + + + + + + + + + + + + + + 100% + 50% + 0-60° + Threshold Angle 60° + Speeds [50] + + + + + + + + + + + + + 100% + Threshold Angle 90° + Speeds doesn't matter + + + + + + + + + + + + + + + + + + + + + + + + 75% + 50% + 25% + 0-45° + 45-90° + 90° + Threshold Angle 0° + Speeds [75, 50, 25] + 100% + + 90° + + Part outline + + Speed region + + Next extrusion path + + Existing vertex of extrusion path + + New vertex of extrusion path + + diff --git a/include/LayerPlan.h b/include/LayerPlan.h index a4fcc1fc0a..149138e9cb 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -62,7 +62,7 @@ class LayerPlan : public NoCopy public: struct OverhangMask { - Shape mask; + Shape supported_region; Ratio speed_ratio; }; @@ -121,8 +121,8 @@ class LayerPlan : public NoCopy Comb* comb_; coord_t comb_move_inside_distance_; //!< Whenever using the minimum boundary for combing it tries to move the coordinates inside by this distance after calculating the combing. Shape bridge_wall_mask_; //!< The regions of a layer part that are not supported, used for bridging - std::vector - overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most overhanging. + std::vector overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most + //!< overhanging. For a visual explanation of the result, see doc/gradual_overhang_speed.svg Shape seam_overhang_mask_; //!< The regions of a layer part where the walls overhang, specifically as defined for the seam Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air @@ -400,6 +400,17 @@ class LayerPlan : public NoCopy const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, const bool travel_to_z = true); + void addExtrusionMoveWithGradualOverhang( + const Point3LL& p, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const Ratio& flow = 1.0_r, + const Ratio width_factor = 1.0_r, + const bool spiralize = false, + const Ratio speed_factor = 1.0_r, + const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT, + const bool travel_to_z = true); + /*! * Add polygon to the gcode starting at vertex \p startIdx * \param polygon The polygon @@ -1007,13 +1018,6 @@ class LayerPlan : public NoCopy * \return The distance from the start of the current wall line to the first bridge segment */ coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const; - - /*! - * \brief Calculates whether the given segment is to be treated as overhanging - * \param p0 The start point of the segment - * \param p1 The end point of the segment - */ - bool segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const; }; } // namespace cura diff --git a/include/geometry/Shape.h b/include/geometry/Shape.h index 552325676f..4397036633 100644 --- a/include/geometry/Shape.h +++ b/include/geometry/Shape.h @@ -256,6 +256,14 @@ class Shape : public LinesSet */ void simplify(ClipperLib::PolyFillType fill_type = ClipperLib::pftEvenOdd); + /*! + * Calculates the intersections between the given segment and all the segments of the shape + * @param start The start position of the segment + * @param end The end position of the segment + * @return The parameters of the intersections on the segment (intersection = start + t * (end - start)), unsorted + */ + std::vector intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const; + #ifdef BUILD_TESTS /*! * @brief Import the polygon from a WKT string diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index e164cb475a..567e6d821a 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3080,36 +3080,61 @@ bool FffGcodeWriter::processInsets( gcode_layer.setBridgeWallMask(Shape()); } - const auto get_overhang_region = [&](const AngleDegrees overhang_angle) -> Shape + const Shape fully_supported_region = outlines_below.offset(-half_outer_wall_width); + const Shape part_print_region = part.outline.offset(-half_outer_wall_width); + + const auto get_supported_region = [&fully_supported_region, &layer_height](const AngleDegrees& overhang_angle) -> Shape { // the overhang mask is set to the area of the current part's outline minus the region that is considered to be supported // the supported region is made up of those areas that really are supported by either model or support on the layer below // expanded to take into account the overhang angle, the greater the overhang angle, the larger the supported area is // considered to be - const coord_t overhang_width = layer_height * std::tan(overhang_angle / (180 / std::numbers::pi)); - return part.outline.offset(-half_outer_wall_width).difference(outlines_below.offset(10 + overhang_width - half_outer_wall_width)).offset(10); + const coord_t overhang_width = layer_height * std::tan(AngleRadians(overhang_angle)); + return fully_supported_region.offset(overhang_width + 10); }; - // Build overhang masks for all the overhang speeds +#warning remove SVG writing + SVG svg(fmt::format("/tmp/overhang_mask_{}.svg", gcode_layer.getLayerNr().value), storage.getMachineBorder(), 0.001); + svg.writePolygons(part.outline, SVG::Color::BLACK, 0.01); + + // Build supported regions for all the overhang speeds. For a visual explanation of the result, see doc/gradual_overhang_speed.svg std::vector overhang_masks; const auto overhang_speed_factors = mesh.settings.get>("wall_overhang_speed_factors"); const size_t overhang_angles_count = overhang_speed_factors.size(); - if (overhang_angles_count > 0) + const auto wall_overhang_angle = mesh.settings.get("wall_overhang_angle"); + if (overhang_angles_count > 0 && wall_overhang_angle < 90.0) { - const auto wall_overhang_angle = mesh.settings.get("wall_overhang_angle"); - if (wall_overhang_angle < 90.0) + const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast(overhang_angles_count); + for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index) { - const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast(overhang_angles_count); - for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index) - { - const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast(angle_index) * overhang_step; - overhang_masks.emplace_back(get_overhang_region(actual_wall_overhang_angle), overhang_speed_factors[angle_index]); - } + const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast(angle_index) * overhang_step; + const Ratio speed_factor = angle_index == 0 ? 1.0_r : overhang_speed_factors[angle_index - 1]; + + overhang_masks.emplace_back(get_supported_region(actual_wall_overhang_angle), speed_factor); + } + + // Add an empty region, which actually means everything and should be ignored anyway + overhang_masks.emplace_back(Shape(), overhang_speed_factors.back()); + + for (const auto& region : overhang_masks) + { + svg.writePolygons(region.supported_region, SVG::Color::RAINBOW, 0.01); } } gcode_layer.setOverhangMasks(overhang_masks); - gcode_layer.setSeamOverhangMask(get_overhang_region(mesh.settings.get("seam_overhang_angle"))); + // the seam overhang mask is set to the area of the current part's outline minus the region that is considered to be supported, + // which will then be empty if everything is considered supported i.r.t. the angle + const AngleDegrees seam_overhang_angle = mesh.settings.get("seam_overhang_angle"); + if (seam_overhang_angle < 90.0) + { + const Shape supported_region_seam = get_supported_region(seam_overhang_angle); + gcode_layer.setSeamOverhangMask(part_print_region.difference(supported_region_seam).offset(10)); + } + else + { + gcode_layer.setSeamOverhangMask(Shape()); + } const auto roofing_mask_fn = [&]() -> Shape { diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 170bcfa18b..0617d04a54 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -558,6 +558,124 @@ void LayerPlan::addExtrusionMove( last_planned_position_ = p.toPoint2LL(); } +void LayerPlan::addExtrusionMoveWithGradualOverhang( + const Point3LL& p, + const GCodePathConfig& config, + const SpaceFillType space_fill_type, + const Ratio& flow, + const Ratio width_factor, + const bool spiralize, + const Ratio speed_factor, + const double fan_speed, + const bool travel_to_z) +{ + const auto add_extrusion_move = [&](const Point3LL& target, const Ratio& overhang_speed_factor = 1.0_r) + { + addExtrusionMove(target, config, space_fill_type, flow, width_factor, spiralize, speed_factor * overhang_speed_factor, fan_speed, travel_to_z); + }; + + if (overhang_masks_.empty() || ! last_planned_position_.has_value()) + { + // Unable to apply gradual overhanging (probably just disabled), just add the basic extrusion move + add_extrusion_move(p); + return; + } + + // First, find the speed region where the segment starts + const Point2LL start = last_planned_position_.value(); + size_t actual_speed_region_index = overhang_masks_.size() - 1; // Default to last region, which is infinity and beyond + for (const auto& [index, overhang_region] : overhang_masks_ | ranges::views::drop_last(1) | ranges::views::enumerate) + { + if (overhang_region.supported_region.inside(start, true)) + { + actual_speed_region_index = index; + break; + } + } + + // Pre-calculate the intersections of the segment with all regions (except last one, you cannot intersect an infinite plane) + const Point2LL end = p.toPoint2LL(); + const Point2LL vector = end - start; + std::vector> speed_regions_intersections; + speed_regions_intersections.reserve(overhang_masks_.size() - 1); + for (const OverhangMask& overhang_region : overhang_masks_ | ranges::views::drop_last(1)) + { + std::vector intersections = overhang_region.supported_region.intersectionsWithSegment(start, end); + ranges::sort(intersections); + speed_regions_intersections.push_back(intersections); + if (! intersections.empty()) + { + spdlog::debug("coucou"); + } + } + + const auto remove_previous_intersections = [&speed_regions_intersections](const float current_intersection) + { + for (std::vector& intersections : speed_regions_intersections) + { + auto iterator = ranges::find_if( + intersections, + [¤t_intersection](const float next_intersection) + { + return next_intersection > current_intersection; + }); + + intersections.erase(intersections.begin(), iterator); + } + }; + + // Now move along segment and split it where we cross speed regions + while (true) + { + // First, see if we cross either the border or our current region (go out) or the border of the inner region (go in) + auto get_first_intersection = [](const std::vector* intersections) -> std::optional + { + return intersections != nullptr && ! intersections->empty() ? std::make_optional(intersections->front()) : std::nullopt; + }; + + std::vector* intersections_current_region + = actual_speed_region_index < speed_regions_intersections.size() ? &speed_regions_intersections[actual_speed_region_index] : nullptr; + const std::optional first_intersection_current_region = get_first_intersection(intersections_current_region); + + std::vector* intersections_inner_region = actual_speed_region_index > 0 ? &speed_regions_intersections[actual_speed_region_index - 1] : nullptr; + const std::optional first_intersection_inner_region = get_first_intersection(intersections_inner_region); + + if (first_intersection_current_region.has_value() || first_intersection_inner_region.has_value()) + { + float intersection_parameter; + size_t next_speed_region_index; + + if (first_intersection_current_region.has_value() + && (! first_intersection_inner_region.has_value() || first_intersection_inner_region.value() > first_intersection_current_region.value())) + { + // We crossed the border of the current region, which means we are getting out of it to an outer region + intersection_parameter = first_intersection_current_region.value(); + next_speed_region_index = actual_speed_region_index + 1; + } + else + { + // We crossed the border of the inner region, which means we are getting inside of it + intersection_parameter = first_intersection_inner_region.value(); + next_speed_region_index = actual_speed_region_index - 1; + } + + // Move to intersection at current region speed + const Point2LL split_position = start + vector * intersection_parameter; + add_extrusion_move(split_position, overhang_masks_[actual_speed_region_index].speed_ratio); + + // Prepare for next move in different region + actual_speed_region_index = next_speed_region_index; + remove_previous_intersections(intersection_parameter); + } + else + { + // We cross no border, which means we can reach the end of the segment within the current speed region, so we are done + add_extrusion_move(p, overhang_masks_[actual_speed_region_index].speed_ratio); + return; + } + } +} + template void LayerPlan::addWipeTravel(const PathAdapter& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position) { @@ -732,8 +850,6 @@ void LayerPlan::addWallLine( const coord_t min_bridge_line_len = settings.get("bridge_wall_min_length"); const Ratio bridge_wall_coast = settings.get("bridge_wall_coast"); - const Ratio overhang_speed_factor = settings.get("wall_overhang_speed_factor"); - const auto overhang_speed_factors = settings.get>("wall_overhang_speed_factors"); Point3LL cur_point = p0; @@ -799,14 +915,14 @@ void LayerPlan::addWallLine( else { // no coasting required, just normal segment using non-bridge config - addExtrusionMove( + addExtrusionMoveWithGradualOverhang( segment_end, default_config, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, - segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, + speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -816,14 +932,14 @@ void LayerPlan::addWallLine( else { // no coasting required, just normal segment using non-bridge config - addExtrusionMove( + addExtrusionMoveWithGradualOverhang( segment_end, default_config, SpaceFillType::Polygons, segment_flow, width_factor, spiralize, - segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, + speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -921,14 +1037,14 @@ void LayerPlan::addWallLine( else if (bridge_wall_mask_.empty()) { // no bridges required - addExtrusionMove( + addExtrusionMoveWithGradualOverhang( p1, default_config, SpaceFillType::Polygons, flow, width_factor, spiralize, - segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor, + speed_factor, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); } @@ -1948,15 +2064,6 @@ void LayerPlan::addLinesInGivenOrder( } } -bool LayerPlan::segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const -{ - // const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() }; - // const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment }); - // return ! intersected_lines.empty() && (static_cast(intersected_lines.length()) / segment.length()) > 0.5; - - return false; -} - void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed) { Application::getInstance().communication_->sendLineTo( diff --git a/src/geometry/Shape.cpp b/src/geometry/Shape.cpp index 61d029c4e2..b16d68b336 100644 --- a/src/geometry/Shape.cpp +++ b/src/geometry/Shape.cpp @@ -890,6 +890,25 @@ void Shape::simplify(ClipperLib::PolyFillType fill_type) } } +std::vector Shape::intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const +{ + std::vector result; + + for (const Polygon& polygon : getLines()) + { + for (auto iterator = polygon.beginSegments(); iterator != polygon.endSegments(); ++iterator) + { + float t, u; + if (LinearAlg2D::segmentSegmentIntersection(start, end, (*iterator).start, (*iterator).end, t, u)) + { + result.push_back(t); + } + } + } + + return result; +} + void Shape::ensureManifold() { std::vector duplicate_locations; From 0c00e154b6435331b0d37cf61dabd728ddb8e2f9 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 12 Dec 2024 14:49:18 +0100 Subject: [PATCH 3/6] Fix interaction with bridging CURA-11966 --- include/LayerPlan.h | 1 + src/FffGcodeWriter.cpp | 9 --------- src/LayerPlan.cpp | 46 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 149138e9cb..835d3c827f 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -401,6 +401,7 @@ class LayerPlan : public NoCopy const bool travel_to_z = true); void addExtrusionMoveWithGradualOverhang( + SVG& logger, const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 567e6d821a..15c2b3cce4 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3093,10 +3093,6 @@ bool FffGcodeWriter::processInsets( return fully_supported_region.offset(overhang_width + 10); }; -#warning remove SVG writing - SVG svg(fmt::format("/tmp/overhang_mask_{}.svg", gcode_layer.getLayerNr().value), storage.getMachineBorder(), 0.001); - svg.writePolygons(part.outline, SVG::Color::BLACK, 0.01); - // Build supported regions for all the overhang speeds. For a visual explanation of the result, see doc/gradual_overhang_speed.svg std::vector overhang_masks; const auto overhang_speed_factors = mesh.settings.get>("wall_overhang_speed_factors"); @@ -3115,11 +3111,6 @@ bool FffGcodeWriter::processInsets( // Add an empty region, which actually means everything and should be ignored anyway overhang_masks.emplace_back(Shape(), overhang_speed_factors.back()); - - for (const auto& region : overhang_masks) - { - svg.writePolygons(region.supported_region, SVG::Color::RAINBOW, 0.01); - } } gcode_layer.setOverhangMasks(overhang_masks); diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 0617d04a54..c61fe134e9 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -559,6 +559,7 @@ void LayerPlan::addExtrusionMove( } void LayerPlan::addExtrusionMoveWithGradualOverhang( + SVG& logger, const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, @@ -603,10 +604,6 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( std::vector intersections = overhang_region.supported_region.intersectionsWithSegment(start, end); ranges::sort(intersections); speed_regions_intersections.push_back(intersections); - if (! intersections.empty()) - { - spdlog::debug("coucou"); - } } const auto remove_previous_intersections = [&speed_regions_intersections](const float current_intersection) @@ -624,6 +621,8 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( } }; + const std::vector colors = { SVG::Color::RED, SVG::Color::GREEN, SVG::Color::BLUE, SVG::Color::YELLOW }; + // Now move along segment and split it where we cross speed regions while (true) { @@ -661,6 +660,7 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( // Move to intersection at current region speed const Point2LL split_position = start + vector * intersection_parameter; + logger.writeLine(last_planned_position_.value(), split_position, colors[actual_speed_region_index], 0.02); add_extrusion_move(split_position, overhang_masks_[actual_speed_region_index].speed_ratio); // Prepare for next move in different region @@ -670,6 +670,7 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( else { // We cross no border, which means we can reach the end of the segment within the current speed region, so we are done + logger.writeLine(last_planned_position_.value(), p.toPoint2LL(), colors[actual_speed_region_index], 0.025); add_extrusion_move(p, overhang_masks_[actual_speed_region_index].speed_ratio); return; } @@ -829,6 +830,9 @@ void LayerPlan::addPolygonsByOptimizer( static constexpr double max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate +#warning remove this +std::map counts; + void LayerPlan::addWallLine( const Point3LL& p0, const Point3LL& p1, @@ -853,6 +857,23 @@ void LayerPlan::addWallLine( Point3LL cur_point = p0; +#warning remove SVG writing + size_t count = 0; + if (counts.contains(layer_nr_)) + { + count = counts[layer_nr_] + 1; + } + counts[layer_nr_] = count; + + SVG svg(fmt::format("/tmp/overhang_mask_{}_{}.svg", layer_nr_.value, count), storage_.getMachineBorder(), 0.001); + + for (const OverhangMask& mask : overhang_masks_) + { + svg.writePolygons(mask.supported_region, SVG::Color::MAGENTA, 0.01); + } + + svg.writePolygons(bridge_wall_mask_, SVG::Color::ORANGE, 0.01); + // helper function to add a single non-bridge line // If the line precedes a bridge line, it may be coasted to reduce the nozzle pressure before the bridge is reached @@ -916,6 +937,7 @@ void LayerPlan::addWallLine( { // no coasting required, just normal segment using non-bridge config addExtrusionMoveWithGradualOverhang( + svg, segment_end, default_config, SpaceFillType::Polygons, @@ -933,6 +955,7 @@ void LayerPlan::addWallLine( { // no coasting required, just normal segment using non-bridge config addExtrusionMoveWithGradualOverhang( + svg, segment_end, default_config, SpaceFillType::Polygons, @@ -1038,6 +1061,7 @@ void LayerPlan::addWallLine( { // no bridges required addExtrusionMoveWithGradualOverhang( + svg, p1, default_config, SpaceFillType::Polygons, @@ -1103,7 +1127,17 @@ void LayerPlan::addWallLine( if (bridge_line_len > min_line_len) { - addExtrusionMove(b1, bridge_config, SpaceFillType::Polygons, flow, width_factor, spiralize, 1.0_r, GCodePathConfig::FAN_SPEED_DEFAULT, travel_to_z); + addExtrusionMoveWithGradualOverhang( + svg, + b1, + bridge_config, + SpaceFillType::Polygons, + flow, + width_factor, + spiralize, + 1.0_r, + GCodePathConfig::FAN_SPEED_DEFAULT, + travel_to_z); non_bridge_line_volume = 0; cur_point = b1; // after a bridge segment, start slow and accelerate to avoid under-extrusion due to extruder lag @@ -1127,7 +1161,7 @@ void LayerPlan::addWallLine( else if (bridge_wall_mask_.inside(p0.toPoint2LL(), true) && (p0 - p1).vSize() >= min_bridge_line_len) { // both p0 and p1 must be above air (the result will be ugly!) - addExtrusionMove(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor); + addExtrusionMoveWithGradualOverhang(svg, p1, bridge_config, SpaceFillType::Polygons, flow, width_factor); non_bridge_line_volume = 0; } else From 23a3a3f9414a66f8a9a36b68c200279359057afb Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Thu, 12 Dec 2024 14:52:58 +0100 Subject: [PATCH 4/6] Remove debug code CURA-11966 --- include/LayerPlan.h | 1 - src/LayerPlan.cpp | 29 +---------------------------- 2 files changed, 1 insertion(+), 29 deletions(-) diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 835d3c827f..149138e9cb 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -401,7 +401,6 @@ class LayerPlan : public NoCopy const bool travel_to_z = true); void addExtrusionMoveWithGradualOverhang( - SVG& logger, const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index c61fe134e9..690f789139 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -559,7 +559,6 @@ void LayerPlan::addExtrusionMove( } void LayerPlan::addExtrusionMoveWithGradualOverhang( - SVG& logger, const Point3LL& p, const GCodePathConfig& config, const SpaceFillType space_fill_type, @@ -660,7 +659,6 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( // Move to intersection at current region speed const Point2LL split_position = start + vector * intersection_parameter; - logger.writeLine(last_planned_position_.value(), split_position, colors[actual_speed_region_index], 0.02); add_extrusion_move(split_position, overhang_masks_[actual_speed_region_index].speed_ratio); // Prepare for next move in different region @@ -670,7 +668,6 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( else { // We cross no border, which means we can reach the end of the segment within the current speed region, so we are done - logger.writeLine(last_planned_position_.value(), p.toPoint2LL(), colors[actual_speed_region_index], 0.025); add_extrusion_move(p, overhang_masks_[actual_speed_region_index].speed_ratio); return; } @@ -830,9 +827,6 @@ void LayerPlan::addPolygonsByOptimizer( static constexpr double max_non_bridge_line_volume = MM2INT(100); // limit to accumulated "volume" of non-bridge lines which is proportional to distance x extrusion rate -#warning remove this -std::map counts; - void LayerPlan::addWallLine( const Point3LL& p0, const Point3LL& p1, @@ -857,23 +851,6 @@ void LayerPlan::addWallLine( Point3LL cur_point = p0; -#warning remove SVG writing - size_t count = 0; - if (counts.contains(layer_nr_)) - { - count = counts[layer_nr_] + 1; - } - counts[layer_nr_] = count; - - SVG svg(fmt::format("/tmp/overhang_mask_{}_{}.svg", layer_nr_.value, count), storage_.getMachineBorder(), 0.001); - - for (const OverhangMask& mask : overhang_masks_) - { - svg.writePolygons(mask.supported_region, SVG::Color::MAGENTA, 0.01); - } - - svg.writePolygons(bridge_wall_mask_, SVG::Color::ORANGE, 0.01); - // helper function to add a single non-bridge line // If the line precedes a bridge line, it may be coasted to reduce the nozzle pressure before the bridge is reached @@ -937,7 +914,6 @@ void LayerPlan::addWallLine( { // no coasting required, just normal segment using non-bridge config addExtrusionMoveWithGradualOverhang( - svg, segment_end, default_config, SpaceFillType::Polygons, @@ -955,7 +931,6 @@ void LayerPlan::addWallLine( { // no coasting required, just normal segment using non-bridge config addExtrusionMoveWithGradualOverhang( - svg, segment_end, default_config, SpaceFillType::Polygons, @@ -1061,7 +1036,6 @@ void LayerPlan::addWallLine( { // no bridges required addExtrusionMoveWithGradualOverhang( - svg, p1, default_config, SpaceFillType::Polygons, @@ -1128,7 +1102,6 @@ void LayerPlan::addWallLine( if (bridge_line_len > min_line_len) { addExtrusionMoveWithGradualOverhang( - svg, b1, bridge_config, SpaceFillType::Polygons, @@ -1161,7 +1134,7 @@ void LayerPlan::addWallLine( else if (bridge_wall_mask_.inside(p0.toPoint2LL(), true) && (p0 - p1).vSize() >= min_bridge_line_len) { // both p0 and p1 must be above air (the result will be ugly!) - addExtrusionMoveWithGradualOverhang(svg, p1, bridge_config, SpaceFillType::Polygons, flow, width_factor); + addExtrusionMoveWithGradualOverhang(p1, bridge_config, SpaceFillType::Polygons, flow, width_factor); non_bridge_line_volume = 0; } else From 71c8e141b4c6fd3f1717510e8fe28823c9bb7843 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 13 Dec 2024 12:59:39 +0100 Subject: [PATCH 5/6] Fix Mac build (hopefully) CURA-11966 --- src/FffGcodeWriter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 15c2b3cce4..26a1995cfc 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -3106,11 +3106,11 @@ bool FffGcodeWriter::processInsets( const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast(angle_index) * overhang_step; const Ratio speed_factor = angle_index == 0 ? 1.0_r : overhang_speed_factors[angle_index - 1]; - overhang_masks.emplace_back(get_supported_region(actual_wall_overhang_angle), speed_factor); + overhang_masks.push_back(LayerPlan::OverhangMask{ get_supported_region(actual_wall_overhang_angle), speed_factor }); } // Add an empty region, which actually means everything and should be ignored anyway - overhang_masks.emplace_back(Shape(), overhang_speed_factors.back()); + overhang_masks.push_back(LayerPlan::OverhangMask{ Shape(), overhang_speed_factors.back() }); } gcode_layer.setOverhangMasks(overhang_masks); From 4379cbfd95cf522fddeb7ea9089d0a6f3ea1d61b Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Mon, 16 Dec 2024 13:13:22 +0100 Subject: [PATCH 6/6] Use different minimum layer time when layer has an overhang CURA-12352 --- include/FanSpeedLayerTime.h | 13 +++++++++---- include/LayerPlan.h | 1 + src/FffGcodeWriter.cpp | 1 + src/LayerPlan.cpp | 15 +++++++++++---- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/include/FanSpeedLayerTime.h b/include/FanSpeedLayerTime.h index bc6c00fc73..05554a886d 100644 --- a/include/FanSpeedLayerTime.h +++ b/include/FanSpeedLayerTime.h @@ -1,5 +1,5 @@ -//Copyright (c) 2020 Ultimaker B.V. -//CuraEngine is released under the terms of the AGPLv3 or higher. +// Copyright (c) 2020 Ultimaker B.V. +// CuraEngine is released under the terms of the AGPLv3 or higher. #ifndef FAN_SPEED_LAYER_TIME_H #define FAN_SPEED_LAYER_TIME_H @@ -8,7 +8,7 @@ #include "settings/types/LayerIndex.h" #include "settings/types/Velocity.h" -namespace cura +namespace cura { /*! @@ -19,7 +19,7 @@ namespace cura * store these settings over and over again for each part, even though the * settings may be different for each part on a layer. */ -struct FanSpeedLayerTimeSettings +struct FanSpeedLayerTimeSettings { public: /*! @@ -28,6 +28,11 @@ struct FanSpeedLayerTimeSettings */ Duration cool_min_layer_time; + /*! + * Similar to Minimum layer time, but to be applied for layers that contain overhanging extrusion. + */ + Duration cool_min_layer_time_overhang; + /*! * "Regular/Maximum Fan Speed Threshold". If the layers take longer to print * than this, they'll use the regular fan speed. If they take shorter, we'll diff --git a/include/LayerPlan.h b/include/LayerPlan.h index 149138e9cb..449a3a3127 100644 --- a/include/LayerPlan.h +++ b/include/LayerPlan.h @@ -124,6 +124,7 @@ class LayerPlan : public NoCopy std::vector overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most //!< overhanging. For a visual explanation of the result, see doc/gradual_overhang_speed.svg Shape seam_overhang_mask_; //!< The regions of a layer part where the walls overhang, specifically as defined for the seam + bool contains_overhang_{ false }; //!< Indicates whether this plan contains any overhanging extrusion Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air bool min_layer_time_used = false; //!< Wether or not the minimum layer time (cool_min_layer_time) was actually used in this layerplan. diff --git a/src/FffGcodeWriter.cpp b/src/FffGcodeWriter.cpp index 26a1995cfc..469d5f4854 100644 --- a/src/FffGcodeWriter.cpp +++ b/src/FffGcodeWriter.cpp @@ -318,6 +318,7 @@ void FffGcodeWriter::setConfigFanSpeedLayerTime() fan_speed_layer_time_settings_per_extruder.emplace_back(); FanSpeedLayerTimeSettings& fan_speed_layer_time_settings = fan_speed_layer_time_settings_per_extruder.back(); fan_speed_layer_time_settings.cool_min_layer_time = train.settings_.get("cool_min_layer_time"); + fan_speed_layer_time_settings.cool_min_layer_time_overhang = train.settings_.get("cool_min_layer_time_overhang"); fan_speed_layer_time_settings.cool_min_layer_time_fan_speed_max = train.settings_.get("cool_min_layer_time_fan_speed_max"); fan_speed_layer_time_settings.cool_fan_speed_0 = train.settings_.get("cool_fan_speed_0") * 100.0; fan_speed_layer_time_settings.cool_fan_speed_min = train.settings_.get("cool_fan_speed_min") * 100.0; diff --git a/src/LayerPlan.cpp b/src/LayerPlan.cpp index 690f789139..dcc92a4f40 100644 --- a/src/LayerPlan.cpp +++ b/src/LayerPlan.cpp @@ -569,8 +569,13 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( const double fan_speed, const bool travel_to_z) { - const auto add_extrusion_move = [&](const Point3LL& target, const Ratio& overhang_speed_factor = 1.0_r) + const auto add_extrusion_move = [&](const Point3LL& target, const std::optional speed_region_index = std::nullopt) { + const Ratio overhang_speed_factor = speed_region_index.has_value() ? overhang_masks_[speed_region_index.value()].speed_ratio : 1.0_r; + if (speed_region_index.has_value() && speed_region_index.value() > 0) + { + contains_overhang_ = true; + } addExtrusionMove(target, config, space_fill_type, flow, width_factor, spiralize, speed_factor * overhang_speed_factor, fan_speed, travel_to_z); }; @@ -659,7 +664,7 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( // Move to intersection at current region speed const Point2LL split_position = start + vector * intersection_parameter; - add_extrusion_move(split_position, overhang_masks_[actual_speed_region_index].speed_ratio); + add_extrusion_move(split_position, actual_speed_region_index); // Prepare for next move in different region actual_speed_region_index = next_speed_region_index; @@ -668,7 +673,7 @@ void LayerPlan::addExtrusionMoveWithGradualOverhang( else { // We cross no border, which means we can reach the end of the segment within the current speed region, so we are done - add_extrusion_move(p, overhang_masks_[actual_speed_region_index].speed_ratio); + add_extrusion_move(p, actual_speed_region_index); return; } } @@ -2562,7 +2567,9 @@ void LayerPlan::processFanSpeedAndMinimalLayerTime(Point2LL starting_position) { other_extr_plan_time += extruder_plan.estimates_.getTotalTime(); } - maximum_cool_min_layer_time = std::max(maximum_cool_min_layer_time, extruder_plan.fan_speed_layer_time_settings_.cool_min_layer_time); + + const FanSpeedLayerTimeSettings& settings = extruder_plan.fan_speed_layer_time_settings_; + maximum_cool_min_layer_time = std::max(maximum_cool_min_layer_time, contains_overhang_ ? settings.cool_min_layer_time_overhang : settings.cool_min_layer_time); // Modify fan speeds for the first layer(s) extruder_plan.processFanSpeedForFirstLayers();