Enhanced avoid_crossing_perimeter: now try to follow the second perimeter where possible

And also trigger as early as 3 nozzle diameter when retract_before_travel is higher
Can trigger without only_retract_when_crossing_perimeters
supermerill/SuperSlicer#1794
supermerill/SuperSlicer#1793
supermerill/SuperSlicer#1790
supermerill/SuperSlicer#1349
supermerill/SuperSlicer#1061
supermerill/SuperSlicer#810
supermerill/SuperSlicer#173
supermerill/SuperSlicer#20
This commit is contained in:
supermerill 2021-11-05 20:11:16 +01:00
parent 6d641fa24b
commit 905deba7a7
7 changed files with 183 additions and 41 deletions

View File

@ -4128,19 +4128,20 @@ void GCode::_add_object_change_labels(std::string& gcode) {
// This method accepts &point in print coordinates. // This method accepts &point in print coordinates.
Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole role) Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole role)
{ {
/* Define the travel move as a line between current position and the taget point. /* Define the travel move as a line between current position and the taget point.
This is expressed in print coordinates, so it will need to be translated by This is expressed in print coordinates, so it will need to be translated by
this->origin in order to get G-code coordinates. */ this->origin in order to get G-code coordinates. */
Polyline travel { this->last_pos(), point }; Polyline travel { this->last_pos(), point };
// check whether a straight travel move would need retraction // check whether a straight travel move would need retraction
bool needs_retraction = this->needs_retraction(travel, role); bool will_cross_perimeter = this->can_cross_perimeter(travel);
bool needs_retraction = will_cross_perimeter && this->needs_retraction(travel, role);
// check whether wipe could be disabled without causing visible stringing // check whether wipe could be disabled without causing visible stringing
bool could_be_wipe_disabled = false; bool could_be_wipe_disabled = false;
// if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // if a retraction would be needed (with a low min_dist threshold), try to use avoid_crossing_perimeters to plan a
// multi-hop travel path inside the configuration space // multi-hop travel path inside the configuration space
if (needs_retraction if (will_cross_perimeter && this->needs_retraction(travel, role, scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)) * 3)
&& m_config.avoid_crossing_perimeters && m_config.avoid_crossing_perimeters
&& ! m_avoid_crossing_perimeters.disabled_once() && ! m_avoid_crossing_perimeters.disabled_once()
&& m_avoid_crossing_perimeters.is_init() && m_avoid_crossing_perimeters.is_init()
@ -4168,10 +4169,10 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole
append(retract_travel.points, travel.points); append(retract_travel.points, travel.points);
travel = std::move(retract_travel); travel = std::move(retract_travel);
} }
} else } else {
// Reset the wipe path when traveling, so one would not wipe along an old path. // Reset the wipe path when traveling, so one would not wipe along an old path.
m_wipe.reset_path(); m_wipe.reset_path();
}
//if needed, write the gcode_label_objects_end then gcode_label_objects_start //if needed, write the gcode_label_objects_end then gcode_label_objects_start
_add_object_change_labels(gcode); _add_object_change_labels(gcode);
@ -4188,9 +4189,13 @@ void GCode::write_travel_to(std::string &gcode, const Polyline& travel, std::str
this->set_last_pos(travel.points.back()); this->set_last_pos(travel.points.back());
} }
} }
bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
bool GCode::needs_retraction(const Polyline& travel, ExtrusionRole role /*=erNone*/, coordf_t max_min_dist /*=0*/)
{ {
if (travel.length() < scale_(EXTRUDER_CONFIG_WITH_DEFAULT(retract_before_travel, 0))) { coordf_t min_dist = scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(retract_before_travel, 0));
if (max_min_dist > 0)
min_dist = std::min(max_min_dist, min_dist);
if (travel.length() < min_dist) {
// skip retraction if the move is shorter than the configured threshold // skip retraction if the move is shorter than the configured threshold
return false; return false;
} }
@ -4202,15 +4207,43 @@ bool GCode::needs_retraction(const Polyline &travel, ExtrusionRole role)
// skip retraction if this is a travel move inside a support material island // skip retraction if this is a travel move inside a support material island
//FIXME not retracting over a long path may cause oozing, which in turn may result in missing material //FIXME not retracting over a long path may cause oozing, which in turn may result in missing material
// at the end of the extrusion path! // at the end of the extrusion path!
return false; return false;
} }
if (m_config.only_retract_when_crossing_perimeters && m_layer != nullptr && return true;
m_config.fill_density.value > 0 && m_layer->any_internal_region_slice_contains(travel)) }
bool GCode::can_cross_perimeter(const Polyline& travel)
{
if(m_layer != nullptr)
if ( (m_config.only_retract_when_crossing_perimeters && m_config.fill_density.value > 0) || m_config.avoid_crossing_perimeters)
{
//test && m_layer->any_internal_region_slice_contains(travel)
// Skip retraction if travel is contained in an internal slice *and* // Skip retraction if travel is contained in an internal slice *and*
// internal infill is enabled (so that stringing is entirely not visible). // internal infill is enabled (so that stringing is entirely not visible).
//FIXME any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. //note: any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first.
return false; //bool inside = false;
//BoundingBox bbtravel(travel.points);
//for (const BoundingBox &bbox : m_layer->lslices_bboxes) {
// inside = bbox.overlap(bbtravel);
// if(inside) break;
//}
////have to do a bit more work to be sure
//if (inside) {
//contained inside at least one bb
//construct m_layer_slices_offseted if needed
if (m_layer_slices_offseted.layer != m_layer) {
m_layer_slices_offseted.layer = m_layer;
m_layer_slices_offseted.diameter = scale_t(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4));
m_layer_slices_offseted.slices = offset_ex(m_layer->lslices, - m_layer_slices_offseted.diameter * 1.5);
}
// test if a expoly contains the entire travel
for (const ExPolygon &poly : m_layer_slices_offseted.slices)
if (poly.contains(travel)) {
return false;
}
//}
}
// retract if only_retract_when_crossing_perimeters is disabled or doesn't apply // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply
return true; return true;

