AvoidCrossingPerimeters: Refactored for better encapsulation.

This commit is contained in:
Vojtech Bubnik 2020-11-17 10:42:27 +01:00
parent 04c2fde671
commit 62ab17bf6e
3 changed files with 303 additions and 345 deletions

View File

@ -46,8 +46,6 @@ using namespace std::literals::string_view_literals;
#endif #endif
#include <assert.h> #include <assert.h>
#include <unordered_set>
#include <boost/functional/hash.hpp>
namespace Slic3r { namespace Slic3r {
@ -232,7 +230,7 @@ namespace Slic3r {
// Move over the wipe tower. // Move over the wipe tower.
// Retract for a tool change, using the toolchange retract value and setting the priming extra length. // Retract for a tool change, using the toolchange retract value and setting the priming extra length.
gcode += gcodegen.retract(true); gcode += gcodegen.retract(true);
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
gcode += gcodegen.travel_to( gcode += gcodegen.travel_to(
wipe_tower_point_to_object_point(gcodegen, start_pos), wipe_tower_point_to_object_point(gcodegen, start_pos),
erMixed, erMixed,
@ -327,7 +325,7 @@ namespace Slic3r {
} }
// Let the planner know we are traveling between objects. // Let the planner know we are traveling between objects.
gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; gcodegen.m_avoid_crossing_perimeters.use_external_mp_once();
return gcode; return gcode;
} }
@ -1175,12 +1173,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Move to the origin position for the copy we're going to print. // Move to the origin position for the copy we're going to print.
// This happens before Z goes down to layer 0 again, so that no collision happens hopefully. // This happens before Z goes down to layer 0 again, so that no collision happens hopefully.
m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer
m_avoid_crossing_perimeters.use_external_mp_once = true; m_avoid_crossing_perimeters.use_external_mp_once();
_write(file, this->retract()); _write(file, this->retract());
_write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object")); _write(file, this->travel_to(Point(0, 0), erNone, "move to origin position for next object"));
m_enable_cooling_markers = true; m_enable_cooling_markers = true;
// Disable motion planner when traveling to first object point. // Disable motion planner when traveling to first object point.
m_avoid_crossing_perimeters.disable_once = true; m_avoid_crossing_perimeters.disable_once();
// Ff we are printing the bottom layer of an object, and we have already finished // Ff we are printing the bottom layer of an object, and we have already finished
// another one, set first layer temperatures. This happens before the Z move // another one, set first layer temperatures. This happens before the Z move
// is triggered, so machine has more time to reach such temperatures. // is triggered, so machine has more time to reach such temperatures.
@ -1998,7 +1996,7 @@ void GCode::process_layer(
if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) {
const std::pair<size_t, size_t> loops = loops_it->second; const std::pair<size_t, size_t> loops = loops_it->second;
this->set_origin(0., 0.); this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true; m_avoid_crossing_perimeters.use_external_mp();
Flow layer_skirt_flow(print.skirt_flow()); Flow layer_skirt_flow(print.skirt_flow());
layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])); layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
@ -2012,23 +2010,23 @@ void GCode::process_layer(
//FIXME using the support_material_speed of the 1st object printed. //FIXME using the support_material_speed of the 1st object printed.
gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value); gcode += this->extrude_loop(loop, "skirt", m_config.support_material_speed.value);
} }
m_avoid_crossing_perimeters.use_external_mp = false; m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers).
if (first_layer && loops.first == 0) if (first_layer && loops.first == 0)
m_avoid_crossing_perimeters.disable_once = true; m_avoid_crossing_perimeters.disable_once();
} }
// Extrude brim with the extruder of the 1st region. // Extrude brim with the extruder of the 1st region.
if (! m_brim_done) { if (! m_brim_done) {
this->set_origin(0., 0.); this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp = true; m_avoid_crossing_perimeters.use_external_mp();
for (const ExtrusionEntity *ee : print.brim().entities) { for (const ExtrusionEntity *ee : print.brim().entities) {
gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value); gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value);
} }
m_brim_done = true; m_brim_done = true;
m_avoid_crossing_perimeters.use_external_mp = false; m_avoid_crossing_perimeters.use_external_mp(false);
// Allow a straight travel move to the first object point. // Allow a straight travel move to the first object point.
m_avoid_crossing_perimeters.disable_once = true; m_avoid_crossing_perimeters.disable_once();
} }
@ -2055,7 +2053,7 @@ void GCode::process_layer(
const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift;
std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset); std::pair<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);
if (m_last_obj_copy != this_object_copy) if (m_last_obj_copy != this_object_copy)
m_avoid_crossing_perimeters.use_external_mp_once = true; m_avoid_crossing_perimeters.use_external_mp_once();
m_last_obj_copy = this_object_copy; m_last_obj_copy = this_object_copy;
this->set_origin(unscale(offset)); this->set_origin(unscale(offset));
if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) {
@ -2647,9 +2645,7 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
/* 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; Polyline travel { this->last_pos(), point };
travel.append(this->last_pos());
travel.append(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 needs_retraction = this->needs_retraction(travel, role);
@ -2660,31 +2656,28 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
// multi-hop travel path inside the configuration space // multi-hop travel path inside the configuration space
if (needs_retraction if (needs_retraction
&& m_config.avoid_crossing_perimeters && m_config.avoid_crossing_perimeters
&& ! m_avoid_crossing_perimeters.disable_once) { && ! m_avoid_crossing_perimeters.disabled_once()) {
travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled);
// check again whether the new travel path still needs a retraction // check again whether the new travel path still needs a retraction
needs_retraction = this->needs_retraction(travel, role); needs_retraction = this->needs_retraction(travel, role);
//if (needs_retraction && m_layer_index > 1) exit(0); //if (needs_retraction && m_layer_index > 1) exit(0);
} }
// Re-allow avoid_crossing_perimeters for the next travel moves // Re-allow avoid_crossing_perimeters for the next travel moves
m_avoid_crossing_perimeters.disable_once = false; m_avoid_crossing_perimeters.reset_once_modifiers();
m_avoid_crossing_perimeters.use_external_mp_once = false;
// generate G-code for the travel move // generate G-code for the travel move
std::string gcode; std::string gcode;
if (needs_retraction) { if (needs_retraction) {
if (m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once && could_be_wipe_disabled) if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled)
m_wipe.reset_path(); m_wipe.reset_path();
Point last_post_before_retract = this->last_pos(); Point last_post_before_retract = this->last_pos();
gcode += this->retract(); gcode += this->retract();
// When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters. // When "Wipe while retracting" is enabled, then extruder moves to another position, and travel from this position can cross perimeters.
// Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction() // Because of it, it is necessary to call avoid crossing perimeters for the path between previous last_post and last_post after calling retraction()
if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters && !m_avoid_crossing_perimeters.disable_once) { if (last_post_before_retract != this->last_pos() && m_config.avoid_crossing_perimeters) {
Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract); Polyline retract_travel = m_avoid_crossing_perimeters.travel_to(*this, last_post_before_retract);
retract_travel.points.reserve(retract_travel.points.size() + travel.points.size());
append(retract_travel.points, travel.points); append(retract_travel.points, travel.points);
travel = std::move(retract_travel); travel = std::move(retract_travel);
} }
@ -2693,11 +2686,10 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string
m_wipe.reset_path(); m_wipe.reset_path();
// use G1 because we rely on paths being straight (G0 may make round paths) // use G1 because we rely on paths being straight (G0 may make round paths)
Lines lines = travel.lines(); if (travel.size() >= 2) {
if (! lines.empty()) { for (size_t i = 1; i < travel.size(); ++ i)
for (const Line &line : lines) gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment);
gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); this->set_last_pos(travel.points.back());
this->set_last_pos(lines.back().b);
} }
return gcode; return gcode;
} }

View File

@ -1,8 +1,6 @@
#include "../Layer.hpp" #include "../Layer.hpp"
#include "../GCode.hpp" #include "../GCode.hpp"
#include "../EdgeGrid.hpp" #include "../EdgeGrid.hpp"
#include "../Geometry.hpp"
#include "../ShortestPath.hpp"
#include "../Print.hpp" #include "../Print.hpp"
#include "../Polygon.hpp" #include "../Polygon.hpp"
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
@ -10,15 +8,80 @@
#include "../SVG.hpp" #include "../SVG.hpp"
#include "AvoidCrossingPerimeters.hpp" #include "AvoidCrossingPerimeters.hpp"
#include <memory>
#include <numeric> #include <numeric>
#include <unordered_set> #include <unordered_set>
#include <tbb/parallel_for.h>
#include <boost/log/trivial.hpp>
namespace Slic3r { namespace Slic3r {
struct TravelPoint
{
Point point;
// Index of the polygon containing this point. A negative value indicates that the point is not on any border
int border_idx;
};
struct Intersection
{
// Index of the polygon containing this point of intersection.
size_t border_idx;
// Index of the line on the polygon containing this point of intersection.
size_t line_idx;
// Point of intersection projected on the travel path.
Point point_transformed;
// Point of intersection.
Point point;
Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point)
: border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){};
inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); }
};
struct AllIntersectionsVisitor
{
AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<Intersection> &intersections)
: grid(grid), intersections(intersections)
{}
AllIntersectionsVisitor(const EdgeGrid::Grid &grid,
std::vector<Intersection> &intersections,
const Matrix2d &transform_to_x_axis,
const Line &travel_line)
: grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line)
{}
void reset() {
intersection_set.clear();
}
bool operator()(coord_t iy, coord_t ix)
{
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second;
++it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
Point intersection_point;
if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) &&
intersection_set.find(*it_contour_and_segment) == intersection_set.end()) {
intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second,
(transform_to_x_axis * intersection_point.cast<double>()).cast<coord_t>(), intersection_point);
intersection_set.insert(*it_contour_and_segment);
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
std::vector<Intersection> &intersections;
Matrix2d transform_to_x_axis;
Line travel_line;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> intersection_set;
};
// Create a rotation matrix for projection on the given vector // Create a rotation matrix for projection on the given vector
static Matrix2d rotation_by_direction(const Point &direction) static Matrix2d rotation_by_direction(const Point &direction)
{ {
@ -185,16 +248,16 @@ static std::pair<Polygons, Polygons> split_expolygon(const ExPolygons &ex_polygo
return std::make_pair(std::move(contours), std::move(holes)); return std::make_pair(std::move(contours), std::move(holes));
} }
static Polyline to_polyline(const std::vector<AvoidCrossingPerimeters::TravelPoint> &travel) static Polyline to_polyline(const std::vector<TravelPoint> &travel)
{ {
Polyline result; Polyline result;
result.points.reserve(travel.size()); result.points.reserve(travel.size());
for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel) for (const TravelPoint &t_point : travel)
result.append(t_point.point); result.append(t_point.point);
return result; return result;
} }
static double travel_length(const std::vector<AvoidCrossingPerimeters::TravelPoint> &travel) { static double travel_length(const std::vector<TravelPoint> &travel) {
double total_length = 0; double total_length = 0;
for (size_t idx = 1; idx < travel.size(); ++idx) for (size_t idx = 1; idx < travel.size(); ++idx)
total_length += (travel[idx].point - travel[idx - 1].point).cast<double>().norm(); total_length += (travel[idx].point - travel[idx - 1].point).cast<double>().norm();
@ -203,11 +266,11 @@ static double travel_length(const std::vector<AvoidCrossingPerimeters::TravelPoi
} }
#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT #ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT
static void export_travel_to_svg(const Polygons &boundary, static void export_travel_to_svg(const Polygons &boundary,
const Line &original_travel, const Line &original_travel,
const Polyline &result_travel, const Polyline &result_travel,
const std::vector<AvoidCrossingPerimeters::Intersection> &intersections, const std::vector<Intersection> &intersections,
const std::string &path) const std::string &path)
{ {
BoundingBox bbox = get_extents(boundary); BoundingBox bbox = get_extents(boundary);
::Slic3r::SVG svg(path, bbox); ::Slic3r::SVG svg(path, bbox);
@ -217,21 +280,21 @@ static void export_travel_to_svg(const Polygons
svg.draw(original_travel.a, "black"); svg.draw(original_travel.a, "black");
svg.draw(original_travel.b, "grey"); svg.draw(original_travel.b, "grey");
for (const AvoidCrossingPerimeters::Intersection &intersection : intersections) for (const Intersection &intersection : intersections)
svg.draw(intersection.point, "lightseagreen"); svg.draw(intersection.point, "lightseagreen");
} }
static void export_travel_to_svg(const Polygons &boundary, static void export_travel_to_svg(const Polygons &boundary,
const Line &original_travel, const Line &original_travel,
const std::vector<AvoidCrossingPerimeters::TravelPoint> &result_travel, const std::vector<TravelPoint> &result_travel,
const std::vector<AvoidCrossingPerimeters::Intersection> &intersections, const std::vector<Intersection> &intersections,
const std::string &path) const std::string &path)
{ {
export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path);
} }
#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ #endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */
ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) static ExPolygons get_boundary(const Layer &layer)
{ {
const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_spacing = get_perimeter_spacing(layer);
const float perimeter_offset = perimeter_spacing / 2.f; const float perimeter_offset = perimeter_spacing / 2.f;
@ -291,7 +354,7 @@ ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer)
return result_boundary; return result_boundary;
} }
ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) static ExPolygons get_boundary_external(const Layer &layer)
{ {
const float perimeter_spacing = get_perimeter_spacing_external(layer); const float perimeter_spacing = get_perimeter_spacing_external(layer);
const float perimeter_offset = perimeter_spacing / 2.f; const float perimeter_offset = perimeter_spacing / 2.f;
@ -328,11 +391,12 @@ ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer)
} }
// Returns a direction of the shortest path along the polygon boundary // Returns a direction of the shortest path along the polygon boundary
AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines, enum class Direction { Forward, Backward };
const size_t start_idx, static Direction get_shortest_direction(const Lines &lines,
const size_t end_idx, const size_t start_idx,
const Point &intersection_first, const size_t end_idx,
const Point &intersection_last) const Point &intersection_first,
const Point &intersection_last)
{ {
double total_length_forward = (lines[start_idx].b - intersection_first).cast<double>().norm(); double total_length_forward = (lines[start_idx].b - intersection_first).cast<double>().norm();
double total_length_backward = (lines[start_idx].a - intersection_first).cast<double>().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast<double>().norm();
@ -358,165 +422,14 @@ AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_directi
return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward;
} }
std::vector<AvoidCrossingPerimeters::TravelPoint> AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid, static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid& edge_grid, const std::vector<TravelPoint>& travel, const Polygons& boundaries, const bool use_heuristics);
const std::vector<TravelPoint> &travel,
const Polygons &boundaries,
const bool use_heuristics)
{
struct Visitor
{
Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
bool operator()(coord_t iy, coord_t ix) static size_t avoid_perimeters(const Polygons &boundaries,
{ const EdgeGrid::Grid &edge_grid,
assert(pt_current != nullptr); const Point &start,
assert(pt_next != nullptr); const Point &end,
// Called with a row and colum of the grid cell, which is intersected by a line. const bool use_heuristics,
auto cell_data_range = grid.cell_data_range(iy, ix); std::vector<TravelPoint> *result_out)
this->intersect = false;
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) {
this->intersect = true;
return false;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
const Slic3r::Point *pt_current = nullptr;
const Slic3r::Point *pt_next = nullptr;
bool intersect = false;
} visitor(edge_grid);
std::vector<TravelPoint> simplified_path;
simplified_path.reserve(travel.size());
simplified_path.emplace_back(travel.front());
// Try to skip some points in the path.
for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) {
const Point &current_point = travel[point_idx - 1].point;
TravelPoint next = travel[point_idx];
visitor.pt_current = &current_point;
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
if (travel[point_idx_2].point == current_point) {
next = travel[point_idx_2];
point_idx = point_idx_2;
continue;
}
visitor.pt_next = &travel[point_idx_2].point;
edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
// Check if deleting point causes crossing a boundary
if (!visitor.intersect) {
next = travel[point_idx_2];
point_idx = point_idx_2;
}
}
simplified_path.emplace_back(next);
}
if(use_heuristics) {
simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries);
std::reverse(simplified_path.begin(),simplified_path.end());
simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries);
std::reverse(simplified_path.begin(),simplified_path.end());
}
return simplified_path;
}
std::vector<AvoidCrossingPerimeters::TravelPoint> AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries)
{
std::vector<TravelPoint> simplified_path;
std::vector<Intersection> intersections;
AllIntersectionsVisitor visitor(edge_grid, intersections);
simplified_path.reserve(travel.size());
simplified_path.emplace_back(travel.front());
for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) {
// Skip all indexes on the same polygon
while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) {
simplified_path.emplace_back(travel[point_idx]);
point_idx++;
}
if (point_idx < travel.size()) {
const TravelPoint &current = travel[point_idx - 1];
const TravelPoint &next = travel[point_idx];
TravelPoint new_next = next;
size_t new_point_idx = point_idx;
double path_length = (next.point - current.point).cast<double>().norm();
double new_path_shorter_by = 0.;
size_t border_idx_change_count = 0;
std::vector<TravelPoint> shortcut;
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
const TravelPoint &possible_new_next = travel[point_idx_2];
if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx)
border_idx_change_count++;
if (border_idx_change_count >= 2)
break;
path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast<double>().norm();
double shortcut_length = (possible_new_next.point - current.point).cast<double>().norm();
if ((path_length - shortcut_length) <= scale_(10.0))
continue;
intersections.clear();
visitor.reset();
visitor.travel_line.a = current.point;
visitor.travel_line.b = possible_new_next.point;
visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector());
edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor);
if (!intersections.empty()) {
std::sort(intersections.begin(), intersections.end());
size_t last_border_idx_count = 0;
for (const Intersection &intersection : intersections)
if (int(intersection.border_idx) == possible_new_next.border_idx)
++last_border_idx_count;
if (last_border_idx_count > 0)
continue;
std::vector<TravelPoint> possible_shortcut;
avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut);
double shortcut_travel = travel_length(possible_shortcut);
if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) {
new_path_shorter_by = path_length - shortcut_travel;
shortcut = possible_shortcut;
new_next = possible_new_next;
new_point_idx = point_idx_2;
}
}
}
if (!shortcut.empty()) {
assert(shortcut.size() >= 2);
simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1);
point_idx = new_point_idx;
}
simplified_path.emplace_back(new_next);
}
}
return simplified_path;
}
size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries,
const EdgeGrid::Grid &edge_grid,
const Point &start,
const Point &end,
const bool use_heuristics,
std::vector<TravelPoint> *result_out)
{ {
const Point direction = end - start; const Point direction = end - start;
Matrix2d transform_to_x_axis = rotation_by_direction(direction); Matrix2d transform_to_x_axis = rotation_by_direction(direction);
@ -608,10 +521,164 @@ size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &bound
return intersections.size(); return intersections.size();
} }
bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, static std::vector<TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
const Line & original_travel, const std::vector<TravelPoint> &travel,
const Polyline &result_travel, const Polygons &boundaries)
const size_t intersection_count) {
std::vector<TravelPoint> simplified_path;
std::vector<Intersection> intersections;
AllIntersectionsVisitor visitor(edge_grid, intersections);
simplified_path.reserve(travel.size());
simplified_path.emplace_back(travel.front());
for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) {
// Skip all indexes on the same polygon
while (point_idx < travel.size() && travel[point_idx - 1].border_idx == travel[point_idx].border_idx) {
simplified_path.emplace_back(travel[point_idx]);
point_idx++;
}
if (point_idx < travel.size()) {
const TravelPoint &current = travel[point_idx - 1];
const TravelPoint &next = travel[point_idx];
TravelPoint new_next = next;
size_t new_point_idx = point_idx;
double path_length = (next.point - current.point).cast<double>().norm();
double new_path_shorter_by = 0.;
size_t border_idx_change_count = 0;
std::vector<TravelPoint> shortcut;
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
const TravelPoint &possible_new_next = travel[point_idx_2];
if (travel[point_idx_2 - 1].border_idx != travel[point_idx_2].border_idx)
border_idx_change_count++;
if (border_idx_change_count >= 2)
break;
path_length += (possible_new_next.point - travel[point_idx_2 - 1].point).cast<double>().norm();
double shortcut_length = (possible_new_next.point - current.point).cast<double>().norm();
if ((path_length - shortcut_length) <= scale_(10.0))
continue;
intersections.clear();
visitor.reset();
visitor.travel_line.a = current.point;
visitor.travel_line.b = possible_new_next.point;
visitor.transform_to_x_axis = rotation_by_direction(visitor.travel_line.vector());
edge_grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor);
if (!intersections.empty()) {
std::sort(intersections.begin(), intersections.end());
size_t last_border_idx_count = 0;
for (const Intersection &intersection : intersections)
if (int(intersection.border_idx) == possible_new_next.border_idx)
++last_border_idx_count;
if (last_border_idx_count > 0)
continue;
std::vector<TravelPoint> possible_shortcut;
avoid_perimeters(boundaries, edge_grid, current.point, possible_new_next.point, false, &possible_shortcut);
double shortcut_travel = travel_length(possible_shortcut);
if (path_length > shortcut_travel && (path_length - shortcut_travel) > new_path_shorter_by) {
new_path_shorter_by = path_length - shortcut_travel;
shortcut = possible_shortcut;
new_next = possible_new_next;
new_point_idx = point_idx_2;
}
}
}
if (!shortcut.empty()) {
assert(shortcut.size() >= 2);
simplified_path.insert(simplified_path.end(), shortcut.begin() + 1, shortcut.end() - 1);
point_idx = new_point_idx;
}
simplified_path.emplace_back(new_next);
}
}
return simplified_path;
}
static std::vector<TravelPoint> simplify_travel(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries,
const bool use_heuristics)
{
struct Visitor
{
Visitor(const EdgeGrid::Grid &grid) : grid(grid) {}
bool operator()(coord_t iy, coord_t ix)
{
assert(pt_current != nullptr);
assert(pt_next != nullptr);
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
this->intersect = false;
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
if (Geometry::segments_intersect(segment.first, segment.second, *pt_current, *pt_next)) {
this->intersect = true;
return false;
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
const Slic3r::Point *pt_current = nullptr;
const Slic3r::Point *pt_next = nullptr;
bool intersect = false;
} visitor(edge_grid);
std::vector<TravelPoint> simplified_path;
simplified_path.reserve(travel.size());
simplified_path.emplace_back(travel.front());
// Try to skip some points in the path.
for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) {
const Point &current_point = travel[point_idx - 1].point;
TravelPoint next = travel[point_idx];
visitor.pt_current = &current_point;
for (size_t point_idx_2 = point_idx + 1; point_idx_2 < travel.size(); ++point_idx_2) {
if (travel[point_idx_2].point == current_point) {
next = travel[point_idx_2];
point_idx = point_idx_2;
continue;
}
visitor.pt_next = &travel[point_idx_2].point;
edge_grid.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor);
// Check if deleting point causes crossing a boundary
if (!visitor.intersect) {
next = travel[point_idx_2];
point_idx = point_idx_2;
}
}
simplified_path.emplace_back(next);
}
if(use_heuristics) {
simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries);
std::reverse(simplified_path.begin(),simplified_path.end());
simplified_path = simplify_travel_heuristics(edge_grid, simplified_path, boundaries);
std::reverse(simplified_path.begin(),simplified_path.end());
}
return simplified_path;
}
static bool need_wipe(const GCode &gcodegen,
const ExPolygons &slice,
const Line &original_travel,
const Polyline &result_travel,
const size_t intersection_count)
{ {
bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.;
bool wipe_needed = false; bool wipe_needed = false;
@ -622,19 +689,19 @@ bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen,
// The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test. // The original layer is intersected with defined boundaries. Then it is necessary to make a detailed test.
// If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes. // If the z-lift is enabled, then a wipe is needed when the original travel leads above the holes.
if (z_lift_enabled) { if (z_lift_enabled) {
if (any_expolygon_contains(m_slice, original_travel)) { if (any_expolygon_contains(slice, original_travel)) {
// Check if original_travel and result_travel are not same. // Check if original_travel and result_travel are not same.
// If both are the same, then it is possible to skip testing of result_travel // If both are the same, then it is possible to skip testing of result_travel
if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) { if (result_travel.size() == 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) {
wipe_needed = false; wipe_needed = false;
} else { } else {
wipe_needed = !any_expolygon_contains(m_slice, result_travel); wipe_needed = !any_expolygon_contains(slice, result_travel);
} }
} else { } else {
wipe_needed = true; wipe_needed = true;
} }
} else { } else {
wipe_needed = !any_expolygon_contains(m_slice, result_travel); wipe_needed = !any_expolygon_contains(slice, result_travel);
} }
} }
@ -646,7 +713,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
{ {
// If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset). // If use_external, then perform the path planning in the world coordinate system (correcting for the gcodegen offset).
// Otherwise perform the path planning in the coordinate system of the active object. // Otherwise perform the path planning in the coordinate system of the active object.
bool use_external = this->use_external_mp || this->use_external_mp_once; bool use_external = m_use_external_mp || m_use_external_mp_once;
Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0);
Point start = gcodegen.last_pos() + scaled_origin; Point start = gcodegen.last_pos() + scaled_origin;
Point end = point + scaled_origin; Point end = point + scaled_origin;
@ -659,9 +726,9 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
std::vector<TravelPoint> result; std::vector<TravelPoint> result;
auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end); auto [start_clamped, end_clamped] = clamp_endpoints_by_bounding_box(use_external ? m_bbox_external : m_bbox, start, end);
if (use_external) if (use_external)
travel_intersection_count = this->avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result); travel_intersection_count = avoid_perimeters(m_boundaries_external, m_grid_external, start_clamped, end_clamped, true, &result);
else else
travel_intersection_count = this->avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result); travel_intersection_count = avoid_perimeters(m_boundaries, m_grid, start_clamped, end_clamped, true, &result);
result_pl = to_polyline(result); result_pl = to_polyline(result);
} }
@ -678,7 +745,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &
result_pl.translate(-scaled_origin); result_pl.translate(-scaled_origin);
*could_be_wipe_disabled = false; *could_be_wipe_disabled = false;
} else } else
*could_be_wipe_disabled = !need_wipe(gcodegen, travel, result_pl, travel_intersection_count); *could_be_wipe_disabled = !need_wipe(gcodegen, m_slice, travel, result_pl, travel_intersection_count);
return result_pl; return result_pl;
} }

