diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index c475c6575..11e37afc6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -99,6 +99,8 @@ add_library(libslic3r STATIC GCode/WipeTower.hpp GCode/GCodeProcessor.cpp GCode/GCodeProcessor.hpp + GCode/AvoidCrossingPerimeters.cpp + GCode/AvoidCrossingPerimeters.hpp GCode.cpp GCode.hpp GCodeReader.cpp @@ -126,8 +128,6 @@ add_library(libslic3r STATIC CustomGCode.hpp Arrange.hpp Arrange.cpp - MotionPlanner.cpp - MotionPlanner.hpp MultiPoint.cpp MultiPoint.hpp MutablePriorityQueue.hpp diff --git a/src/libslic3r/EdgeGrid.hpp b/src/libslic3r/EdgeGrid.hpp index e8a8d4374..c3bc869d4 100644 --- a/src/libslic3r/EdgeGrid.hpp +++ b/src/libslic3r/EdgeGrid.hpp @@ -259,7 +259,14 @@ public: { const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; size_t ipt = contour_and_segment_idx.second; - return std::pair(ipts[ipt], ipts[(ipt + 1 == ipts.size()) ? 0 : ipt + 1]); + return std::pair(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); + } + + Line line(const std::pair &contour_and_segment_idx) const + { + const Slic3r::Points &ipts = *m_contours[contour_and_segment_idx.first]; + size_t ipt = contour_and_segment_idx.second; + return Line(ipts[ipt], ipts[ipt + 1 == ipts.size() ? 0 : ipt + 1]); } protected: diff --git a/src/libslic3r/ElephantFootCompensation.cpp b/src/libslic3r/ElephantFootCompensation.cpp index c11157627..1e50ade5a 100644 --- a/src/libslic3r/ElephantFootCompensation.cpp +++ b/src/libslic3r/ElephantFootCompensation.cpp @@ -254,11 +254,12 @@ std::vector contour_distance2(const EdgeGrid::Grid &grid, const size_t id grid(grid), idx_contour(idx_contour), contour(*grid.contours()[idx_contour]), resampled_point_parameters(resampled_point_parameters), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {} void init(const Points &contour, const Point &apoint) { - this->idx_point = &apoint - contour.data(); - this->point = apoint; - this->found = false; - this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); - } + this->idx_point = &apoint - contour.data(); + this->point = apoint; + this->found = false; + this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); + this->distance = std::numeric_limits::max(); + } bool operator()(coord_t iy, coord_t ix) { // Called with a row and colum of the grid cell, which is intersected by a line. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 989cfd442..02cc126a3 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -42,11 +42,11 @@ void ExPolygon::scale(double factor) hole.scale(factor); } -void ExPolygon::translate(double x, double y) +void ExPolygon::translate(const Point &p) { - contour.translate(x, y); + contour.translate(p); for (Polygon &hole : holes) - hole.translate(x, y); + hole.translate(p); } void ExPolygon::rotate(double angle) diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 0056100b1..b4651abc2 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -42,7 +42,8 @@ public: operator Polylines() const; void clear() { contour.points.clear(); holes.clear(); } void scale(double factor); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } + void translate(const Point &vector); void rotate(double angle); void rotate(double angle, const Point ¢er); double area() const; diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 94b832dbf..7f08ef579 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -10,6 +10,7 @@ #include "ShortestPath.hpp" #include "Print.hpp" #include "Utils.hpp" +#include "ClipperUtils.hpp" #include "libslic3r.h" #include @@ -89,94 +90,6 @@ namespace Slic3r { return ok; } - void AvoidCrossingPerimeters::init_external_mp(const Print& print) - { - m_external_mp = Slic3r::make_unique(union_ex(this->collect_contours_all_layers(print.objects()))); - } - - // Plan a travel move while minimizing the number of perimeter crossings. - // point is in unscaled coordinates, in the coordinate system of the current active object - // (set by gcodegen.set_origin()). - Polyline AvoidCrossingPerimeters::travel_to(const GCode& gcodegen, const Point& point) - { - // 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. - bool use_external = this->use_external_mp || this->use_external_mp_once; - Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - Polyline result = (use_external ? m_external_mp.get() : m_layer_mp.get())-> - shortest_path(gcodegen.last_pos() + scaled_origin, point + scaled_origin); - if (use_external) - result.translate(-scaled_origin); - return result; - } - - // Collect outer contours of all objects over all layers. - // Discard objects only containing thin walls (offset would fail on an empty polygon). - // Used by avoid crossing perimeters feature. - Polygons AvoidCrossingPerimeters::collect_contours_all_layers(const PrintObjectPtrs& objects) - { - Polygons islands; - for (const PrintObject* object : objects) { - // Reducing all the object slices into the Z projection in a logarithimc fashion. - // First reduce to half the number of layers. - std::vector polygons_per_layer((object->layers().size() + 1) / 2); - tbb::parallel_for(tbb::blocked_range(0, object->layers().size() / 2), - [&object, &polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - const Layer* layer1 = object->layers()[i * 2]; - const Layer* layer2 = object->layers()[i * 2 + 1]; - Polygons polys; - polys.reserve(layer1->lslices.size() + layer2->lslices.size()); - for (const ExPolygon& expoly : layer1->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - for (const ExPolygon& expoly : layer2->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer[i] = union_(polys); - } - }); - if (object->layers().size() & 1) { - const Layer* layer = object->layers().back(); - Polygons polys; - polys.reserve(layer->lslices.size()); - for (const ExPolygon& expoly : layer->lslices) - //FIXME no holes? - polys.emplace_back(expoly.contour); - polygons_per_layer.back() = union_(polys); - } - // Now reduce down to a single layer. - size_t cnt = polygons_per_layer.size(); - while (cnt > 1) { - tbb::parallel_for(tbb::blocked_range(0, cnt / 2), - [&polygons_per_layer](const tbb::blocked_range& range) { - for (size_t i = range.begin(); i < range.end(); ++i) { - Polygons polys; - polys.reserve(polygons_per_layer[i * 2].size() + polygons_per_layer[i * 2 + 1].size()); - polygons_append(polys, polygons_per_layer[i * 2]); - polygons_append(polys, polygons_per_layer[i * 2 + 1]); - polygons_per_layer[i * 2] = union_(polys); - } - }); - for (size_t i = 1; i < cnt / 2; ++i) - polygons_per_layer[i] = std::move(polygons_per_layer[i * 2]); - if (cnt & 1) - polygons_per_layer[cnt / 2] = std::move(polygons_per_layer[cnt - 1]); - cnt = (cnt + 1) / 2; - } - // And collect copies of the objects. - for (const PrintInstance& instance : object->instances()) { - // All the layers were reduced to the 1st item of polygons_per_layer. - size_t i = islands.size(); - polygons_append(islands, polygons_per_layer.front()); - for (; i < islands.size(); ++i) - islands[i].translate(instance.shift); - } - } - return islands; - } - - std::string OozePrevention::pre_toolchange(GCode& gcodegen) { std::string gcode; @@ -322,7 +235,7 @@ namespace Slic3r { // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. 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( wipe_tower_point_to_object_point(gcodegen, start_pos), erMixed, @@ -419,7 +332,7 @@ namespace Slic3r { } // 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; } @@ -1235,13 +1148,6 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // Set other general things. _write(file, this->preamble()); - // Initialize a motion planner for object-to-object travel moves. - m_avoid_crossing_perimeters.reset(); - if (print.config().avoid_crossing_perimeters.value) { - m_avoid_crossing_perimeters.init_external_mp(print); - print.throw_if_canceled(); - } - // Calculate wiping points if needed DoExport::init_ooze_prevention(print, m_ooze_prevention); print.throw_if_canceled(); @@ -1277,12 +1183,12 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu // 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. 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->travel_to(Point(0, 0), erNone, "move to origin position for next object")); m_enable_cooling_markers = true; // 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 // another one, set first layer temperatures. This happens before the Z move // is triggered, so machine has more time to reach such temperatures. @@ -2108,7 +2014,7 @@ void GCode::process_layer( if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { const std::pair loops = loops_it->second; 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()); 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(); @@ -2122,23 +2028,23 @@ void GCode::process_layer( //FIXME using the support_material_speed of the 1st object printed. 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). 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. if (! m_brim_done) { 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) { gcode += this->extrude_entity(*ee, "brim", m_config.support_material_speed.value); } 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. - m_avoid_crossing_perimeters.disable_once = true; + m_avoid_crossing_perimeters.disable_once(); } @@ -2158,15 +2064,14 @@ void GCode::process_layer( m_config.apply(instance_to_print.print_object.config(), true); m_layer = layers[instance_to_print.layer_id].layer(); if (m_config.avoid_crossing_perimeters) - m_avoid_crossing_perimeters.init_layer_mp(union_ex(m_layer->lslices, true)); - + m_avoid_crossing_perimeters.init_layer(*m_layer); if (this->config().gcode_label_objects) gcode += std::string("; printing object ") + instance_to_print.print_object.model_object()->name + " id:" + std::to_string(instance_to_print.layer_id) + " copy " + std::to_string(instance_to_print.instance_id) + "\n"; // When starting a new object, use the external motion planner for the first travel move. const Point &offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); 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; this->set_origin(unscale(offset)); if (instance_to_print.object_by_extruder.support != nullptr && !print_wipe_extrusions) { @@ -2758,43 +2663,51 @@ 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. This is expressed in print coordinates, so it will need to be translated by this->origin in order to get G-code coordinates. */ - Polyline travel; - travel.append(this->last_pos()); - travel.append(point); + Polyline travel { this->last_pos(), point }; // 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); + // check whether wipe could be disabled without causing visible stringing + bool could_be_wipe_disabled = false; // if a retraction would be needed, try to use avoid_crossing_perimeters to plan a // multi-hop travel path inside the configuration space if (needs_retraction && m_config.avoid_crossing_perimeters - && ! m_avoid_crossing_perimeters.disable_once) { - travel = m_avoid_crossing_perimeters.travel_to(*this, point); - + && ! m_avoid_crossing_perimeters.disabled_once()) { + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); // check again whether the new travel path still needs a retraction needs_retraction = this->needs_retraction(travel, role); //if (needs_retraction && m_layer_index > 1) exit(0); } // Re-allow avoid_crossing_perimeters for the next travel moves - m_avoid_crossing_perimeters.disable_once = false; - m_avoid_crossing_perimeters.use_external_mp_once = false; + m_avoid_crossing_perimeters.reset_once_modifiers(); // generate G-code for the travel move std::string gcode; - if (needs_retraction) + if (needs_retraction) { + if (m_config.avoid_crossing_perimeters && could_be_wipe_disabled) + m_wipe.reset_path(); + + Point last_post_before_retract = this->last_pos(); gcode += this->retract(); - else + // 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() + 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); + append(retract_travel.points, travel.points); + travel = std::move(retract_travel); + } + } else // Reset the wipe path when traveling, so one would not wipe along an old path. m_wipe.reset_path(); // use G1 because we rely on paths being straight (G0 may make round paths) - Lines lines = travel.lines(); - if (! lines.empty()) { - for (const Line &line : lines) - gcode += m_writer.travel_to_xy(this->point_to_gcode(line.b), comment); - this->set_last_pos(lines.back().b); + if (travel.size() >= 2) { + for (size_t i = 1; i < travel.size(); ++ i) + gcode += m_writer.travel_to_xy(this->point_to_gcode(travel.points[i]), comment); + this->set_last_pos(travel.points.back()); } return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8a8154976..bf1cb1273 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -5,10 +5,10 @@ #include "ExPolygon.hpp" #include "GCodeWriter.hpp" #include "Layer.hpp" -#include "MotionPlanner.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" +#include "GCode/AvoidCrossingPerimeters.hpp" #include "GCode/CoolingBuffer.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" @@ -35,35 +35,6 @@ namespace { struct Item; } struct PrintInstance; using PrintObjectPtrs = std::vector; -class AvoidCrossingPerimeters { -public: - - // this flag triggers the use of the external configuration space - bool use_external_mp; - bool use_external_mp_once; // just for the next travel move - - // 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; - - AvoidCrossingPerimeters() : use_external_mp(false), use_external_mp_once(false), disable_once(true) {} - ~AvoidCrossingPerimeters() {} - - void reset() { m_external_mp.reset(); m_layer_mp.reset(); } - void init_external_mp(const Print &print); - void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - - Polyline travel_to(const GCode &gcodegen, const Point &point); - -private: - // For initializing the regions to avoid. - static Polygons collect_contours_all_layers(const PrintObjectPtrs& objects); - - std::unique_ptr m_external_mp; - std::unique_ptr m_layer_mp; -}; - - class OozePrevention { public: bool enable; @@ -185,6 +156,7 @@ public: const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } + const GCodeWriter& writer() const { return m_writer; } PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } // Process a template through the placeholder parser, collect error messages to be reported diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp new file mode 100644 index 000000000..8262c3857 --- /dev/null +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -0,0 +1,1325 @@ +#include "../Layer.hpp" +#include "../GCode.hpp" +#include "../EdgeGrid.hpp" +#include "../Print.hpp" +#include "../Polygon.hpp" +#include "../ExPolygon.hpp" +#include "../Geometry.hpp" +#include "../ClipperUtils.hpp" +#include "../SVG.hpp" +#include "AvoidCrossingPerimeters.hpp" + +#include +#include + +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. + Point point; + // Distance from the first point in the corresponding boundary + float distance; +}; + +// Finding all intersections of a set of contours with a line segment. +struct AllIntersectionsVisitor +{ + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) : grid(grid), intersections(intersections) + { + intersection_set.reserve(intersections.capacity()); + } + + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections, const Line &travel_line) + : grid(grid), intersections(intersections), travel_line(travel_line) + { + intersection_set.reserve(intersections.capacity()); + } + + 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) { + Point intersection_point; + if (travel_line.intersection(grid.line(*it_contour_and_segment), &intersection_point) && + intersection_set.find(*it_contour_and_segment) == intersection_set.end()) { + intersections.push_back({ it_contour_and_segment->first, it_contour_and_segment->second, intersection_point }); + intersection_set.insert(*it_contour_and_segment); + } + } + // Continue traversing the grid along the edge. + return true; + } + + const EdgeGrid::Grid &grid; + std::vector &intersections; + Line travel_line; + std::unordered_set, boost::hash>> intersection_set; +}; + +// Visitor to check for any collision of a line segment with any contour stored inside the edge_grid. +struct FirstIntersectionVisitor +{ + explicit FirstIntersectionVisitor(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; +}; + +// point_idx is the index from which is different vertex is searched. +template +static Point find_first_different_vertex(const Polygon &polygon, const size_t point_idx, const Point &point) +{ + assert(point_idx < polygon.size()); + // Solve case when vertex on passed index point_idx is different that pass point. This helps the following code keep simple. + if (point != polygon.points[point_idx]) + return polygon.points[point_idx]; + + auto line_idx = (int(point_idx) + 1) % int(polygon.points.size()); + assert(line_idx != int(point_idx)); + if constexpr (forward) + for (; point == polygon.points[line_idx] && line_idx != int(point_idx); line_idx = line_idx + 1 < int(polygon.points.size()) ? line_idx + 1 : 0); + else + for (; point == polygon.points[line_idx] && line_idx != int(point_idx); line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(polygon.points.size()) - 1); + assert(point != polygon.points[line_idx]); + return polygon.points[line_idx]; +} + +static Vec2d three_points_inward_normal(const Point &left, const Point &middle, const Point &right) +{ + assert(left != middle); + assert(middle != right); + return (perp(Point(middle - left)).cast().normalized() + perp(Point(right - middle)).cast().normalized()).normalized(); +} + +// Compute normal of the polygon's vertex in an inward direction +static Vec2d get_polygon_vertex_inward_normal(const Polygon &polygon, const size_t point_idx) +{ + const size_t left_idx = prev_idx_modulo(point_idx, polygon.points); + const size_t right_idx = next_idx_modulo(point_idx, polygon.points); + const Point &middle = polygon.points[point_idx]; + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); + return three_points_inward_normal(left, middle, right); +} + +// Compute offset of point_idx of the polygon in a direction of inward normal +static Point get_polygon_vertex_offset(const Polygon &polygon, const size_t point_idx, const int offset) +{ + return polygon.points[point_idx] + (get_polygon_vertex_inward_normal(polygon, point_idx) * double(offset)).cast(); +} + +// Compute offset (in the direction of inward normal) of the point(passed on "middle") based on the nearest points laying on the polygon (left_idx and right_idx). +static Point get_middle_point_offset(const Polygon &polygon, const size_t left_idx, const size_t right_idx, const Point &middle, const coord_t offset) +{ + const Point &left = find_first_different_vertex(polygon, left_idx, middle); + const Point &right = find_first_different_vertex(polygon, right_idx, middle); + return middle + (three_points_inward_normal(left, middle, right) * double(offset)).cast(); +} + +static Polyline to_polyline(const std::vector &travel) +{ + Polyline result; + result.points.reserve(travel.size()); + for (const TravelPoint &t_point : travel) + result.append(t_point.point); + return result; +} + +// #define AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const Polyline &result_travel, + const std::vector &intersections, + const std::string &path) +{ + BoundingBox bbox = get_extents(boundary); + ::Slic3r::SVG svg(path, bbox); + svg.draw_outline(boundary, "green"); + svg.draw(original_travel, "blue"); + svg.draw(result_travel, "red"); + svg.draw(original_travel.a, "black"); + svg.draw(original_travel.b, "grey"); + + for (const Intersection &intersection : intersections) + svg.draw(intersection.point, "lightseagreen"); +} + +static void export_travel_to_svg(const Polygons &boundary, + const Line &original_travel, + const std::vector &result_travel, + const std::vector &intersections, + const std::string &path) +{ + export_travel_to_svg(boundary, original_travel, to_polyline(result_travel), intersections, path); +} +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + +// Returns a direction of the shortest path along the polygon boundary +enum class Direction { Forward, Backward }; +// Returns a direction of the shortest path along the polygon boundary +static Direction get_shortest_direction(const AvoidCrossingPerimeters::Boundary &boundary, + const Intersection &intersection_first, + const Intersection &intersection_second, + float contour_length) +{ + assert(intersection_first.border_idx == intersection_second.border_idx); + const Polygon &poly = boundary.boundaries[intersection_first.border_idx]; + float dist_first = intersection_first.distance; + float dist_second = intersection_second.distance; + + assert(dist_first >= 0.f && dist_first <= contour_length); + assert(dist_second >= 0.f && dist_second <= contour_length); + + bool reversed = false; + if (dist_first > dist_second) { + std::swap(dist_first, dist_second); + reversed = true; + } + float total_length_forward = dist_second - dist_first; + float total_length_backward = dist_first + contour_length - dist_second; + if (reversed) std::swap(total_length_forward, total_length_backward); + + total_length_forward -= (intersection_first.point - poly[intersection_first.line_idx]).cast().norm(); + total_length_backward -= (poly[(intersection_first.line_idx + 1) % poly.size()] - intersection_first.point).cast().norm(); + + total_length_forward -= (poly[(intersection_second.line_idx + 1) % poly.size()] - intersection_second.point).cast().norm(); + total_length_backward -= (intersection_second.point - poly[intersection_second.line_idx]).cast().norm(); + + if (total_length_forward < total_length_backward) return Direction::Forward; + return Direction::Backward; +} + +// Straighten the travel path as long as it does not collide with the contours stored in edge_grid. +static std::vector simplify_travel(const AvoidCrossingPerimeters::Boundary &boundary, const std::vector &travel) +{ + FirstIntersectionVisitor visitor(boundary.grid); + std::vector simplified_path; + simplified_path.reserve(travel.size()); + simplified_path.emplace_back(travel.front()); + + // Try to skip some points in the path. + //FIXME maybe use a binary search to trim the line? + //FIXME how about searching tangent point at long segments? + for (size_t point_idx = 1; point_idx < travel.size(); ++point_idx) { + const Point ¤t_point = travel[point_idx - 1].point; + TravelPoint next = travel[point_idx]; + + visitor.pt_current = ¤t_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; + boundary.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); + } + + return simplified_path; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + std::vector &result_out) +{ + const Polygons &boundaries = boundary.boundaries; + const EdgeGrid::Grid &edge_grid = boundary.grid; + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + intersections.reserve(boundaries.size()); + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + for (Intersection &intersection : intersections) + intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx]; + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + } + + std::vector result; + result.push_back({start, -1}); + + auto crossing_boundary_from_inside = [&boundary](const Point &start, const Intersection &intersection) { + const Polygon &poly = boundary.boundaries[intersection.border_idx]; + Vec2d poly_line = Line(poly[intersection.line_idx], poly[(intersection.line_idx + 1) % poly.size()]).normal().cast(); + Vec2d intersection_vec = (intersection.point - start).cast(); + return poly_line.normalized().dot(intersection_vec.normalized()) >= 0; + }; + + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + // The entry point to the boundary polygon + const Intersection &intersection_first = *it_first; + if(!crossing_boundary_from_inside(start, intersection_first)) + continue; + // Skip the it_first from the search for the farthest exit point from the boundary polygon + auto it_last_item = std::make_reverse_iterator(it_first) - 1; + // Search for the farthest intersection different from it_first but with the same border_idx + auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { + return intersection_first.border_idx == intersection.border_idx; + }); + + // Append the first intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; + // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the + // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the + // appended point will be inside the polygon and not on the polygon border. + result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Check if intersection line also exit the boundary polygon + if (it_second_r != it_last_item) { + // Transform reverse iterator to forward + auto it_second = it_second_r.base() - 1; + // The exit point from the boundary polygon + const Intersection &intersection_second = *it_second; + Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, + boundary.boundaries_params[intersection_first.border_idx].back()); + // Append the path around the border into the path + if (shortest_direction == Direction::Forward) + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + else + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); + // Skip intersections in between + it_first = it_second; + } + } + + result.push_back({end, -1}); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + if (! intersections.empty()) + result = simplify_travel(boundary, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + append(result_out, std::move(result)); + return intersections.size(); +} + +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const GCode &gcodegen, const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + Polyline &result_out) +{ + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(gcodegen, boundary, start, end, path); + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundary.boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + return num_intersections; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() and AvoidCrossingPerimeters::travel_to() +// FIXME Lukas H.: Maybe similar approach could also be used for ExPolygon::contains() +static bool any_expolygon_contains(const ExPolygons &ex_polygons, + const std::vector &ex_polygons_bboxes, + const EdgeGrid::Grid &grid_lslice, + const Line &travel) +{ + assert(ex_polygons.size() == ex_polygons_bboxes.size()); + if(!grid_lslice.bbox().contains(travel.a) || !grid_lslice.bbox().contains(travel.b)) + return false; + + FirstIntersectionVisitor visitor(grid_lslice); + visitor.pt_current = &travel.a; + visitor.pt_next = &travel.b; + grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + if (!visitor.intersect) { + for (const ExPolygon &ex_polygon : ex_polygons) { + const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()]; + if (bbox.contains(travel.a) && bbox.contains(travel.b) && ex_polygon.contains(travel.a)) + return true; + } + } + return false; +} + +// Check if anyone of ExPolygons contains whole travel. +// called by need_wipe() +static bool any_expolygon_contains(const ExPolygons &ex_polygons, const std::vector &ex_polygons_bboxes, const EdgeGrid::Grid &grid_lslice, const Polyline &travel) +{ + assert(ex_polygons.size() == ex_polygons_bboxes.size()); + if(std::any_of(travel.points.begin(), travel.points.end(), [&grid_lslice](const Point &point) { return !grid_lslice.bbox().contains(point); })) + return false; + + FirstIntersectionVisitor visitor(grid_lslice); + bool any_intersection = false; + for (size_t line_idx = 1; line_idx < travel.size(); ++line_idx) { + visitor.pt_current = &travel.points[line_idx - 1]; + visitor.pt_next = &travel.points[line_idx]; + grid_lslice.visit_cells_intersecting_line(*visitor.pt_current, *visitor.pt_next, visitor); + any_intersection = visitor.intersect; + if (any_intersection) break; + } + + if (!any_intersection) { + for (const ExPolygon &ex_polygon : ex_polygons) { + const BoundingBox &bbox = ex_polygons_bboxes[&ex_polygon - &ex_polygons.front()]; + if (std::all_of(travel.points.begin(), travel.points.end(), [&bbox](const Point &point) { return bbox.contains(point); }) && + ex_polygon.contains(travel.points.front())) + return true; + } + } + return false; +} + +static bool need_wipe(const GCode &gcodegen, + const EdgeGrid::Grid &grid_lslice, + const Line &original_travel, + const Polyline &result_travel, + const size_t intersection_count) +{ + const ExPolygons &lslices = gcodegen.layer()->lslices; + const std::vector &lslices_bboxes = gcodegen.layer()->lslices_bboxes; + bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; + bool wipe_needed = false; + + // If the original unmodified path doesn't have any intersection with boundary, then it is entirely inside the object otherwise is entirely + // outside the object. + if (intersection_count > 0) { + // 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 (z_lift_enabled) { + if (any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, original_travel)) { + // 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 + wipe_needed = !(result_travel.size() > 2 && result_travel.first_point() == original_travel.a && result_travel.last_point() == original_travel.b) && + !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + } else { + wipe_needed = true; + } + } else { + wipe_needed = !any_expolygon_contains(lslices, lslices_bboxes, grid_lslice, result_travel); + } + } + + return wipe_needed; +} + +// called by get_perimeter_spacing() / get_perimeter_spacing_external() +static inline float get_default_perimeter_spacing(const PrintObject &print_object) +{ + std::vector printing_extruders = print_object.object_extruders(); + assert(!printing_extruders.empty()); + float avg_extruder = 0; + for(unsigned int extruder_id : printing_extruders) + avg_extruder += float(scale_(print_object.print()->config().nozzle_diameter.get_at(extruder_id))); + avg_extruder /= printing_extruders.size(); + return avg_extruder; +} + +// called by get_boundary() +static float get_perimeter_spacing(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const LayerRegion *layer_region : layer.regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + +// called by get_boundary_external() +static float get_perimeter_spacing_external(const Layer &layer) +{ + size_t regions_count = 0; + float perimeter_spacing = 0.f; + for (const PrintObject *object : layer.object()->print()->objects()) + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + if (layer_region != nullptr && !layer_region->slices.empty()) { + perimeter_spacing += layer_region->flow(frPerimeter).scaled_spacing(); + ++ regions_count; + } + + assert(perimeter_spacing >= 0.f); + if (regions_count != 0) + perimeter_spacing /= float(regions_count); + else + perimeter_spacing = get_default_perimeter_spacing(*layer.object()); + return perimeter_spacing; +} + +// Adds points around all vertices so that the offset affects only small sections around these vertices. +static void resample_polygon(Polygon &polygon, double dist_from_vertex) +{ + Points resampled_poly; + resampled_poly.reserve(3 * polygon.size()); + resampled_poly.emplace_back(polygon.first_point()); + for (size_t pt_idx = 1; pt_idx < polygon.size(); ++pt_idx) { + const Point &p1 = polygon[pt_idx - 1]; + const Point &p2 = polygon[pt_idx]; + double line_length = (p2 - p1).cast().norm(); + Vector line_vec = ((p2 - p1).cast().normalized() * dist_from_vertex).cast(); + if (line_length > 2 * dist_from_vertex) { + resampled_poly.emplace_back(p1 + line_vec); + resampled_poly.emplace_back(p2 - line_vec); + } + resampled_poly.emplace_back(polygon[pt_idx]); + } + polygon.points = std::move(resampled_poly); +} + +static void resample_expolygon(ExPolygon &ex_polygon, double dist_from_vertex) +{ + resample_polygon(ex_polygon.contour, dist_from_vertex); + for (Polygon &polygon : ex_polygon.holes) resample_polygon(polygon, dist_from_vertex); +} + +static void resample_expolygons(ExPolygons &ex_polygons, double dist_from_vertex) +{ + for (ExPolygon &ex_poly : ex_polygons) resample_expolygon(ex_poly, dist_from_vertex); +} + +static void precompute_polygon_distances(const Polygon &polygon, std::vector &polygon_distances_out) +{ + polygon_distances_out.assign(polygon.size() + 1, 0.f); + for (size_t point_idx = 1; point_idx < polygon.size(); ++point_idx) + polygon_distances_out[point_idx] = polygon_distances_out[point_idx - 1] + (polygon[point_idx].cast() - polygon[point_idx - 1].cast()).norm(); + polygon_distances_out.back() = polygon_distances_out[polygon.size() - 1] + (polygon.last_point().cast() - polygon.first_point().cast()).norm(); +} + +static void precompute_expolygon_distances(const ExPolygon &ex_polygon, std::vector> &expolygon_distances_out) +{ + expolygon_distances_out.assign(ex_polygon.holes.size() + 1, std::vector()); + precompute_polygon_distances(ex_polygon.contour, expolygon_distances_out.front()); + for (size_t hole_idx = 0; hole_idx < ex_polygon.holes.size(); ++hole_idx) + precompute_polygon_distances(ex_polygon.holes[hole_idx], expolygon_distances_out[hole_idx + 1]); +} + +// It is highly based on the function contour_distance2 from the ElephantFootCompensation.cpp +static std::vector contour_distance(const EdgeGrid::Grid &grid, + const std::vector &poly_distances, + const size_t contour_idx, + const Polygon &polygon, + double compensation, + double search_radius) +{ + assert(! polygon.empty()); + assert(polygon.size() >= 2); + + std::vector out; + + if (polygon.size() > 2) + { + struct Visitor { + Visitor(const EdgeGrid::Grid &grid, const size_t contour_idx, const std::vector &polygon_distances, double dist_same_contour_accept, double dist_same_contour_reject) : + grid(grid), idx_contour(contour_idx), contour(*grid.contours()[contour_idx]), boundary_parameters(polygon_distances), dist_same_contour_accept(dist_same_contour_accept), dist_same_contour_reject(dist_same_contour_reject) {} + + void init(const Points &contour, const Point &apoint) + { + this->idx_point = &apoint - contour.data(); + this->point = apoint; + this->found = false; + this->dir_inside = this->dir_inside_at_point(contour, this->idx_point); + this->distance = std::numeric_limits::max(); + } + + 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 = this->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. + std::pair segment = this->grid.segment(*it_contour_and_segment); + const Vec2d v = (segment.second - segment.first).cast(); + const Vec2d va = (this->point - segment.first).cast(); + const double l2 = v.squaredNorm(); // avoid a sqrt + const double t = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2); + // Closest point from this->point to the segment. + const Vec2d foot = segment.first.cast() + t * v; + const Vec2d bisector = foot - this->point.cast(); + const double dist = bisector.norm(); + + if ((!this->found || dist < this->distance) && this->dir_inside.dot(bisector) > 0) { + bool accept = true; + if (it_contour_and_segment->first == idx_contour) { + // Complex case: The closest segment originates from the same contour as the starting point. + // Reject the closest point if its distance along the contour is reasonable compared to the current contour bisector + // (this->pt, foot). + const Slic3r::Points &ipts = *grid.contours()[it_contour_and_segment->first]; + double param_lo = boundary_parameters[this->idx_point]; + double param_hi = t * sqrt(l2); + double param_end = boundary_parameters.back(); + const size_t ipt = it_contour_and_segment->second; + if (ipt + 1 < ipts.size()) + param_hi += boundary_parameters[ipt > 0 ? ipt - 1 : 0]; + if (param_lo > param_hi) + std::swap(param_lo, param_hi); + assert(param_lo > -SCALED_EPSILON && param_lo <= param_end + SCALED_EPSILON); + assert(param_hi > -SCALED_EPSILON && param_hi <= param_end + SCALED_EPSILON); + double dist_along_contour = std::min(param_hi - param_lo, param_lo + param_end - param_hi); + if (dist_along_contour < dist_same_contour_accept) + accept = false; + else if (dist < dist_same_contour_reject + SCALED_EPSILON) { + // this->point is close to foot. This point will only be accepted if the path along the contour is significantly + // longer than the bisector. That is, the path shall not bulge away from the bisector too much. + // Bulge is estimated by 0.6 of the circle circumference drawn around the bisector. + // Test whether the contour is convex or concave. + bool inside = (t == 0.) ? this->inside_corner(ipts, ipt, this->point) : + (t == 1.) ? this->inside_corner(ipts, ipt + 1 == ipts.size() ? 0 : ipt + 1, this->point) : + this->left_of_segment(ipts, ipt, this->point); + accept = inside && dist_along_contour > 0.6 * M_PI * dist; + } + } + if (accept && (!this->found || dist < this->distance)) { + // Simple case: Just measure the shortest distance. + this->distance = dist; + this->found = true; + } + } + } + // Continue traversing the grid. + return true; + } + + const EdgeGrid::Grid &grid; + const size_t idx_contour; + const Points &contour; + + const std::vector &boundary_parameters; + const double dist_same_contour_accept; + const double dist_same_contour_reject; + + size_t idx_point; + Point point; + // Direction inside the contour from idx_point, not normalized. + Vec2d dir_inside; + bool found; + double distance; + + private: + static Vec2d dir_inside_at_point(const Points &contour, size_t i) + { + size_t iprev = prev_idx_modulo(i, contour); + size_t inext = next_idx_modulo(i, contour); + Vec2d v1 = (contour[i] - contour[iprev]).cast(); + Vec2d v2 = (contour[inext] - contour[i]).cast(); + return Vec2d(-v1.y() - v2.y(), v1.x() + v2.x()); + } + + static bool inside_corner(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) + { + const Vec2d pt = pt_oposite.cast(); + size_t iprev = prev_idx_modulo(i, contour); + size_t inext = next_idx_modulo(i, contour); + Vec2d v1 = (contour[i] - contour[iprev]).cast(); + Vec2d v2 = (contour[inext] - contour[i]).cast(); + bool left_of_v1 = cross2(v1, pt - contour[iprev].cast()) > 0.; + bool left_of_v2 = cross2(v2, pt - contour[i].cast()) > 0.; + return cross2(v1, v2) > 0 ? left_of_v1 && left_of_v2 : // convex corner + left_of_v1 || left_of_v2; // concave corner + } + + static bool left_of_segment(const Slic3r::Points &contour, size_t i, const Point &pt_oposite) + { + const Vec2d pt = pt_oposite.cast(); + size_t inext = next_idx_modulo(i, contour); + Vec2d v = (contour[inext] - contour[i]).cast(); + return cross2(v, pt - contour[i].cast()) > 0.; + } + } visitor(grid, contour_idx, poly_distances, 0.5 * compensation * M_PI, search_radius); + + out.reserve(polygon.size()); + Point radius_vector(search_radius, search_radius); + for (const Point &pt : polygon.points) { + visitor.init(polygon.points, pt); + grid.visit_cells_intersecting_box(BoundingBox(pt - radius_vector, pt + radius_vector), visitor); + out.emplace_back(float(visitor.found ? std::min(visitor.distance, search_radius) : search_radius)); + } + } + + return out; +} + +// Polygon offset which ensures that if a polygon breaks up into several separate parts, the original polygon will be used in these places. +static ExPolygons inner_offset(const ExPolygons &ex_polygons, double offset, double min_contour_width = scale_(0.001)) +{ + double search_radius = 2. * (offset + min_contour_width); + ExPolygons ex_poly_result = ex_polygons; + resample_expolygons(ex_poly_result, offset / 2); + + for (ExPolygon &ex_poly : ex_poly_result) { + BoundingBox bbox(get_extents(ex_poly)); + bbox.offset(SCALED_EPSILON); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(ex_poly, coord_t(0.7 * search_radius)); + + std::vector> ex_poly_distances; + precompute_expolygon_distances(ex_poly, ex_poly_distances); + + std::vector> offsets; + offsets.reserve(ex_poly.holes.size() + 1); + for (size_t idx_contour = 0; idx_contour <= ex_poly.holes.size(); ++idx_contour) { + const Polygon &poly = (idx_contour == 0) ? ex_poly.contour : ex_poly.holes[idx_contour - 1]; + assert(poly.is_counter_clockwise() == (idx_contour == 0)); + std::vector distances = contour_distance(grid, ex_poly_distances[idx_contour], idx_contour, poly, offset, search_radius); + for (float &distance : distances) { + if (distance < min_contour_width) + distance = 0.f; + else if (distance > min_contour_width + 2. * offset) + distance = - float(offset); + else + distance = - (distance - float(min_contour_width)) / 2.f; + } + offsets.emplace_back(distances); + } + + ExPolygons offset_ex_poly = variable_offset_inner_ex(ex_poly, offsets); + // If variable_offset_inner_ex produces empty result, then original ex_polygon is used + if (offset_ex_poly.size() == 1) { + ex_poly = std::move(offset_ex_poly.front()); + } else if (offset_ex_poly.size() > 1) { + // fix_after_inner_offset called inside variable_offset_inner_ex sometimes produces + // tiny artefacts polygons, so these artefacts are removed. + double max_area = offset_ex_poly.front().area(); + size_t max_area_idx = 0; + for (size_t poly_idx = 1; poly_idx < offset_ex_poly.size(); ++poly_idx) { + double area = offset_ex_poly[poly_idx].area(); + if (max_area < area) { + max_area = area; + max_area_idx = poly_idx; + } + } + ex_poly = std::move(offset_ex_poly[max_area_idx]); + } + } + return ex_poly_result; +} + +// called by AvoidCrossingPerimeters::travel_to() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary = union_ex(inner_offset(layer.lslices, perimeter_offset)); + // Collect all top layers that will not be crossed. + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return boundary; +} + +// called by AvoidCrossingPerimeters::travel_to() +static Polygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + Polygons boundary; + // Collect all holes for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + Polygons polygons_per_obj; + if (const Layer *l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const ExPolygon &island : l->lslices) append(polygons_per_obj, island.holes); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + append(boundary, polygons_per_obj); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + + // Used offset_ex for cases when another object will be in the hole of another polygon + boundary = to_polygons(offset_ex(boundary, perimeter_offset)); + // Reverse all polygons for making normals point from the polygon out. + for (Polygon &poly : boundary) + poly.reverse(); + + return boundary; +} + +static void init_boundary_distances(AvoidCrossingPerimeters::Boundary *boundary) +{ + boundary->boundaries_params.assign(boundary->boundaries.size(), std::vector()); + for (size_t poly_idx = 0; poly_idx < boundary->boundaries.size(); ++poly_idx) + precompute_polygon_distances(boundary->boundaries[poly_idx], boundary->boundaries_params[poly_idx]); +} + +static void init_boundary(AvoidCrossingPerimeters::Boundary *boundary, Polygons &&boundary_polygons) +{ + boundary->clear(); + boundary->boundaries = std::move(boundary_polygons); + + BoundingBox bbox(get_extents(boundary->boundaries)); + bbox.offset(SCALED_EPSILON); + boundary->bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + boundary->grid.set_bbox(bbox); + // FIXME 1mm grid? + boundary->grid.create(boundary->boundaries, coord_t(scale_(1.))); + init_boundary_distances(boundary); +} + +// Plan travel, which avoids perimeter crossings by following the boundaries of the layer. +Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +{ + // 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. + 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); + const Point start = gcodegen.last_pos() + scaled_origin; + const Point end = point + scaled_origin; + const Line travel(start, end); + + Polyline result_pl; + size_t travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + + if (!use_external && !gcodegen.layer()->lslices.empty() && !any_expolygon_contains(gcodegen.layer()->lslices, gcodegen.layer()->lslices_bboxes, m_grid_lslice, travel)) { + // Initialize m_internal only when it is necessary. + if (m_internal.boundaries.empty()) + init_boundary(&m_internal, to_polygons(get_boundary(*gcodegen.layer()))); + + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, m_internal.bbox)) { + travel_intersection_count = avoid_perimeters(gcodegen, m_internal, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } + } else if(use_external) { + // Initialize m_external only when exist any external travel for the current layer. + if (m_external.boundaries.empty()) + init_boundary(&m_external, get_boundary_external(*gcodegen.layer())); + + // Trim the travel line by the bounding box. + if (!m_external.boundaries.empty() && Geometry::liang_barsky_line_clipping(startf, endf, m_external.bbox)) { + travel_intersection_count = avoid_perimeters(gcodegen, m_external, startf.cast(), endf.cast(), result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } + } + + if(result_pl.empty()) { + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; + } + + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + + if (use_external) { + result_pl.translate(-scaled_origin); + *could_be_wipe_disabled = false; + } else + *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); + + return result_pl; +} + +// ************************************* AvoidCrossingPerimeters::init_layer() ***************************************** + +void AvoidCrossingPerimeters::init_layer(const Layer &layer) +{ + m_internal.clear(); + m_external.clear(); + + BoundingBox bbox_slice(get_extents(layer.lslices)); + bbox_slice.offset(SCALED_EPSILON); + + m_grid_lslice.set_bbox(bbox_slice); + //FIXME 1mm grid? + m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); +} + +#if 0 +static double travel_length(const std::vector &travel) { + double total_length = 0; + for (size_t idx = 1; idx < travel.size(); ++idx) + total_length += (travel[idx].point - travel[idx - 1].point).cast().norm(); + + return total_length; +} + +// Called by avoid_perimeters() and by simplify_travel_heuristics(). +static size_t avoid_perimeters_inner(const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + std::vector &result_out) +{ + const Polygons &boundaries = boundary.boundaries; + const EdgeGrid::Grid &edge_grid = boundary.grid; + // Find all intersections between boundaries and the line segment, sort them along the line segment. + std::vector intersections; + { + intersections.reserve(boundaries.size()); + AllIntersectionsVisitor visitor(edge_grid, intersections, Line(start, end)); + edge_grid.visit_cells_intersecting_line(start, end, visitor); + Vec2d dir = (end - start).cast(); + for (Intersection &intersection : intersections) + intersection.distance = boundary.boundaries_params[intersection.border_idx][intersection.line_idx]; + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + } + + std::vector result; + result.push_back({start, -1}); + for (auto it_first = intersections.begin(); it_first != intersections.end(); ++it_first) { + // The entry point to the boundary polygon + const Intersection &intersection_first = *it_first; + // Skip the it_first from the search for the farthest exit point from the boundary polygon + auto it_last_item = std::make_reverse_iterator(it_first) - 1; + // Search for the farthest intersection different from it_first but with the same border_idx + auto it_second_r = std::find_if(intersections.rbegin(), it_last_item, [&intersection_first](const Intersection &intersection) { + return intersection_first.border_idx == intersection.border_idx; + }); + + // Append the first intersection into the path + size_t left_idx = intersection_first.line_idx; + size_t right_idx = intersection_first.line_idx + 1 == boundaries[intersection_first.border_idx].points.size() ? 0 : intersection_first.line_idx + 1; + // Offset of the polygon's point using get_middle_point_offset is used to simplify the calculation of intersection between the + // boundary and the travel. The appended point is translated in the direction of inward normal. This translation ensures that the + // appended point will be inside the polygon and not on the polygon border. + result.push_back({get_middle_point_offset(boundaries[intersection_first.border_idx], left_idx, right_idx, intersection_first.point, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Check if intersection line also exit the boundary polygon + if (it_second_r != it_last_item) { + // Transform reverse iterator to forward + auto it_second = it_second_r.base() - 1; + // The exit point from the boundary polygon + const Intersection &intersection_second = *it_second; + Direction shortest_direction = get_shortest_direction(boundary, intersection_first, intersection_second, + boundary.boundaries_params[intersection_first.border_idx].back()); + // Append the path around the border into the path + if (shortest_direction == Direction::Forward) + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx + 1 < int(boundaries[intersection_first.border_idx].size()) ? line_idx + 1 : 0) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_first.border_idx], + (line_idx + 1 == int(boundaries[intersection_first.border_idx].points.size())) ? 0 : (line_idx + 1), coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + else + for (int line_idx = int(intersection_first.line_idx); line_idx != int(intersection_second.line_idx); + line_idx = line_idx - 1 >= 0 ? line_idx - 1 : int(boundaries[intersection_first.border_idx].size()) - 1) + result.push_back({get_polygon_vertex_offset(boundaries[intersection_second.border_idx], line_idx + 0, coord_t(SCALED_EPSILON)), int(intersection_first.border_idx)}); + + // Append the farthest intersection into the path + left_idx = intersection_second.line_idx; + right_idx = (intersection_second.line_idx >= (boundaries[intersection_second.border_idx].points.size() - 1)) ? 0 : (intersection_second.line_idx + 1); + result.push_back({get_middle_point_offset(boundaries[intersection_second.border_idx], left_idx, right_idx, intersection_second.point, coord_t(SCALED_EPSILON)), int(intersection_second.border_idx)}); + // Skip intersections in between + it_first = it_second; + } + } + + result.push_back({end, -1}); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-initial-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + if (! intersections.empty()) + result = simplify_travel(boundary, result); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), result, intersections, + debug_out_path("AvoidCrossingPerimetersInner-final-%d.svg", iRun++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + append(result_out, std::move(result)); + return intersections.size(); +} + +static std::vector simplify_travel_heuristics(const AvoidCrossingPerimeters::Boundary &boundary, + const std::vector &travel) +{ + std::vector simplified_path; + std::vector intersections; + AllIntersectionsVisitor visitor(boundary.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 ¤t = 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().norm(); + double new_path_shorter_by = 0.; + size_t border_idx_change_count = 0; + std::vector 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().norm(); + double shortcut_length = (possible_new_next.point - current.point).cast().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; + boundary.grid.visit_cells_intersecting_line(visitor.travel_line.a, visitor.travel_line.b, visitor); + if (!intersections.empty()) { + Vec2d dir = (visitor.travel_line.b - visitor.travel_line.a).cast(); + std::sort(intersections.begin(), intersections.end(), [dir](const auto &l, const auto &r) { return (r.point - l.point).template cast().dot(dir) > 0.; }); + 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 possible_shortcut; + avoid_perimeters_inner(boundary, current.point, possible_new_next.point, 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; +} + +// Called by AvoidCrossingPerimeters::travel_to() +static size_t avoid_perimeters(const AvoidCrossingPerimeters::Boundary &boundary, + const Point &start, + const Point &end, + Polyline &result_out) +{ + // Travel line is completely or partially inside the bounding box. + std::vector path; + size_t num_intersections = avoid_perimeters_inner(boundary, start, end, path); + if (num_intersections) { + path = simplify_travel_heuristics(boundary, path); + std::reverse(path.begin(), path.end()); + path = simplify_travel_heuristics(boundary, path); + std::reverse(path.begin(), path.end()); + } + + result_out = to_polyline(path); + +#ifdef AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT + { + static int iRun = 0; + export_travel_to_svg(boundaries, Line(start, end), path, {}, debug_out_path("AvoidCrossingPerimeters-final-%d.svg", iRun ++)); + } +#endif /* AVOID_CROSSING_PERIMETERS_DEBUG_OUTPUT */ + + return num_intersections; +} + +// Plan travel, which avoids perimeter crossings by following the boundaries of the layer. +Polyline AvoidCrossingPerimeters::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +{ + // 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. + 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 start = gcodegen.last_pos() + scaled_origin; + Point end = point + scaled_origin; + Polyline result_pl; + size_t travel_intersection_count = 0; + Vec2d startf = start.cast(); + Vec2d endf = end .cast(); + // Trim the travel line by the bounding box. + if (Geometry::liang_barsky_line_clipping(startf, endf, (use_external ? m_external : m_internal).bbox)) { + // Travel line is completely or partially inside the bounding box. + //FIXME initialize m_boundaries / m_boundaries_external on demand? + travel_intersection_count = avoid_perimeters((use_external ? m_external : m_internal), startf.cast(), endf.cast(), + result_pl); + result_pl.points.front() = start; + result_pl.points.back() = end; + } else { + // Travel line is completely outside the bounding box. + result_pl = {start, end}; + travel_intersection_count = 0; + } + + Line travel(start, end); + double max_detour_length scale_(gcodegen.config().avoid_crossing_perimeters_max_detour); + if (max_detour_length > 0 && (result_pl.length() - travel.length()) > max_detour_length) + result_pl = {start, end}; + + if (use_external) { + result_pl.translate(-scaled_origin); + *could_be_wipe_disabled = false; + } else + *could_be_wipe_disabled = !need_wipe(gcodegen, m_grid_lslice, travel, result_pl, travel_intersection_count); + + return result_pl; +} + +// called by AvoidCrossingPerimeters::init_layer()->get_boundary()/get_boundary_external() +static std::pair split_expolygon(const ExPolygons &ex_polygons) +{ + Polygons contours, holes; + contours.reserve(ex_polygons.size()); + holes.reserve(std::accumulate(ex_polygons.begin(), ex_polygons.end(), size_t(0), + [](size_t sum, const ExPolygon &ex_poly) { return sum + ex_poly.holes.size(); })); + for (const ExPolygon &ex_poly : ex_polygons) { + contours.emplace_back(ex_poly.contour); + append(holes, ex_poly.holes); + } + return std::make_pair(std::move(contours), std::move(holes)); +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + size_t polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + polygons_count += layer_region->slices.surfaces.size(); + + ExPolygons boundary; + boundary.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->slices.surfaces) + boundary.emplace_back(surface.expolygon); + + boundary = union_ex(boundary); + ExPolygons perimeter_boundary = offset_ex(boundary, -perimeter_offset); + ExPolygons result_boundary; + if (perimeter_boundary.size() != boundary.size()) { + //FIXME ??? + // If any part of the polygon is missing after shrinking, then for misisng parts are is used the boundary of the slice. + ExPolygons missing_perimeter_boundary = offset_ex(diff_ex(boundary, + offset_ex(perimeter_boundary, perimeter_offset + float(SCALED_EPSILON) / 2.f)), + perimeter_offset + float(SCALED_EPSILON)); + perimeter_boundary = offset_ex(perimeter_boundary, perimeter_offset); + append(perimeter_boundary, std::move(missing_perimeter_boundary)); + // By calling intersection_ex some artifacts arose by previous operations are removed. + result_boundary = intersection_ex(offset_ex(perimeter_boundary, -perimeter_offset), boundary); + } else { + result_boundary = std::move(perimeter_boundary); + } + + auto [contours, holes] = split_expolygon(boundary); + // Add an outer boundary to avoid crossing perimeters from supports + ExPolygons outer_boundary = union_ex( + diff(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing), offset(contours, perimeter_spacing + perimeter_offset))); + result_boundary.insert(result_boundary.end(), outer_boundary.begin(), outer_boundary.end()); + ExPolygons holes_boundary = offset_ex(holes, -perimeter_spacing); + result_boundary.insert(result_boundary.end(), holes_boundary.begin(), holes_boundary.end()); + result_boundary = union_ex(result_boundary); + + // Collect all top layers that will not be crossed. + polygons_count = 0; + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) ++polygons_count; + + if (polygons_count > 0) { + ExPolygons top_layer_polygons; + top_layer_polygons.reserve(polygons_count); + for (const LayerRegion *layer_region : layer.regions()) + for (const Surface &surface : layer_region->fill_surfaces.surfaces) + if (surface.is_top()) top_layer_polygons.emplace_back(surface.expolygon); + + top_layer_polygons = union_ex(top_layer_polygons); + return diff_ex(result_boundary, offset_ex(top_layer_polygons, -perimeter_offset)); + } + + return result_boundary; +} + +// called by AvoidCrossingPerimeters::init_layer() +static ExPolygons get_boundary_external(const Layer &layer) +{ + const float perimeter_spacing = get_perimeter_spacing_external(layer); + const float perimeter_offset = perimeter_spacing / 2.f; + ExPolygons boundary; + // Collect all polygons for all printed objects and their instances, which will be printed at the same time as passed "layer". + for (const PrintObject *object : layer.object()->print()->objects()) { + ExPolygons polygons_per_obj; + //FIXME with different layering, layers on other objects will not be found at this object's print_z. + // Search an overlap of layers? + if (const Layer* l = object->get_layer_at_printz(layer.print_z, EPSILON); l) + for (const LayerRegion *layer_region : l->regions()) + for (const Surface &surface : layer_region->slices.surfaces) + polygons_per_obj.emplace_back(surface.expolygon); + + for (const PrintInstance &instance : object->instances()) { + size_t boundary_idx = boundary.size(); + boundary.insert(boundary.end(), polygons_per_obj.begin(), polygons_per_obj.end()); + for (; boundary_idx < boundary.size(); ++boundary_idx) + boundary[boundary_idx].translate(instance.shift); + } + } + boundary = union_ex(boundary); + auto [contours, holes] = split_expolygon(boundary); + // Polygons in which is possible traveling without crossing perimeters of another object. + // A convex hull allows removing unnecessary detour caused by following the boundary of the object. + ExPolygons result_boundary = + diff_ex(offset(Geometry::convex_hull(contours), 2.f * perimeter_spacing),offset(contours, perimeter_spacing + perimeter_offset)); + // All holes are extended for forcing travel around the outer perimeter of a hole when a hole is crossed. + append(result_boundary, diff_ex(offset(holes, perimeter_spacing), offset(holes, perimeter_offset))); + return union_ex(result_boundary); +} + +void AvoidCrossingPerimeters::init_layer(const Layer &layer) +{ + m_internal.boundaries.clear(); + m_external.boundaries.clear(); + + m_internal.boundaries = to_polygons(get_boundary(layer)); + m_external.boundaries = to_polygons(get_boundary_external(layer)); + + BoundingBox bbox(get_extents(m_internal.boundaries)); + bbox.offset(SCALED_EPSILON); + BoundingBox bbox_external = get_extents(m_external.boundaries); + bbox_external.offset(SCALED_EPSILON); + BoundingBox bbox_slice(get_extents(layer.lslices)); + bbox_slice.offset(SCALED_EPSILON); + + m_internal.bbox = BoundingBoxf(bbox.min.cast(), bbox.max.cast()); + m_external.bbox = BoundingBoxf(bbox_external.min.cast(), bbox_external.max.cast()); + + m_internal.grid.set_bbox(bbox); + //FIX1ME 1mm grid? + m_internal.grid.create(m_internal.boundaries, coord_t(scale_(1.))); + m_external.grid.set_bbox(bbox_external); + //FIX1ME 1mm grid? + m_external.grid.create(m_external.boundaries, coord_t(scale_(1.))); + m_grid_lslice.set_bbox(bbox_slice); + //FIX1ME 1mm grid? + m_grid_lslice.create(layer.lslices, coord_t(scale_(1.))); + + init_boundary_distances(&m_internal); + init_boundary_distances(&m_external); +} +#endif + +} // namespace Slic3r diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp new file mode 100644 index 000000000..03c420a32 --- /dev/null +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -0,0 +1,70 @@ +#ifndef slic3r_AvoidCrossingPerimeters_hpp_ +#define slic3r_AvoidCrossingPerimeters_hpp_ + +#include "../libslic3r.h" +#include "../ExPolygon.hpp" +#include "../EdgeGrid.hpp" + +namespace Slic3r { + +// Forward declarations. +class GCode; +class Layer; +class Point; + +class AvoidCrossingPerimeters +{ +public: + // 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) + { + 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); + + struct Boundary { + // Collection of boundaries used for detection of crossing perimeters for travels + Polygons boundaries; + // Bounding box of boundaries + BoundingBoxf bbox; + // Precomputed distances of all points in boundaries + std::vector> boundaries_params; + // Used for detection of intersection between line and any polygon from boundaries + EdgeGrid::Grid grid; + + void clear() + { + boundaries.clear(); + boundaries_params.clear(); + } + }; + +private: + bool m_use_external_mp { false }; + // just for the next travel move + bool m_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 m_disabled_once { true }; + + // Used for detection of line or polyline is inside of any polygon. + EdgeGrid::Grid m_grid_lslice; + // Store all needed data for travels inside object + Boundary m_internal; + // Store all needed data for travels outside object + Boundary m_external; +}; + +} // namespace Slic3r + +#endif // slic3r_AvoidCrossingPerimeters_hpp_ diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp deleted file mode 100644 index a8ce59086..000000000 --- a/src/libslic3r/MotionPlanner.cpp +++ /dev/null @@ -1,363 +0,0 @@ -#include "BoundingBox.hpp" -#include "MotionPlanner.hpp" -#include "MutablePriorityQueue.hpp" -#include "Utils.hpp" - -#include // for numeric_limits -#include - -#define BOOST_VORONOI_USE_GMP 1 -#include "boost/polygon/voronoi.hpp" -using boost::polygon::voronoi_builder; -using boost::polygon::voronoi_diagram; - -namespace Slic3r { - -MotionPlanner::MotionPlanner(const ExPolygons &islands) : m_initialized(false) -{ - ExPolygons expp; - for (const ExPolygon &island : islands) { - island.simplify(SCALED_EPSILON, &expp); - for (ExPolygon &island : expp) - m_islands.emplace_back(MotionPlannerEnv(island)); - expp.clear(); - } -} - -void MotionPlanner::initialize() -{ - // prevent initialization of empty BoundingBox - if (m_initialized || m_islands.empty()) - return; - - // loop through islands in order to create inner expolygons and collect their contours. - Polygons outer_holes; - for (MotionPlannerEnv &island : m_islands) { - // Generate the internal env boundaries by shrinking the island - // we'll use these inner rings for motion planning (endpoints of the Voronoi-based - // graph, visibility check) in order to avoid moving too close to the boundaries. - island.m_env = ExPolygonCollection(offset_ex(island.m_island, -MP_INNER_MARGIN)); - // Island contours are holes of our external environment. - outer_holes.push_back(island.m_island.contour); - } - - // Generate a box contour around everyting. - Polygons contour = offset(get_extents(outer_holes).polygon(), +MP_OUTER_MARGIN*2); - assert(contour.size() == 1); - // make expolygon for outer environment - ExPolygons outer = diff_ex(contour, outer_holes); - assert(outer.size() == 1); - // If some of the islands are nested, then the 0th contour is the outer contour due to the order of conversion - // from Clipper data structure into the Slic3r expolygons inside diff_ex(). - m_outer = MotionPlannerEnv(outer.front()); - m_outer.m_env = ExPolygonCollection(diff_ex(contour, offset(outer_holes, +MP_OUTER_MARGIN))); - m_graphs.resize(m_islands.size() + 1); - m_initialized = true; -} - -Polyline MotionPlanner::shortest_path(const Point &from, const Point &to) -{ - // If we have an empty configuration space, return a straight move. - if (m_islands.empty()) - return Polyline(from, to); - - // Are both points in the same island? - int island_idx_from = -1; - int island_idx_to = -1; - int island_idx = -1; - for (MotionPlannerEnv &island : m_islands) { - int idx = &island - m_islands.data(); - if (island.island_contains(from)) - island_idx_from = idx; - if (island.island_contains(to)) - island_idx_to = idx; - if (island_idx_from == idx && island_idx_to == idx) { - // Since both points are in the same island, is a direct move possible? - // If so, we avoid generating the visibility environment. - if (island.m_island.contains(Line(from, to))) - return Polyline(from, to); - // Both points are inside a single island, but the straight line crosses the island boundary. - island_idx = idx; - break; - } - } - - // lazy generation of configuration space. - this->initialize(); - - // Get environment. If the from / to points do not share an island, then they cross an open space, - // therefore island_idx == -1 and env will be set to the environment of the empty space. - const MotionPlannerEnv &env = this->get_env(island_idx); - if (env.m_env.expolygons.empty()) { - // if this environment is empty (probably because it's too small), perform straight move - // and avoid running the algorithms on empty dataset - return Polyline(from, to); - } - - // Now check whether points are inside the environment. - Point inner_from = from; - Point inner_to = to; - - if (island_idx == -1) { - // The end points do not share the same island. In that case some of the travel - // will be likely performed inside the empty space. - // TODO: instead of using the nearest_env_point() logic, we should - // create a temporary graph where we connect 'from' and 'to' to the - // nodes which don't require more than one crossing, and let Dijkstra - // figure out the entire path - this should also replace the call to - // find_node() below - if (island_idx_from != -1) - // The start point is inside some island. Find the closest point at the empty space to start from. - inner_from = env.nearest_env_point(from, to); - if (island_idx_to != -1) - // The start point is inside some island. Find the closest point at the empty space to start from. - inner_to = env.nearest_env_point(to, inner_from); - } - - // Perform a path search either in the open space, or in a common island of from/to. - const MotionPlannerGraph &graph = this->init_graph(island_idx); - // If no path exists without crossing perimeters, returns a straight segment. - Polyline polyline = graph.shortest_path(inner_from, inner_to); - polyline.points.insert(polyline.points.begin(), from); - polyline.points.emplace_back(to); - - { - // grow our environment slightly in order for simplify_by_visibility() - // to work best by considering moves on boundaries valid as well - ExPolygonCollection grown_env(offset_ex(env.m_env.expolygons, float(+SCALED_EPSILON))); - - if (island_idx == -1) { - /* If 'from' or 'to' are not inside our env, they were connected using the - nearest_env_point() search which maybe produce ugly paths since it does not - include the endpoint in the Dijkstra search; the simplify_by_visibility() - call below will not work in many cases where the endpoint is not contained in - grown_env (whose contour was arbitrarily constructed with MP_OUTER_MARGIN, - which may not be enough for, say, including a skirt point). So we prune - the extra points manually. */ - if (! grown_env.contains(from)) { - // delete second point while the line connecting first to third crosses the - // boundaries as many times as the current first to second - while (polyline.points.size() > 2 && intersection_ln(Line(from, polyline.points[2]), (Polygons)grown_env).size() == 1) - polyline.points.erase(polyline.points.begin() + 1); - } - if (! grown_env.contains(to)) - while (polyline.points.size() > 2 && intersection_ln(Line(*(polyline.points.end() - 3), to), (Polygons)grown_env).size() == 1) - polyline.points.erase(polyline.points.end() - 2); - } - - // Perform some quick simplification (simplify_by_visibility() would make this - // unnecessary, but this is much faster) - polyline.simplify(MP_INNER_MARGIN/10); - - // remove unnecessary vertices - // Note: this is computationally intensive and does not look very necessary - // now that we prune the endpoints with the logic above, - // so we comment it for now until a good test case arises - //polyline.simplify_by_visibility(grown_env); - - /* - SVG svg("shortest_path.svg"); - svg.draw(grown_env.expolygons); - svg.arrows = false; - for (MotionPlannerGraph::adjacency_list_t::const_iterator it = graph->adjacency_list.begin(); it != graph->adjacency_list.end(); ++it) { - Point a = graph->nodes[it - graph->adjacency_list.begin()]; - for (std::vector::const_iterator n = it->begin(); n != it->end(); ++n) { - Point b = graph->nodes[n->target]; - svg.draw(Line(a, b)); - } - } - svg.arrows = true; - svg.draw(from); - svg.draw(inner_from, "red"); - svg.draw(to); - svg.draw(inner_to, "red"); - svg.draw(polyline, "red"); - svg.Close(); - */ - } - - return polyline; -} - -const MotionPlannerGraph& MotionPlanner::init_graph(int island_idx) -{ - // 0th graph is the graph for m_outer. Other graphs are 1 indexed. - MotionPlannerGraph *graph = m_graphs[island_idx + 1].get(); - if (graph == nullptr) { - // If this graph doesn't exist, initialize it. - m_graphs[island_idx + 1] = make_unique(); - graph = m_graphs[island_idx + 1].get(); - - /* We don't add polygon boundaries as graph edges, because we'd need to connect - them to the Voronoi-generated edges by recognizing coinciding nodes. */ - - typedef voronoi_diagram VD; - VD vd; - // Mapping between Voronoi vertices and graph nodes. - std::map vd_vertices; - // get boundaries as lines - const MotionPlannerEnv &env = this->get_env(island_idx); - Lines lines = env.m_env.lines(); - boost::polygon::construct_voronoi(lines.begin(), lines.end(), &vd); - // traverse the Voronoi diagram and generate graph nodes and edges - for (const VD::edge_type &edge : vd.edges()) { - if (edge.is_infinite()) - continue; - const VD::vertex_type* v0 = edge.vertex0(); - const VD::vertex_type* v1 = edge.vertex1(); - Point p0(v0->x(), v0->y()); - Point p1(v1->x(), v1->y()); - // Insert only Voronoi edges fully contained in the island. - //FIXME This test has a terrible O(n^2) time complexity. - if (env.island_contains_b(p0) && env.island_contains_b(p1)) { - // Find v0 in the graph, allocate a new node if v0 does not exist in the graph yet. - auto i_v0 = vd_vertices.find(v0); - size_t v0_idx; - if (i_v0 == vd_vertices.end()) - vd_vertices[v0] = v0_idx = graph->add_node(p0); - else - v0_idx = i_v0->second; - // Find v1 in the graph, allocate a new node if v0 does not exist in the graph yet. - auto i_v1 = vd_vertices.find(v1); - size_t v1_idx; - if (i_v1 == vd_vertices.end()) - vd_vertices[v1] = v1_idx = graph->add_node(p1); - else - v1_idx = i_v1->second; - // Euclidean distance is used as weight for the graph edge - graph->add_edge(v0_idx, v1_idx, (p1 - p0).cast().norm()); - } - } - } - - return *graph; -} - -// Find a middle point on the path from start_point to end_point with the shortest path. -static inline size_t nearest_waypoint_index(const Point &start_point, const Points &middle_points, const Point &end_point) -{ - size_t idx = size_t(-1); - double dmin = std::numeric_limits::infinity(); - for (const Point &p : middle_points) { - double d = (p - start_point).cast().norm() + (end_point - p).cast().norm(); - if (d < dmin) { - idx = &p - middle_points.data(); - dmin = d; - if (dmin < EPSILON) - break; - } - } - return idx; -} - -Point MotionPlannerEnv::nearest_env_point(const Point &from, const Point &to) const -{ - /* In order to ensure that the move between 'from' and the initial env point does - not violate any of the configuration space boundaries, we limit our search to - the points that satisfy this condition. */ - - /* Assume that this method is never called when 'env' contains 'from'; - so 'from' is either inside a hole or outside all contours */ - - // get the points of the hole containing 'from', if any - Points pp; - for (const ExPolygon &ex : m_env.expolygons) { - for (const Polygon &hole : ex.holes) - if (hole.contains(from)) - pp = hole.points; - if (! pp.empty()) - break; - } - - // If 'from' is not inside a hole, it's outside of all contours, so take all contours' points. - if (pp.empty()) - for (const ExPolygon &ex : m_env.expolygons) - append(pp, ex.contour.points); - - // Find the candidate result and check that it doesn't cross too many boundaries. - while (pp.size() > 1) { - // find the point in pp that is closest to both 'from' and 'to' - size_t result = nearest_waypoint_index(from, pp, to); - // as we assume 'from' is outside env, any node will require at least one crossing - if (intersection_ln(Line(from, pp[result]), m_island).size() > 1) { - // discard result - pp.erase(pp.begin() + result); - } else - return pp[result]; - } - - // if we're here, return last point if any (better than nothing) - // if we have no points at all, then we have an empty environment and we - // make this method behave as a no-op (we shouldn't get here by the way) - return pp.empty() ? from : pp.front(); -} - -// Add a new directed edge to the adjacency graph. -void MotionPlannerGraph::add_edge(size_t from, size_t to, double weight) -{ - // Extend adjacency list until this start node. - if (m_adjacency_list.size() < from + 1) { - // Reserve in powers of two to avoid repeated reallocation. - m_adjacency_list.reserve(std::max(8, next_highest_power_of_2((uint32_t)(from + 1)))); - // Allocate new empty adjacency vectors. - m_adjacency_list.resize(from + 1); - } - m_adjacency_list[from].emplace_back(Neighbor(node_t(to), weight)); -} - -// Dijkstra's shortest path in a weighted graph from node_start to node_end. -// The returned path contains the end points. -// If no path exists from node_start to node_end, a straight segment is returned. -Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) const -{ - // This prevents a crash in case for some reason we got here with an empty adjacency list. - if (this->empty()) - return Polyline(); - - // Dijkstra algorithm, previous node of the current node 'u' in the shortest path towards node_start. - std::vector previous(m_adjacency_list.size(), -1); - std::vector distance(m_adjacency_list.size(), std::numeric_limits::infinity()); - std::vector map_node_to_queue_id(m_adjacency_list.size(), size_t(-1)); - distance[node_start] = 0.; - - auto queue = make_mutable_priority_queue( - [&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; }, - [&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; }); - queue.reserve(m_adjacency_list.size()); - for (size_t i = 0; i < m_adjacency_list.size(); ++ i) - queue.push(node_t(i)); - - while (! queue.empty()) { - // Get the next node with the lowest distance to node_start. - node_t u = node_t(queue.top()); - queue.pop(); - map_node_to_queue_id[u] = size_t(-1); - // Stop searching if we reached our destination. - if (size_t(u) == node_end) - break; - // Visit each edge starting at node u. - for (const Neighbor& neighbor : m_adjacency_list[u]) - if (map_node_to_queue_id[neighbor.target] != size_t(-1)) { - weight_t alt = distance[u] + neighbor.weight; - // If total distance through u is shorter than the previous - // distance (if any) between node_start and neighbor.target, replace it. - if (alt < distance[neighbor.target]) { - distance[neighbor.target] = alt; - previous[neighbor.target] = u; - queue.update(map_node_to_queue_id[neighbor.target]); - } - } - } - - // In case the end point was not reached, previous[node_end] contains -1 - // and a straight line from node_start to node_end is returned. - Polyline polyline; - polyline.points.reserve(m_adjacency_list.size()); - for (node_t vertex = node_t(node_end); vertex != -1; vertex = previous[vertex]) - polyline.points.emplace_back(m_nodes[vertex]); - polyline.points.emplace_back(m_nodes[node_start]); - polyline.reverse(); - return polyline; -} - -} diff --git a/src/libslic3r/MotionPlanner.hpp b/src/libslic3r/MotionPlanner.hpp deleted file mode 100644 index e912f2fb4..000000000 --- a/src/libslic3r/MotionPlanner.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef slic3r_MotionPlanner_hpp_ -#define slic3r_MotionPlanner_hpp_ - -#include "libslic3r.h" -#include "BoundingBox.hpp" -#include "ClipperUtils.hpp" -#include "ExPolygonCollection.hpp" -#include "Polyline.hpp" -#include -#include -#include -#include - -#define MP_INNER_MARGIN scale_(1.0) -#define MP_OUTER_MARGIN scale_(2.0) - -namespace Slic3r { - -class MotionPlanner; - -class MotionPlannerEnv -{ - friend class MotionPlanner; - -public: - MotionPlannerEnv() {}; - MotionPlannerEnv(const ExPolygon &island) : m_island(island), m_island_bbox(get_extents(island)) {}; - Point nearest_env_point(const Point &from, const Point &to) const; - bool island_contains(const Point &pt) const - { return m_island_bbox.contains(pt) && m_island.contains(pt); } - bool island_contains_b(const Point &pt) const - { return m_island_bbox.contains(pt) && m_island.contains_b(pt); } - -private: - ExPolygon m_island; - BoundingBox m_island_bbox; - // Region, where the travel is allowed. - ExPolygonCollection m_env; -}; - -// A 2D directed graph for searching a shortest path using the famous Dijkstra algorithm. -class MotionPlannerGraph -{ -public: - // Add a directed edge into the graph. - size_t add_node(const Point &p) { m_nodes.emplace_back(p); return m_nodes.size() - 1; } - void add_edge(size_t from, size_t to, double weight); - size_t find_closest_node(const Point &point) const { return point.nearest_point_index(m_nodes); } - - bool empty() const { return m_adjacency_list.empty(); } - Polyline shortest_path(size_t from, size_t to) const; - Polyline shortest_path(const Point &from, const Point &to) const - { return this->shortest_path(this->find_closest_node(from), this->find_closest_node(to)); } - -private: - typedef int node_t; - typedef double weight_t; - struct Neighbor { - Neighbor(node_t target, weight_t weight) : target(target), weight(weight) {} - node_t target; - weight_t weight; - }; - Points m_nodes; - std::vector> m_adjacency_list; -}; - -class MotionPlanner -{ -public: - MotionPlanner(const ExPolygons &islands); - ~MotionPlanner() {} - - Polyline shortest_path(const Point &from, const Point &to); - size_t islands_count() const { return m_islands.size(); } - -private: - bool m_initialized; - std::vector m_islands; - MotionPlannerEnv m_outer; - // 0th graph is the graph for m_outer. Other graphs are 1 indexed. - std::vector> m_graphs; - - void initialize(); - const MotionPlannerGraph& init_graph(int island_idx); - const MotionPlannerEnv& get_env(int island_idx) const - { return (island_idx == -1) ? m_outer : m_islands[island_idx]; } -}; - -} - -#endif diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 39b07e7d8..03f7ff59c 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -18,13 +18,6 @@ void MultiPoint::scale(double factor_x, double factor_y) } } -void MultiPoint::translate(double x, double y) -{ - Vector v(x, y); - for (Point &pt : points) - pt += v; -} - void MultiPoint::translate(const Point &v) { for (Point &pt : points) @@ -140,6 +133,17 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const return found; } +bool MultiPoint::intersections(const Line &line, Points *intersections) const +{ + size_t intersections_size = intersections->size(); + for (const Line &polygon_line : this->lines()) { + Point intersection; + if (polygon_line.intersection(line, &intersection)) + intersections->emplace_back(std::move(intersection)); + } + return intersections->size() > intersections_size; +} + std::vector MultiPoint::_douglas_peucker(const std::vector& pts, const double tolerance) { std::vector result_pts; diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index fa6dbabb9..d5bac7249 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -26,7 +26,7 @@ public: MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } void scale(double factor); void scale(double factor_x, double factor_y); - void translate(double x, double y); + void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(const Point &vector); void rotate(double angle) { this->rotate(cos(angle), sin(angle)); } void rotate(double cos_angle, double sin_angle); @@ -79,7 +79,8 @@ public: bool intersection(const Line& line, Point* intersection) const; bool first_intersection(const Line& line, Point* intersection) const; - + bool intersections(const Line &line, Points *intersections) const; + static Points _douglas_peucker(const Points &points, const double tolerance); static Points visivalingam(const Points& pts, const double& tolerance); }; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index cde517aca..3972b20d2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -410,7 +410,7 @@ const std::vector& Preset::print_options() "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", "solid_infill_below_area", "only_retract_when_crossing_perimeters", "infill_first", "ironing", "ironing_type", "ironing_flowrate", "ironing_speed", "ironing_spacing", - "max_print_speed", "max_volumetric_speed", + "max_print_speed", "max_volumetric_speed", "avoid_crossing_perimeters_max_detour", #ifdef HAS_PRESSURE_EQUALIZER "max_volumetric_extrusion_rate_slope_positive", "max_volumetric_extrusion_rate_slope_negative", #endif /* HAS_PRESSURE_EQUALIZER */ diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 3215e376d..1b901341d 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -71,6 +71,7 @@ bool Print::invalidate_state_by_config_options(const std::vector steps_gcode = { "avoid_crossing_perimeters", + "avoid_crossing_perimeters_max_detour", "bed_shape", "bed_temperature", "before_layer_gcode", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 89ae92a9e..fcdb3ab04 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -184,6 +184,16 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("avoid_crossing_perimeters_max_detour", coFloat); + def->label = L("Avoid crossing perimeters - The max detour lenght"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The maximum detour length for avoid crossing perimeters. " + "If the detour is longer than this value, avoid crossing perimeters is not applied for this path."); + def->sidetext = L("mm (zero to disable)"); + def->min = 0; + def->mode = comExpert; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("bed_temperature", coInts); def->label = L("Other layers"); def->tooltip = L("Bed temperature for layers after the first one. " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 9eb79d5fa..f30d2e875 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -828,6 +828,7 @@ class PrintConfig : public MachineEnvelopeConfig, public GCodeConfig public: ConfigOptionBool avoid_crossing_perimeters; + ConfigOptionFloat avoid_crossing_perimeters_max_detour; ConfigOptionPoints bed_shape; ConfigOptionInts bed_temperature; ConfigOptionFloat bridge_acceleration; @@ -902,6 +903,7 @@ protected: this->MachineEnvelopeConfig::initialize(cache, base_ptr); this->GCodeConfig::initialize(cache, base_ptr); OPT_PTR(avoid_crossing_perimeters); + OPT_PTR(avoid_crossing_perimeters_max_detour); OPT_PTR(bed_shape); OPT_PTR(bed_temperature); OPT_PTR(bridge_acceleration); diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 4371cbeee..2ef258a4c 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -184,7 +184,7 @@ inline std::unique_ptr make_unique(Args&&... args) { // Variant of std::lower_bound() with compare predicate, but without the key. // This variant is very useful in case that the T type is large or it does not even have a public constructor. template -ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_thank_key) +ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKeyPredicate lower_than_key) { ForwardIt it; typename std::iterator_traits::difference_type count, step; @@ -194,7 +194,7 @@ ForwardIt lower_bound_by_predicate(ForwardIt first, ForwardIt last, LowerThanKey it = first; step = count / 2; std::advance(it, step); - if (lower_thank_key(*it)) { + if (lower_than_key(*it)) { first = ++it; count -= step + 1; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 185f9aa25..aba26b203 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -307,6 +307,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); + + bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters"); + toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters); } void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 36ad2c02f..289287c51 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -542,7 +542,7 @@ void Tab::decorate() wxColour* colored_label_clr = nullptr; if (opt.first == "bed_shape" || opt.first == "filament_ramming_parameters" || - opt.first == "compatible_prints" || opt.first == "compatible_printers") + opt.first == "compatible_prints" || opt.first == "compatible_printers") colored_label_clr = (m_colored_Label_colors.find(opt.first) == m_colored_Label_colors.end()) ? nullptr : m_colored_Label_colors.at(opt.first); if (!colored_label_clr) { @@ -1424,6 +1424,7 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); optgroup->append_single_option_line("avoid_crossing_perimeters", category_path + "avoid-crossing-perimeters"); + optgroup->append_single_option_line("avoid_crossing_perimeters_max_detour", category_path + "avoid_crossing_perimeters_max_detour"); optgroup->append_single_option_line("thin_walls", category_path + "detect-thin-walls"); optgroup->append_single_option_line("overhangs", category_path + "detect-bridging-perimeters"); diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index 75d236a54..962e2e04d 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -62,7 +62,6 @@ set(XS_XSP_FILES ${XSP_DIR}/Layer.xsp ${XSP_DIR}/Line.xsp ${XSP_DIR}/Model.xsp - ${XSP_DIR}/MotionPlanner.xsp ${XSP_DIR}/PerimeterGenerator.xsp ${XSP_DIR}/PlaceholderParser.xsp ${XSP_DIR}/Point.xsp diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index be539e108..aec6ceb6a 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -32,7 +32,6 @@ REGISTER_CLASS(ModelMaterial, "Model::Material"); REGISTER_CLASS(ModelObject, "Model::Object"); REGISTER_CLASS(ModelVolume, "Model::Volume"); REGISTER_CLASS(ModelInstance, "Model::Instance"); -REGISTER_CLASS(MotionPlanner, "MotionPlanner"); REGISTER_CLASS(BoundingBox, "Geometry::BoundingBox"); REGISTER_CLASS(BoundingBoxf, "Geometry::BoundingBoxf"); REGISTER_CLASS(BoundingBoxf3, "Geometry::BoundingBoxf3"); diff --git a/xs/t/18_motionplanner.t b/xs/t/18_motionplanner.t deleted file mode 100644 index dfcfec67f..000000000 --- a/xs/t/18_motionplanner.t +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/perl - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../../lib"; -} - -use Slic3r::XS; -use Test::More tests => 20; - -my $square = Slic3r::Polygon->new( # ccw - [100, 100], - [200, 100], - [200, 200], - [100, 200], -); -my $hole_in_square = Slic3r::Polygon->new( # cw - [140, 140], - [140, 160], - [160, 160], - [160, 140], -); -$_->scale(1/0.000001) for $square, $hole_in_square; - -my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); - -{ - my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(120, 120); - my $to = Slic3r::Point->new(180,180); - $_->scale(1/0.000001) for $from, $to; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; - ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; - ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; - ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; - ok $expolygon->contains_polyline($path), 'path is fully contained in expolygon'; -} - -{ - my $mp = Slic3r::MotionPlanner->new([ $expolygon ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(80, 100); - my $to = Slic3r::Point->new(220,200); - $_->scale(1/0.000001) for $from, $to; - - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; - ok $path->length > Slic3r::Line->new($from, $to)->length, 'path length is greater than straight line'; - ok $path->first_point->coincides_with($from), 'first path point coincides with initial point'; - ok $path->last_point->coincides_with($to), 'last path point coincides with destination point'; - is scalar(@{ Slic3r::Geometry::Clipper::intersection_pl([$path], [@$expolygon]) }), 0, 'path has no intersection with expolygon'; -} - -{ - my $expolygon2 = $expolygon->clone; - $expolygon2->translate(300/0.000001, 0); - my $mp = Slic3r::MotionPlanner->new([ $expolygon, $expolygon2 ]); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(120, 120); - my $to = Slic3r::Point->new(120 + 300, 120); - $_->scale(1/0.000001) for $from, $to; - ok $expolygon->contains_point($from), 'start point is contained in first expolygon'; - ok $expolygon2->contains_point($to), 'end point is contained in second expolygon'; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; -} - -{ - my $expolygons = [ - Slic3r::ExPolygon->new([[123800962,89330311],[123959159,89699438],[124000004,89898430],[124000012,110116427],[123946510,110343065],[123767391,110701303],[123284087,111000001],[102585791,111000009],[102000004,110414223],[102000004,89585787],[102585790,89000000],[123300022,88999993]]), - Slic3r::ExPolygon->new([[97800954,89330311],[97959151,89699438],[97999996,89898430],[98000004,110116427],[97946502,110343065],[97767383,110701303],[97284079,111000001],[76585783,111000009],[75999996,110414223],[75999996,89585787],[76585782,89000000],[97300014,88999993]]), - ]; - my $mp = Slic3r::MotionPlanner->new($expolygons); - isa_ok $mp, 'Slic3r::MotionPlanner'; - - my $from = Slic3r::Point->new(79120520, 107839491); - my $to = Slic3r::Point->new(104664164, 108335852); - ok $expolygons->[1]->contains_point($from), 'start point is contained in second expolygon'; - ok $expolygons->[0]->contains_point($to), 'end point is contained in first expolygon'; - my $path = $mp->shortest_path($from, $to); - ok $path->is_valid(), 'return path is valid'; -} - -__END__ diff --git a/xs/xsp/MotionPlanner.xsp b/xs/xsp/MotionPlanner.xsp deleted file mode 100644 index d93ed09ea..000000000 --- a/xs/xsp/MotionPlanner.xsp +++ /dev/null @@ -1,15 +0,0 @@ -%module{Slic3r::XS}; - -%{ -#include -#include "libslic3r/MotionPlanner.hpp" -%} - -%name{Slic3r::MotionPlanner} class MotionPlanner { - MotionPlanner(ExPolygons islands); - ~MotionPlanner(); - - int islands_count(); - Clone shortest_path(Point* from, Point* to) - %code%{ RETVAL = THIS->shortest_path(*from, *to); %}; -}; diff --git a/xs/xsp/my.map b/xs/xsp/my.map index 7e51b237c..2ecff6e3f 100644 --- a/xs/xsp/my.map +++ b/xs/xsp/my.map @@ -191,18 +191,6 @@ GCode* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T -//GCodePreviewData* O_OBJECT_SLIC3R -//Ref O_OBJECT_SLIC3R_T -//Clone O_OBJECT_SLIC3R_T - -MotionPlanner* O_OBJECT_SLIC3R -Ref O_OBJECT_SLIC3R_T -Clone O_OBJECT_SLIC3R_T - -// GCodeSender* O_OBJECT_SLIC3R -// Ref O_OBJECT_SLIC3R_T -// Clone O_OBJECT_SLIC3R_T - BridgeDetector* O_OBJECT_SLIC3R Ref O_OBJECT_SLIC3R_T Clone O_OBJECT_SLIC3R_T diff --git a/xs/xsp/typemap.xspt b/xs/xsp/typemap.xspt index 385b50f1a..2d364628e 100644 --- a/xs/xsp/typemap.xspt +++ b/xs/xsp/typemap.xspt @@ -97,12 +97,6 @@ %typemap{PolylineCollection*}; %typemap{Ref}{simple}; %typemap{Clone}{simple}; -%typemap{MotionPlanner*}; -%typemap{Ref}{simple}; -%typemap{Clone}{simple}; -// %typemap{GCodeSender*}; -// %typemap{Ref}{simple}; -// %typemap{Clone}{simple}; %typemap{BridgeDetector*}; %typemap{Ref}{simple}; %typemap{Clone}{simple};