View File

@ -318,7 +318,8 @@ private:
Polyline travel_to(std::string& gcode, const Point &point, ExtrusionRole role); Polyline travel_to(std::string& gcode, const Point &point, ExtrusionRole role);
void write_travel_to(std::string& gcode, const Polyline& travel, std::string comment); void write_travel_to(std::string& gcode, const Polyline& travel, std::string comment);
bool needs_retraction(const Polyline &travel, ExtrusionRole role = erNone); bool can_cross_perimeter(const Polyline& travel);
bool needs_retraction(const Polyline& travel, ExtrusionRole role = erNone, coordf_t max_min_dist = 0);
std::string retract(bool toolchange = false); std::string retract(bool toolchange = false);
std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } std::string unretract() { return m_writer.unlift() + m_writer.unretract(); }
std::string set_extruder(uint16_t extruder_id, double print_z, bool no_toolchange = false); std::string set_extruder(uint16_t extruder_id, double print_z, bool no_toolchange = false);
@ -361,6 +362,11 @@ private:
// Current layer processed. Insequential printing mode, only a single copy will be printed. // Current layer processed. Insequential printing mode, only a single copy will be printed.
// In non-sequential mode, all its copies will be printed. // In non-sequential mode, all its copies will be printed.
const Layer* m_layer; const Layer* m_layer;
// For crossing perimeter retraction detection (contain the layer & nozzle widdth used to construct it)
// !!!! not thread-safe !!!! if threaded per layer, please store it in the thread.
struct SliceOffsetted {
ExPolygons slices; const Layer* layer; coord_t diameter;
} m_layer_slices_offseted{ {},nullptr, 0};
double m_volumetric_speed; double m_volumetric_speed;
// Support for the extrusion role markers. Which marker is active? // Support for the extrusion role markers. Which marker is active?
ExtrusionRole m_last_extrusion_role; ExtrusionRole m_last_extrusion_role;

View File

