Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CURA-11966 reduce speed on overhang #2186

Merged
merged 11 commits into from
Feb 3, 2025
Prev Previous commit
Next Next commit
Basically working gradual overhang speed
  • Loading branch information
wawanbreton committed Dec 11, 2024
commit afd9a27e64e52b14b6147dc984654e072cae8ff6
857 changes: 857 additions & 0 deletions doc/gradual_overhang_speed.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 14 additions & 10 deletions include/LayerPlan.h
Original file line number Diff line number Diff line change
@@ -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<OverhangMask>
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<OverhangMask> 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
8 changes: 8 additions & 0 deletions include/geometry/Shape.h
Original file line number Diff line number Diff line change
@@ -256,6 +256,14 @@ class Shape : public LinesSet<Polygon>
*/
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<float> intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const;

#ifdef BUILD_TESTS
/*!
* @brief Import the polygon from a WKT string
53 changes: 39 additions & 14 deletions src/FffGcodeWriter.cpp
Original file line number Diff line number Diff line change
@@ -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<LayerPlan::OverhangMask> overhang_masks;
const auto overhang_speed_factors = mesh.settings.get<std::vector<Ratio>>("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<AngleDegrees>("wall_overhang_angle");
if (overhang_angles_count > 0 && wall_overhang_angle < 90.0)
{
const auto wall_overhang_angle = mesh.settings.get<AngleDegrees>("wall_overhang_angle");
if (wall_overhang_angle < 90.0)
const AngleDegrees overhang_step = (90.0 - wall_overhang_angle) / static_cast<double>(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<double>(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<double>(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<double>(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<AngleDegrees>("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<AngleDegrees>("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
{
141 changes: 124 additions & 17 deletions src/LayerPlan.cpp
Original file line number Diff line number Diff line change
@@ -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<std::vector<float>> 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<float> 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<float>& intersections : speed_regions_intersections)
{
auto iterator = ranges::find_if(
intersections,
[&current_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<float>* intersections) -> std::optional<float>
{
return intersections != nullptr && ! intersections->empty() ? std::make_optional(intersections->front()) : std::nullopt;
};

std::vector<float>* intersections_current_region
= actual_speed_region_index < speed_regions_intersections.size() ? &speed_regions_intersections[actual_speed_region_index] : nullptr;
const std::optional<float> first_intersection_current_region = get_first_intersection(intersections_current_region);

std::vector<float>* intersections_inner_region = actual_speed_region_index > 0 ? &speed_regions_intersections[actual_speed_region_index - 1] : nullptr;
const std::optional<float> 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<class PathType>
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)
{
@@ -732,8 +850,6 @@ void LayerPlan::addWallLine(

const coord_t min_bridge_line_len = settings.get<coord_t>("bridge_wall_min_length");
const Ratio bridge_wall_coast = settings.get<Ratio>("bridge_wall_coast");
const Ratio overhang_speed_factor = settings.get<Ratio>("wall_overhang_speed_factor");
const auto overhang_speed_factors = settings.get<std::vector<int>>("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<double>(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(
19 changes: 19 additions & 0 deletions src/geometry/Shape.cpp
Original file line number Diff line number Diff line change
@@ -890,6 +890,25 @@ void Shape::simplify(ClipperLib::PolyFillType fill_type)
}
}

std::vector<float> Shape::intersectionsWithSegment(const Point2LL& start, const Point2LL& end) const
{
std::vector<float> 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<Point2LL> duplicate_locations;
Loading