From 04c2fde671772600b98e094f9714acc5190b9e4c Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 17 Nov 2020 09:33:10 +0100 Subject: [PATCH] Removed the old motion planner. --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/GCode.cpp | 12 +- src/libslic3r/GCode.hpp | 3 +- .../GCode/AvoidCrossingPerimeters.cpp | 169 ++------ .../GCode/AvoidCrossingPerimeters.hpp | 86 ++--- src/libslic3r/MotionPlanner.cpp | 363 ------------------ src/libslic3r/MotionPlanner.hpp | 91 ----- xs/src/perlglue.cpp | 3 - xs/t/18_motionplanner.t | 92 ----- xs/xsp/MotionPlanner.xsp | 15 - xs/xsp/my.map | 12 - xs/xsp/typemap.xspt | 6 - 12 files changed, 67 insertions(+), 787 deletions(-) delete mode 100644 src/libslic3r/MotionPlanner.cpp delete mode 100644 src/libslic3r/MotionPlanner.hpp delete mode 100644 xs/t/18_motionplanner.t delete mode 100644 xs/xsp/MotionPlanner.xsp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 93f947545..4d65de81c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -128,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/GCode.cpp b/src/libslic3r/GCode.cpp index f1c8f5bbf..96eff24e7 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1140,13 +1140,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(); @@ -2054,11 +2047,8 @@ void GCode::process_layer( for (InstanceToPrint &instance_to_print : instances_to_print) { 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)); + if (m_config.avoid_crossing_perimeters) 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. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index d13ace30b..b21f4c97d 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -5,7 +5,6 @@ #include "ExPolygon.hpp" #include "GCodeWriter.hpp" #include "Layer.hpp" -#include "MotionPlanner.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" @@ -299,7 +298,7 @@ private: std::set m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; - AvoidCrossingPerimeters2 m_avoid_crossing_perimeters; + AvoidCrossingPerimeters m_avoid_crossing_perimeters; bool m_enable_loop_clipping; // If enabled, the G-code generator will put following comments at the ends // of the G-code lines: _EXTRUDE_SET_SPEED, _WIPE, _BRIDGE_FAN_START, _BRIDGE_FAN_END diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 4aafa77fb..6a211e05b 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1,7 +1,5 @@ #include "../Layer.hpp" -#include "../MotionPlanner.hpp" #include "../GCode.hpp" -#include "../MotionPlanner.hpp" #include "../EdgeGrid.hpp" #include "../Geometry.hpp" #include "../ShortestPath.hpp" @@ -21,93 +19,6 @@ namespace Slic3r { -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; -} - // Create a rotation matrix for projection on the given vector static Matrix2d rotation_by_direction(const Point &direction) { @@ -274,16 +185,16 @@ static std::pair split_expolygon(const ExPolygons &ex_polygo return std::make_pair(std::move(contours), std::move(holes)); } -static Polyline to_polyline(const std::vector &travel) +static Polyline to_polyline(const std::vector &travel) { Polyline result; result.points.reserve(travel.size()); - for (const AvoidCrossingPerimeters2::TravelPoint &t_point : travel) + for (const AvoidCrossingPerimeters::TravelPoint &t_point : travel) result.append(t_point.point); return result; } -static double travel_length(const std::vector &travel) { +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(); @@ -292,11 +203,11 @@ static double travel_length(const std::vector &intersections, - const std::string &path) +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); @@ -306,21 +217,21 @@ static void export_travel_to_svg(const Polygons svg.draw(original_travel.a, "black"); svg.draw(original_travel.b, "grey"); - for (const AvoidCrossingPerimeters2::Intersection &intersection : intersections) + for (const AvoidCrossingPerimeters::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) +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 */ -ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) +ExPolygons AvoidCrossingPerimeters::get_boundary(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -380,7 +291,7 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary(const Layer &layer) return result_boundary; } -ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) +ExPolygons AvoidCrossingPerimeters::get_boundary_external(const Layer &layer) { const float perimeter_spacing = get_perimeter_spacing_external(layer); const float perimeter_offset = perimeter_spacing / 2.f; @@ -417,11 +328,11 @@ ExPolygons AvoidCrossingPerimeters2::get_boundary_external(const Layer &layer) } // Returns a direction of the shortest path along the polygon boundary -AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direction(const Lines &lines, - const size_t start_idx, - const size_t end_idx, - const Point &intersection_first, - const Point &intersection_last) +AvoidCrossingPerimeters::Direction AvoidCrossingPerimeters::get_shortest_direction(const Lines &lines, + const size_t start_idx, + const size_t end_idx, + const Point &intersection_first, + const Point &intersection_last) { double total_length_forward = (lines[start_idx].b - intersection_first).cast().norm(); double total_length_backward = (lines[start_idx].a - intersection_first).cast().norm(); @@ -447,10 +358,10 @@ AvoidCrossingPerimeters2::Direction AvoidCrossingPerimeters2::get_shortest_direc return (total_length_forward < total_length_backward) ? Direction::Forward : Direction::Backward; } -std::vector AvoidCrossingPerimeters2::simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics) +std::vector AvoidCrossingPerimeters::simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics) { struct Visitor { @@ -521,9 +432,9 @@ std::vector AvoidCrossingPerimeters2::sim return simplified_path; } -std::vector AvoidCrossingPerimeters2::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries) +std::vector AvoidCrossingPerimeters::simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries) { std::vector simplified_path; std::vector intersections; @@ -600,12 +511,12 @@ std::vector AvoidCrossingPerimeters2::sim return simplified_path; } -size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boundaries, - const EdgeGrid::Grid &edge_grid, - const Point &start, - const Point &end, - const bool use_heuristics, - std::vector *result_out) +size_t AvoidCrossingPerimeters::avoid_perimeters(const Polygons &boundaries, + const EdgeGrid::Grid &edge_grid, + const Point &start, + const Point &end, + const bool use_heuristics, + std::vector *result_out) { const Point direction = end - start; Matrix2d transform_to_x_axis = rotation_by_direction(direction); @@ -697,10 +608,10 @@ size_t AvoidCrossingPerimeters2::avoid_perimeters(const Polygons &boun return intersections.size(); } -bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, - const Line & original_travel, - const Polyline &result_travel, - const size_t intersection_count) +bool AvoidCrossingPerimeters::need_wipe(const GCode & gcodegen, + const Line & original_travel, + const Polyline &result_travel, + const size_t intersection_count) { bool z_lift_enabled = gcodegen.config().retract_lift.get_at(gcodegen.writer().extruder()->id()) > 0.; bool wipe_needed = false; @@ -731,7 +642,7 @@ bool AvoidCrossingPerimeters2::need_wipe(const GCode & gcodegen, } // Plan travel, which avoids perimeter crossings by following the boundaries of the layer. -Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) +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. @@ -772,7 +683,7 @@ Polyline AvoidCrossingPerimeters2::travel_to(const GCode &gcodegen, const Point return result_pl; } -void AvoidCrossingPerimeters2::init_layer(const Layer &layer) +void AvoidCrossingPerimeters::init_layer(const Layer &layer) { m_slice.clear(); m_boundaries.clear(); diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp index f1a5324f4..0f99108f9 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.hpp @@ -13,7 +13,6 @@ namespace Slic3r { // Forward declarations. class GCode; class Layer; -class MotionPlanner; class Point; class Print; class PrintObject; @@ -23,44 +22,6 @@ 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) {} - virtual ~AvoidCrossingPerimeters() = default; - - void reset() - { - m_external_mp.reset(); - m_layer_mp.reset(); - } - virtual void init_external_mp(const Print &print); - virtual void init_layer_mp(const ExPolygons &islands) { m_layer_mp = Slic3r::make_unique(islands); } - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point); - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) - { - *could_be_wipe_disabled = true; - return this->travel_to(gcodegen, point); - } - -protected: - // 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 AvoidCrossingPerimeters2 : public AvoidCrossingPerimeters -{ public: struct Intersection { @@ -88,14 +49,14 @@ public: struct AllIntersectionsVisitor { - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, std::vector &intersections) : grid(grid), intersections(intersections) {} - AllIntersectionsVisitor(const EdgeGrid::Grid &grid, - std::vector &intersections, - const Matrix2d &transform_to_x_axis, - const Line &travel_line) + AllIntersectionsVisitor(const EdgeGrid::Grid &grid, + std::vector &intersections, + const Matrix2d &transform_to_x_axis, + const Line &travel_line) : grid(grid), intersections(intersections), transform_to_x_axis(transform_to_x_axis), travel_line(travel_line) {} @@ -125,7 +86,7 @@ public: } const EdgeGrid::Grid &grid; - std::vector &intersections; + std::vector &intersections; Matrix2d transform_to_x_axis; Line travel_line; std::unordered_set, boost::hash>> intersection_set; @@ -141,14 +102,14 @@ private: static Direction get_shortest_direction( const Lines &lines, const size_t start_idx, const size_t end_idx, const Point &intersection_first, const Point &intersection_last); - static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries, - const bool use_heuristics); + static std::vector simplify_travel(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries, + const bool use_heuristics); - static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, - const std::vector &travel, - const Polygons &boundaries); + static std::vector simplify_travel_heuristics(const EdgeGrid::Grid &edge_grid, + const std::vector &travel, + const Polygons &boundaries); static size_t avoid_perimeters(const Polygons &boundaries, const EdgeGrid::Grid &grid, @@ -173,24 +134,27 @@ private: EdgeGrid::Grid m_grid_external; public: - AvoidCrossingPerimeters2() : AvoidCrossingPerimeters() {} + // this flag triggers the use of the external configuration space + bool use_external_mp { false }; + // just for the next travel move + bool use_external_mp_once { false }; + // this flag disables avoid_crossing_perimeters just for the next travel move + // we enable it by default for the first travel move in print + bool disable_once { true }; - virtual ~AvoidCrossingPerimeters2() = default; + AvoidCrossingPerimeters() = default; - // Used for disabling unnecessary calling collect_contours_all_layers - virtual void init_external_mp(const Print &print) override {}; - virtual void init_layer_mp(const ExPolygons &islands) override {}; - - virtual Polyline travel_to(const GCode &gcodegen, const Point &point) override + Polyline travel_to(const GCode& gcodegen, const Point& point) { bool could_be_wipe_disabled; return this->travel_to(gcodegen, point, &could_be_wipe_disabled); } - virtual Polyline travel_to(const GCode &gcodegen, const Point &point, bool *could_be_wipe_disabled) override; + Polyline travel_to(const GCode& gcodegen, const Point& point, bool* could_be_wipe_disabled); void init_layer(const Layer &layer); }; + } // namespace Slic3r -#endif // slic3r_AvoidCrossingPerimeters_hpp_ \ No newline at end of file +#endif // slic3r_AvoidCrossingPerimeters_hpp_ diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp deleted file mode 100644 index ae50df8f4..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; - 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/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 47961c623..9e406828a 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -15,8 +15,6 @@ REGISTER_CLASS(Filler, "Filler"); REGISTER_CLASS(Flow, "Flow"); REGISTER_CLASS(CoolingBuffer, "GCode::CoolingBuffer"); REGISTER_CLASS(GCode, "GCode"); -//REGISTER_CLASS(GCodePreviewData, "GCode::PreviewData"); -// REGISTER_CLASS(GCodeSender, "GCode::Sender"); REGISTER_CLASS(Layer, "Layer"); REGISTER_CLASS(SupportLayer, "Layer::Support"); REGISTER_CLASS(LayerRegion, "Layer::Region"); @@ -35,7 +33,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};