@ -268,14 +268,97 @@ static std::vector<TravelPoint> simplify_travel(const AvoidCrossingPerimeters::B
return simplified_path; return simplified_path;
} }
bool is_in_internal_boundary(const AvoidCrossingPerimeters::Boundary& boundary, const Point& pt) {
for (const std::pair<ExPolygon, ExPolygons>& bound : boundary.boundary_growth)
for (const ExPolygon& poly : bound.second)
if (poly.contains(pt))
return true;
return false;
}
bool find_point_on_boundary(Point& pt_to_move, const AvoidCrossingPerimeters::Boundary& boundary, coord_t max_dist) {
if (!is_in_internal_boundary(boundary, pt_to_move)) {
//get nearest point
EdgeGrid::Grid::ClosestPointResult pt_closest = boundary.grid.closest_point(pt_to_move, max_dist);
Point contour_pt;
if (pt_closest.contour_idx == size_t(-1)) {
//manual search on edges
bool found = false;
for (const std::pair<ExPolygon, ExPolygons>& bound : boundary.boundary_growth)
if (bound.first.contains(pt_to_move)) {
coordf_t dist2 = std::numeric_limits<coordf_t>::max();
for (const Polygon& poly : to_polygons(bound.second)) {
Point test_point = *poly.closest_point(pt_to_move);
coordf_t dist2_test = test_point.distance_to_square(pt_to_move);
if (dist2_test < dist2) {
dist2 = dist2_test;
contour_pt = test_point;
found = true;
}
}
if(std::sqrt(dist2) > max_dist)
for (const Polygon& poly : to_polygons(bound.second)) {
Point test_point = pt_to_move.projection_onto(poly);
coordf_t dist2_test = test_point.distance_to_square(pt_to_move);
if (dist2_test < dist2) {
dist2 = dist2_test;
contour_pt = test_point;
found = true;
}
}
break;
}
if (!found) {
return false;
}
} else {
const Slic3r::Points& pts = *boundary.grid.contours()[pt_closest.contour_idx];
contour_pt = pts[pt_closest.start_point_idx].interpolate(pt_closest.t, pts[(pt_closest.start_point_idx + 1 == pts.size()) ? 0 : pt_closest.start_point_idx + 1]);
}
//push it a bit to be sure it's inside
Line l{ pt_to_move, contour_pt };
l.extend_end(SCALED_EPSILON);
pt_to_move = l.b;
}
return true;
}
// Called by avoid_perimeters() and by simplify_travel_heuristics(). // Called by avoid_perimeters() and by simplify_travel_heuristics().
static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start, const Point &real_start,
const Point &end, const Point &real_end,
coord_t extrusion_spacing,
std::vector<TravelPoint> &result_out) std::vector<TravelPoint> &result_out)
{ {
const Polygons &boundaries = boundary.boundaries; const Polygons &boundaries = boundary.boundaries;
const EdgeGrid::Grid &edge_grid = boundary.grid; const EdgeGrid::Grid &edge_grid = boundary.grid;
Point start = real_start;
Point end = real_end;
//ensure that start & end are inside
if(extrusion_spacing > 0)
if (!find_point_on_boundary(start, boundary, (extrusion_spacing * 3)/2) || !find_point_on_boundary(end, boundary, (extrusion_spacing * 3) / 2)) {
BOOST_LOG_TRIVIAL(info) << "Fail to find a point in the contour for avoid_perimeter.";
//{
// static int isazfn = 0;
// std::stringstream stri;
// stri << "avoid_peri_initbad_" << isazfn++ << ".svg";
// SVG svg(stri.str());
// for (auto elt : boundary.boundary_growth)
// svg.draw((elt.first), "grey");
// for (auto elt : boundary.boundary_growth)
// svg.draw((elt.second), "pink");
// //svg.draw((boundary.boundaries), "pink");
// svg.draw((Line{ start,end }), "red");
// svg.draw((Line{ real_start,real_end }), "green");
// svg.Close();
//}
result_out = { {start,-1}, {end,-1} };
return 0;
}
// Find all intersections between boundaries and the line segment, sort them along the line segment. // Find all intersections between boundaries and the line segment, sort them along the line segment.
std::vector<Intersection> intersections; std::vector<Intersection> intersections;
{ {
@ -366,8 +449,11 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++));
} }
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
if(start != real_start)
result_out.push_back({ real_start, -1 });
append(result_out, std::move(result)); append(result_out, std::move(result));
if (end != real_end)
result_out.push_back({ real_end, -1 });
return intersections.size(); return intersections.size();
} }
@ -375,11 +461,12 @@ static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &bo
static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary,
const Point &start, const Point &start,
const Point &end, const Point &end,
coord_t spacing,
Polyline &result_out) Polyline &result_out)
{ {
// Travel line is completely or partially inside the bounding box. // Travel line is completely or partially inside the bounding box.
std::vector<TravelPoint> path; std::vector<TravelPoint> path;
size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); size_t num_intersections = avoid_perimeters_inner(boundary, start, end, spacing, path);
result_out = to_polyline(path); result_out = to_polyline(path);
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
@ -796,7 +883,7 @@ static ExPolygons get_boundary(const Layer &layer)
append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset)); append(boundary, inner_offset(support_layer->support_islands.expolygons, perimeter_offset));
#endif #endif
auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON); auto *layer_below = layer.object()->get_first_layer_bellow_printz(layer.print_z, EPSILON);
if (layer_below) if (layer_below) //why?
append(boundary, inner_offset(layer_below->lslices, perimeter_offset)); append(boundary, inner_offset(layer_below->lslices, perimeter_offset));
// After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons // After calling inner_offset it is necessary to call union_ex because of the possibility of intersection ExPolygons
boundary = union_ex(boundary); boundary = union_ex(boundary);
@ -913,14 +1000,41 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
const ExPolygons &lslices = gcodegen.layer()->lslices; const ExPolygons &lslices = gcodegen.layer()->lslices;
const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes; const std::vector<BoundingBox> &lslices_bboxes = gcodegen.layer()->lslices_bboxes;
bool is_support_layer = dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr; bool is_support_layer = dynamic_cast<const SupportLayer *>(gcodegen.layer()) != nullptr;
if (!use_external && (is_support_layer || (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)))) { const float perimeter_spacing = get_perimeter_spacing(*gcodegen.layer());
if (!use_external && (is_support_layer || !lslices.empty()
/*|| (!lslices.empty() && !any_expolygon_contains(lslices, lslices_bboxes, m_grid_lslice, travel)) already done by the caller */
)) {
// Initialize m_internal only when it is necessary. // Initialize m_internal only when it is necessary.
if (m_internal.boundaries.empty()) if (m_internal.boundaries.empty()) {
init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); std::vector<std::pair<ExPolygon, ExPolygons>> boundary_growth;
//create better slice (on second perimeter instead of the first)
ExPolygons expoly_boundary;
//as we are going to reduce, do it expoli per expoli
for (const ExPolygon& origin : lslices) {
ExPolygons second_peri = offset_ex(origin, -perimeter_spacing * 1.5f);
//there is a collapse! try to add missing parts
if (second_peri.size() > 1) {
// get the bits that are collapsed
ExPolygons missing_parts = diff_ex({ origin }, offset_ex(second_peri, perimeter_spacing * 1.51f), true);
//have to grow a bit to be able to fit inside the reduced thing
// then intersect to be sure it don't stick out of the initial poly
missing_parts = intersection_ex({ origin }, offset_ex(missing_parts, perimeter_spacing * 1.1f));
// offset to second peri (-first) where possible, then union and reduce to the first.
second_peri = offset_ex(union_ex(missing_parts, offset_ex(origin, -perimeter_spacing * 0.9f)), -perimeter_spacing * .6f);
} else if (second_peri.size() == 0) {
// try again with the first perimeter (should be 0.5, but even with overlapping peri, it's almost never a 50% overlap, so it's better that way)
second_peri = offset_ex(origin, -perimeter_spacing * .6f);
}
append(expoly_boundary, second_peri);
boundary_growth.push_back({ origin, second_peri });
}
init_boundary(&m_internal, to_polygons(expoly_boundary));
m_internal.boundary_growth = boundary_growth;
}
// Trim the travel line by the bounding box. // Trim the travel line by the bounding box.
if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { if (!m_internal.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) {
travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); travel_intersection_count = avoid_perimeters(m_internal, startf.cast<coord_t>(), endf.cast<coord_t>(), perimeter_spacing, result_pl);
result_pl.points.front() = start; result_pl.points.front() = start;
result_pl.points.back() = end; result_pl.points.back() = end;
} }
@ -931,7 +1045,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
// Trim the travel line by the bounding box. // Trim the travel line by the bounding box.
if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) {
travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), result_pl); travel_intersection_count = avoid_perimeters(m_external, startf.cast<coord_t>(), endf.cast<coord_t>(), 0, result_pl);
result_pl.points.front() = start; result_pl.points.front() = start;
result_pl.points.back() = end; result_pl.points.back() = end;
} }