View File

@ -5,120 +5,40 @@
#include "../ExPolygon.hpp" #include "../ExPolygon.hpp"
#include "../EdgeGrid.hpp" #include "../EdgeGrid.hpp"
#include <unordered_set>
#include <boost/functional/hash.hpp>
namespace Slic3r { namespace Slic3r {
// Forward declarations. // Forward declarations.
class GCode; class GCode;
class Layer; class Layer;
class Point; class Point;
class Print;
class PrintObject;
struct PrintInstance;
using PrintObjectPtrs = std::vector<PrintObject *>;
class AvoidCrossingPerimeters class AvoidCrossingPerimeters
{ {
public: public:
struct Intersection // Routing around the objects vs. inside a single object.
void use_external_mp(bool use = true) { m_use_external_mp = use; };
void use_external_mp_once() { m_use_external_mp_once = true; }
void disable_once() { m_disabled_once = true; }
bool disabled_once() const { return m_disabled_once; }
void reset_once_modifiers() { m_use_external_mp_once = false; m_disabled_once = false; }
void init_layer(const Layer &layer);
Polyline travel_to(const GCode& gcodegen, const Point& point)
{ {
// Index of the polygon containing this point of intersection. bool could_be_wipe_disabled;
size_t border_idx; return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
// Index of the line on the polygon containing this point of intersection. }
size_t line_idx;
// Point of intersection projected on the travel path.
Point point_transformed;
// Point of intersection.
Point point;
Intersection(size_t border_idx, size_t line_idx, const Point &point_transformed, const Point &point) Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
: border_idx(border_idx), line_idx(line_idx), point_transformed(point_transformed), point(point){};
inline bool operator<(const Intersection &other) const { return this->point_transformed.x() < other.point_transformed.x(); }
};
struct TravelPoint
{
Point point;
// Index of the polygon containing this point. A negative value indicates that the point is not on any border
int border_idx;
};
struct AllIntersectionsVisitor
{
AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector<AvoidCrossingPerimeters::Intersection> &intersections)
: grid(grid), intersections(intersections)
{}
AllIntersectionsVisitor(const EdgeGrid::Grid &grid,
std::vector<AvoidCrossingPerimeters::Intersection> &intersections,
const Matrix2d &transform_to_x_axis,
const Line &travel_line)
: grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line)
{}
void reset() {
intersection_set.clear();
}
bool operator()(coord_t iy, coord_t ix)
{
// Called with a row and colum of the grid cell, which is intersected by a line.
auto cell_data_range = grid.cell_data_range(iy, ix);
for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second;
++it_contour_and_segment) {
// End points of the line segment and their vector.
auto segment = grid.segment(*it_contour_and_segment);
Point intersection_point;
if (travel_line.intersection(Line(segment.first, segment.second), &intersection_point) &&
intersection_set.find(*it_contour_and_segment) == intersection_set.end()) {
intersections.emplace_back(it_contour_and_segment->first, it_contour_and_segment->second,
(transform_to_x_axis * intersection_point.cast<double>()).cast<coord_t>(), intersection_point);
intersection_set.insert(*it_contour_and_segment);
}
}
// Continue traversing the grid along the edge.
return true;
}
const EdgeGrid::Grid &grid;
std::vector<AvoidCrossingPerimeters::Intersection> &intersections;
Matrix2d transform_to_x_axis;
Line travel_line;
std::unordered_set<std::pair<size_t, size_t>, boost::hash<std::pair<size_t, size_t>>> intersection_set;
};
enum class Direction { Forward, Backward };
private: private:
static ExPolygons get_boundary(const Layer &layer); bool m_use_external_mp { false };
// just for the next travel move
static ExPolygons get_boundary_external(const Layer &layer); bool m_use_external_mp_once { false };
// this flag disables avoid_crossing_perimeters just for the next travel move
static Direction get_shortest_direction( // we enable it by default for the first travel move in print
const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); bool m_disabled_once { true };
static std::vector<AvoidCrossingPerimeters::TravelPoint> simplify_travel(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries,
const bool use_heuristics);
static std::vector<AvoidCrossingPerimeters::TravelPoint> simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid,
const std::vector<TravelPoint> &travel,
const Polygons &boundaries);
static size_t avoid_perimeters(const Polygons &boundaries,
const EdgeGrid::Grid &grid,
const Point &start,
const Point &end,
const bool use_heuristics,
std::vector<TravelPoint> *result_out);
bool need_wipe(const GCode &gcodegen, const Line &original_travel, const Polyline &result_travel, const size_t intersection_count);
// Slice of layer with elephant foot compensation // Slice of layer with elephant foot compensation
ExPolygons m_slice; ExPolygons m_slice;
@ -132,27 +52,6 @@ private:
BoundingBox m_bbox_external; BoundingBox m_bbox_external;
EdgeGrid::Grid m_grid; EdgeGrid::Grid m_grid;
EdgeGrid::Grid m_grid_external; EdgeGrid::Grid m_grid_external;
public:
// this flag triggers the use of the external configuration space
bool use_external_mp { false };
// just for the next travel move
bool use_external_mp_once { false };
// this flag disables avoid_crossing_perimeters just for the next travel move
// we enable it by default for the first travel move in print
bool disable_once { true };
AvoidCrossingPerimeters() = default;
Polyline travel_to(const GCode& gcodegen, const Point& point)
{
bool could_be_wipe_disabled;
return this->travel_to(gcodegen, point, &could_be_wipe_disabled);
}
Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled);
void init_layer(const Layer &layer);
}; };
} // namespace Slic3r } // namespace Slic3r