Skip to content

Commit afd9a27

Browse files
committed
Basically working gradual overhang speed
CURA-11966
1 parent 20090e8 commit afd9a27

File tree

6 files changed

+1061
-41
lines changed

6 files changed

+1061
-41
lines changed

doc/gradual_overhang_speed.svg

Lines changed: 857 additions & 0 deletions
Loading

include/LayerPlan.h

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class LayerPlan : public NoCopy
6262
public:
6363
struct OverhangMask
6464
{
65-
Shape mask;
65+
Shape supported_region;
6666
Ratio speed_ratio;
6767
};
6868

@@ -121,8 +121,8 @@ class LayerPlan : public NoCopy
121121
Comb* comb_;
122122
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.
123123
Shape bridge_wall_mask_; //!< The regions of a layer part that are not supported, used for bridging
124-
std::vector<OverhangMask>
125-
overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most overhanging.
124+
std::vector<OverhangMask> overhang_masks_; //!< The regions of a layer part where the walls overhang, calculated for multiple overhang angles. The latter is the most
125+
//!< overhanging. For a visual explanation of the result, see doc/gradual_overhang_speed.svg
126126
Shape seam_overhang_mask_; //!< The regions of a layer part where the walls overhang, specifically as defined for the seam
127127
Shape roofing_mask_; //!< The regions of a layer part where the walls are exposed to the air
128128

@@ -400,6 +400,17 @@ class LayerPlan : public NoCopy
400400
const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT,
401401
const bool travel_to_z = true);
402402

403+
void addExtrusionMoveWithGradualOverhang(
404+
const Point3LL& p,
405+
const GCodePathConfig& config,
406+
const SpaceFillType space_fill_type,
407+
const Ratio& flow = 1.0_r,
408+
const Ratio width_factor = 1.0_r,
409+
const bool spiralize = false,
410+
const Ratio speed_factor = 1.0_r,
411+
const double fan_speed = GCodePathConfig::FAN_SPEED_DEFAULT,
412+
const bool travel_to_z = true);
413+
403414
/*!
404415
* Add polygon to the gcode starting at vertex \p startIdx
405416
* \param polygon The polygon
@@ -1007,13 +1018,6 @@ class LayerPlan : public NoCopy
10071018
* \return The distance from the start of the current wall line to the first bridge segment
10081019
*/
10091020
coord_t computeDistanceToBridgeStart(const ExtrusionLine& wall, const size_t current_index, const coord_t min_bridge_line_len) const;
1010-
1011-
/*!
1012-
* \brief Calculates whether the given segment is to be treated as overhanging
1013-
* \param p0 The start point of the segment
1014-
* \param p1 The end point of the segment
1015-
*/
1016-
bool segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const;
10171021
};
10181022

10191023
} // namespace cura

include/geometry/Shape.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,14 @@ class Shape : public LinesSet<Polygon>
256256
*/
257257
void simplify(ClipperLib::PolyFillType fill_type = ClipperLib::pftEvenOdd);
258258