View File

@ -42,11 +42,14 @@ public:
std::vector<std::vector<float>> boundaries_params; std::vector<std::vector<float>> boundaries_params;
// Used for detection of intersection between line and any polygon from boundaries // Used for detection of intersection between line and any polygon from boundaries
EdgeGrid::Grid grid; EdgeGrid::Grid grid;
//used to move the point inside the boundary
std::vector<std::pair<ExPolygon, ExPolygons>> boundary_growth;
void clear() void clear()
{ {
boundaries.clear(); boundaries.clear();
boundaries_params.clear(); boundaries_params.clear();
boundary_growth.clear();
} }
}; };

View File

@ -143,14 +143,6 @@ public:
void restore_untyped_slices(); void restore_untyped_slices();
// Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices. // Slices merged into islands, to be used by the elephant foot compensation to trim the individual surfaces with the shrunk merged slices.
ExPolygons merged(float offset) const; ExPolygons merged(float offset) const;
template <class T> bool any_internal_region_slice_contains(const T &item) const {
for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_internal_contains(item)) return true;
return false;
}
template <class T> bool any_bottom_region_slice_contains(const T &item) const {
for (const LayerRegion *layerm : m_regions) if (layerm->slices().any_bottom_contains(item)) return true;
return false;
}
void make_perimeters(); void make_perimeters();
void make_milling_post_process(); void make_fills() { this->make_fills(nullptr, nullptr); }; void make_milling_post_process(); void make_fills() { this->make_fills(nullptr, nullptr); };

View File

@ -123,7 +123,9 @@ BoundingBox get_extents(const Lines &lines)
} }
return bbox; return bbox;
}Point Line::point_at(double distance) const { }
Point Line::point_at(double distance) const {
Point point; Point point;
double len = this->length(); double len = this->length();
point = this->a; point = this->a;

View File

@ -19,14 +19,6 @@ public:
operator ExPolygons() const; operator ExPolygons() const;
void simplify(double tolerance); void simplify(double tolerance);
void group(std::vector<SurfacesPtr> *retval); void group(std::vector<SurfacesPtr> *retval);
template <class T> bool any_internal_contains(const T &item) const {
for (const Surface &surface : this->surfaces) if (surface.has_pos_internal() && surface.expolygon.contains(item)) return true;
return false;
}
template <class T> bool any_bottom_contains(const T &item) const {
for (const Surface &surface : this->surfaces) if (surface.has_pos_bottom() && surface.expolygon.contains(item)) return true;
return false;
}
SurfacesConstPtr filter_by_type(const SurfaceType type) const; SurfacesConstPtr filter_by_type(const SurfaceType type) const;
SurfacesConstPtr filter_by_type_flag(const SurfaceType allowed, const SurfaceType not_allowed = stNone) const; SurfacesConstPtr filter_by_type_flag(const SurfaceType allowed, const SurfaceType not_allowed = stNone) const;
SurfacesConstPtr filter_by_types(const SurfaceType *types, int ntypes) const; SurfacesConstPtr filter_by_types(const SurfaceType *types, int ntypes) const;