259+
/*!
260+
* Calculates the intersections between the given segment and all the segments of the shape
261+
* @param start The start position of the segment
262+
* @param end The end position of the segment
263+
* @return The parameters of the intersections on the segment (intersection = start + t * (end - start)), unsorted
264+
*/
265+
std::vector<float> intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const;
266+
259267
#ifdef BUILD_TESTS
260268
/*!
261269
* @brief Import the polygon from a WKT string

src/FffGcodeWriter.cpp

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3080,36 +3080,61 @@ bool FffGcodeWriter::processInsets(
30803080
gcode_layer.setBridgeWallMask(Shape());
30813081
}
30823082

3083-
const auto get_overhang_region = [&](const AngleDegrees overhang_angle) -> Shape
3083+
const Shape fully_supported_region = outlines_below.offset(-half_outer_wall_width);
3084+
const Shape part_print_region = part.outline.offset(-half_outer_wall_width);
3085+
3086+
const auto get_supported_region = [&fully_supported_region, &layer_height](const AngleDegrees& overhang_angle) -> Shape
30843087
{
30853088
// the overhang mask is set to the area of the current part's outline minus the region that is considered to be supported
30863089
// the supported region is made up of those areas that really are supported by either model or support on the layer below
30873090
// expanded to take into account the overhang angle, the greater the overhang angle, the larger the supported area is
30883091
// considered to be
3089-
const coord_t overhang_width = layer_height * std::tan(overhang_angle / (180 / std::numbers::pi));
3090-
return part.outline.offset(-half_outer_wall_width).difference(outlines_below.offset(10 + overhang_width - half_outer_wall_width)).offset(10);
3092+
const coord_t overhang_width = layer_height * std::tan(AngleRadians(overhang_angle));
3093+
return fully_supported_region.offset(overhang_width + 10);
30913094
};
30923095

3093-
// Build overhang masks for all the overhang speeds
3096+
#warning remove SVG writing
3097+
SVG svg(fmt::format("/tmp/overhang_mask_{}.svg", gcode_layer.getLayerNr().value), storage.getMachineBorder(), 0.001);
3098+
svg.writePolygons(part.outline, SVG::Color::BLACK, 0.01);
3099+
3100+
// Build supported regions for all the overhang speeds. For a visual explanation of the result, see doc/gradual_overhang_speed.svg
30943101
std::vector<LayerPlan::OverhangMask> overhang_masks;
30953102
const auto overhang_speed_factors = mesh.settings.get<std::vector<Ratio>>("wall_overhang_speed_factors");
30963103
const size_t overhang_angles_count = overhang_speed_factors.size();
3097-
if (overhang_angles_count > 0)
3104+
const auto wall_overhang_angle = mesh.settings.get<AngleDegrees>("wall_overhang_angle");
3105+
if (overhang_angles_count > 0 && wall_overhang_angle < 90.0)
30983106
{
3099-
const auto wall_overhang_angle = mesh.settings.get<AngleDegrees>("wall_overhang_angle");
3100-
if (wall_overhang_angle < 90.0)
3107+
const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast<double>(overhang_angles_count);
3108+
for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index)
31013109
{
3102-
const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast<double>(overhang_angles_count);
3103-
for (size_t angle_index = 0; angle_index < overhang_angles_count; ++angle_index)
3104-
{
3105-
const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast<double>(angle_index) * overhang_step;
3106-
overhang_masks.emplace_back(get_overhang_region(actual_wall_overhang_angle), overhang_speed_factors[angle_index]);
3107-
}
3110+
const AngleDegrees actual_wall_overhang_angle = wall_overhang_angle + static_cast<double>(angle_index) * overhang_step;
3111+
const Ratio speed_factor = angle_index == 0 ? 1.0_r : overhang_speed_factors[angle_index - 1];
3112+
3113+
overhang_masks.emplace_back(get_supported_region(actual_wall_overhang_angle), speed_factor);
3114+
}
3115+
3116+
// Add an empty region, which actually means everything and should be ignored anyway
3117+
overhang_masks.emplace_back(Shape(), overhang_speed_factors.back());
3118+
3119+
for (const auto& region : overhang_masks)
3120+
{
3121+
svg.writePolygons(region.supported_region, SVG::Color::RAINBOW, 0.01);
31083122
}
31093123
}
31103124
gcode_layer.setOverhangMasks(overhang_masks);
31113125

3112-
gcode_layer.setSeamOverhangMask(get_overhang_region(mesh.settings.get<AngleDegrees>("seam_overhang_angle")));
3126+
// the seam overhang mask is set to the area of the current part's outline minus the region that is considered to be supported,
3127+
// which will then be empty if everything is considered supported i.r.t. the angle
3128+
const AngleDegrees seam_overhang_angle = mesh.settings.get<AngleDegrees>("seam_overhang_angle");
3129+
if (seam_overhang_angle < 90.0)
3130+
{
3131+
const Shape supported_region_seam = get_supported_region(seam_overhang_angle);
3132+
gcode_layer.setSeamOverhangMask(part_print_region.difference(supported_region_seam).offset(10));
3133+
}
3134+
else
3135+
{
3136+
gcode_layer.setSeamOverhangMask(Shape());
3137+
}
31133138

31143139
const auto roofing_mask_fn = [&]() -> Shape
31153140
{

src/LayerPlan.cpp

Lines changed: 124 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,124 @@ void LayerPlan::addExtrusionMove(
558558
last_planned_position_ = p.toPoint2LL();
559559
}
560560

561+
void LayerPlan::addExtrusionMoveWithGradualOverhang(
562+
const Point3LL& p,
563+
const GCodePathConfig& config,
564+
const SpaceFillType space_fill_type,
565+
const Ratio& flow,
566+
const Ratio width_factor,
567+
const bool spiralize,
568+
const Ratio speed_factor,
569+
const double fan_speed,
570+
const bool travel_to_z)
571+
{
572+
const auto add_extrusion_move = [&](const Point3LL& target, const Ratio& overhang_speed_factor = 1.0_r)
573+
{
574+
addExtrusionMove(target, config, space_fill_type, flow, width_factor, spiralize, speed_factor * overhang_speed_factor, fan_speed, travel_to_z);
575+
};
576+
577+
if (overhang_masks_.empty() || ! last_planned_position_.has_value())
578+
{
579+
// Unable to apply gradual overhanging (probably just disabled), just add the basic extrusion move
580+
add_extrusion_move(p);
581+
return;
582+
}
583+
584+
// First, find the speed region where the segment starts
585+
const Point2LL start = last_planned_position_.value();
586+
size_t actual_speed_region_index = overhang_masks_.size() - 1; // Default to last region, which is infinity and beyond
587+
for (const auto& [index, overhang_region] : overhang_masks_ | ranges::views::drop_last(1) | ranges::views::enumerate)
588+
{
589+
if (overhang_region.supported_region.inside(start, true))
590+
{
591+
actual_speed_region_index = index;
592+
break;
593+
}
594+
}
595+
596+
// Pre-calculate the intersections of the segment with all regions (except last one, you cannot intersect an infinite plane)
597+
const Point2LL end = p.toPoint2LL();
598+
const Point2LL vector = end - start;
599+
std::vector<std::vector<float>> speed_regions_intersections;
600+
speed_regions_intersections.reserve(overhang_masks_.size() - 1);
601+
for (const OverhangMask& overhang_region : overhang_masks_ | ranges::views::drop_last(1))
602+
{
603+
std::vector<float> intersections = overhang_region.supported_region.intersectionsWithSegment(start, end);
604+
ranges::sort(intersections);
605+
speed_regions_intersections.push_back(intersections);
606+
if (! intersections.empty())
607+
{
608+
spdlog::debug("coucou");
609+
}
610+
}
611+
612+
const auto remove_previous_intersections = [&speed_regions_intersections](const float current_intersection)
613+
{
614+
for (std::vector<float>& intersections : speed_regions_intersections)
615+
{
616+
auto iterator = ranges::find_if(
617+
intersections,
618+
[&current_intersection](const float next_intersection)
619+
{
620+
return next_intersection > current_intersection;
621+
});
622+
623+
intersections.erase(intersections.begin(), iterator);
624+
}
625+
};
626+
627+
// Now move along segment and split it where we cross speed regions
628+
while (true)
629+
{
630+
// First, see if we cross either the border or our current region (go out) or the border of the inner region (go in)
631+
auto get_first_intersection = [](const std::vector<float>* intersections) -> std::optional<float>
632+
{
633+
return intersections != nullptr && ! intersections->empty() ? std::make_optional(intersections->front()) : std::nullopt;
634+
};
635+
636+
std::vector<float>* intersections_current_region
637+
= actual_speed_region_index < speed_regions_intersections.size() ? &speed_regions_intersections[actual_speed_region_index] : nullptr;
638+
const std::optional<float> first_intersection_current_region = get_first_intersection(intersections_current_region);
639+
640+
std::vector<float>* intersections_inner_region = actual_speed_region_index > 0 ? &speed_regions_intersections[actual_speed_region_index - 1] : nullptr;
641+
const std::optional<float> first_intersection_inner_region = get_first_intersection(intersections_inner_region);
642+
643+
if (first_intersection_current_region.has_value() || first_intersection_inner_region.has_value())
644+
{
645+
float intersection_parameter;
646+
size_t next_speed_region_index;
647+
648+
if (first_intersection_current_region.has_value()
649+
&& (! first_intersection_inner_region.has_value() || first_intersection_inner_region.value() > first_intersection_current_region.value()))
650+
{
651+
// We crossed the border of the current region, which means we are getting out of it to an outer region
652+
intersection_parameter = first_intersection_current_region.value();
653+
next_speed_region_index = actual_speed_region_index + 1;
654+
}
655+
else
656+
{
657+
// We crossed the border of the inner region, which means we are getting inside of it
658+
intersection_parameter = first_intersection_inner_region.value();
659+
next_speed_region_index = actual_speed_region_index - 1;
660+
}
661+
662+
// Move to intersection at current region speed
663+
const Point2LL split_position = start + vector * intersection_parameter;
664+
add_extrusion_move(split_position, overhang_masks_[actual_speed_region_index].speed_ratio);
665+
666+
// Prepare for next move in different region
667+
actual_speed_region_index = next_speed_region_index;
668+
remove_previous_intersections(intersection_parameter);
669+
}
670+
else
671+
{
672+
// We cross no border, which means we can reach the end of the segment within the current speed region, so we are done
673+
add_extrusion_move(p, overhang_masks_[actual_speed_region_index].speed_ratio);
674+
return;
675+
}
676+
}
677+
}
678+
561679
template<class PathType>
562680
void LayerPlan::addWipeTravel(const PathAdapter<PathType>& path, const coord_t wipe_distance, const bool backwards, const size_t start_index, const Point2LL& last_path_position)
563681
{
@@ -732,8 +850,6 @@ void LayerPlan::addWallLine(
732850

733851
const coord_t min_bridge_line_len = settings.get<coord_t>("bridge_wall_min_length");
734852
const Ratio bridge_wall_coast = settings.get<Ratio>("bridge_wall_coast");
735-
const Ratio overhang_speed_factor = settings.get<Ratio>("wall_overhang_speed_factor");
736-
const auto overhang_speed_factors = settings.get<std::vector<int>>("wall_overhang_speed_factors");
737853

738854
Point3LL cur_point = p0;
739855

@@ -799,14 +915,14 @@ void LayerPlan::addWallLine(
799915
else
800916
{
801917
// no coasting required, just normal segment using non-bridge config
802-
addExtrusionMove(
918+
addExtrusionMoveWithGradualOverhang(
803919
segment_end,
804920
default_config,
805921
SpaceFillType::Polygons,
806922
segment_flow,
807923
width_factor,
808924
spiralize,
809-
segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor,
925+
speed_factor,
810926
GCodePathConfig::FAN_SPEED_DEFAULT,
811927
travel_to_z);
812928
}
@@ -816,14 +932,14 @@ void LayerPlan::addWallLine(
816932
else
817933
{
818934
// no coasting required, just normal segment using non-bridge config
819-
addExtrusionMove(
935+
addExtrusionMoveWithGradualOverhang(
820936
segment_end,
821937
default_config,
822938
SpaceFillType::Polygons,
823939
segment_flow,
824940
width_factor,
825941
spiralize,
826-
segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor,
942+
speed_factor,
827943
GCodePathConfig::FAN_SPEED_DEFAULT,
828944
travel_to_z);
829945
}
@@ -921,14 +1037,14 @@ void LayerPlan::addWallLine(
9211037
else if (bridge_wall_mask_.empty())
9221038
{
9231039
// no bridges required
924-
addExtrusionMove(
1040+
addExtrusionMoveWithGradualOverhang(
9251041
p1,
9261042
default_config,
9271043
SpaceFillType::Polygons,
9281044
flow,
9291045
width_factor,
9301046
spiralize,
931-
segmentIsOnOverhang(p0, p1) ? overhang_speed_factor : speed_factor,
1047+
speed_factor,
9321048
GCodePathConfig::FAN_SPEED_DEFAULT,
9331049
travel_to_z);
9341050
}
@@ -1948,15 +2064,6 @@ void LayerPlan::addLinesInGivenOrder(
19482064
}
19492065
}
19502066

1951-
bool LayerPlan::segmentIsOnOverhang(const Point3LL& p0, const Point3LL& p1) const
1952-
{
1953-
// const OpenPolyline segment{ p0.toPoint2LL(), p1.toPoint2LL() };
1954-
// const OpenLinesSet intersected_lines = overhang_mask_.intersection(OpenLinesSet{ segment });
1955-
// return ! intersected_lines.empty() && (static_cast<double>(intersected_lines.length()) / segment.length()) > 0.5;
1956-
1957-
return false;
1958-
}
1959-
19602067
void LayerPlan::sendLineTo(const GCodePath& path, const Point3LL& position, const double extrude_speed)
19612068
{
19622069
Application::getInstance().communication_->sendLineTo(

src/geometry/Shape.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -890,6 +890,25 @@ void Shape::simplify(ClipperLib::PolyFillType fill_type)
890890
}
891891
}
892892

893+
std::vector<float> Shape::intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const
894+
{
895+
std::vector<float> result;
896+
897+
for (const Polygon& polygon : getLines())
898+
{
899+
for (auto iterator = polygon.beginSegments(); iterator != polygon.endSegments(); ++iterator)
900+
{
901+
float t, u;
902+
if (LinearAlg2D::segmentSegmentIntersection(start, end, (*iterator).start, (*iterator).end, t, u))
903+
{
904+
result.push_back(t);
905+
}
906+
}
907+
}
908+
909+
return result;
910+
}
911+
893912
void Shape::ensureManifold()
894913
{
895914
std::vector<Point2LL> duplicate_locations;

0 commit comments

Comments
 (0)