From 633ce8aa216798591def24d5905bcc0ed7a16fca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20Miku=C5=A1?= Date: Wed, 9 Nov 2022 13:33:58 +0100 Subject: [PATCH] Pm jps path finding (#8) New step - estimation of curling on both the model and the support extrusions. Improvements in curled filament estimation algortihm Implementation of Jump Point Search algorithm Use of JPS algorithm to avoid curled extrusions during travel moves in Gcode export --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 19 +- src/libslic3r/GCode.hpp | 2 + src/libslic3r/JumpPointSearch.cpp | 364 +++++++++++++++++++++ src/libslic3r/JumpPointSearch.hpp | 36 ++ src/libslic3r/Layer.hpp | 3 + src/libslic3r/Measure.cpp | 1 + src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 3 + src/libslic3r/Print.hpp | 3 +- src/libslic3r/PrintConfig.cpp | 7 + src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 30 +- src/libslic3r/SupportSpotsGenerator.cpp | 194 +++++++++-- src/libslic3r/SupportSpotsGenerator.hpp | 14 +- src/slic3r/GUI/Tab.cpp | 1 + tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_jump_point_search.cpp | 35 ++ 18 files changed, 687 insertions(+), 31 deletions(-) create mode 100644 src/libslic3r/JumpPointSearch.cpp create mode 100644 src/libslic3r/JumpPointSearch.hpp create mode 100644 tests/libslic3r/test_jump_point_search.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 17ed512de3..53cbd14666 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -160,6 +160,8 @@ set(SLIC3R_SOURCES Geometry/VoronoiOffset.hpp Geometry/VoronoiVisualUtils.hpp Int128.hpp + JumpPointSearch.cpp + JumpPointSearch.hpp KDTreeIndirect.hpp Layer.cpp Layer.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 798f7759dd..c218826066 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7,6 +7,9 @@ #include "GCode/PrintExtents.hpp" #include "GCode/Thumbnails.hpp" #include "GCode/WipeTower.hpp" +#include "Point.hpp" +#include "Polygon.hpp" +#include "PrintConfig.hpp" #include "ShortestPath.hpp" #include "Print.hpp" #include "Thread.hpp" @@ -2347,6 +2350,14 @@ LayerResult GCode::process_layer( } } // for objects + if (this->config().avoid_curled_filament_during_travels) { + m_avoid_curled_filaments.clear(); + for (const LayerToPrint &layer_to_print : layers) { + m_avoid_curled_filaments.add_obstacles(layer_to_print.object_layer, Point(scaled(this->origin()))); + m_avoid_curled_filaments.add_obstacles(layer_to_print.support_layer, Point(scaled(this->origin()))); + } + } + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) { @@ -2405,7 +2416,7 @@ LayerResult GCode::process_layer( for (int print_wipe_extrusions = is_anything_overridden; print_wipe_extrusions>=0; --print_wipe_extrusions) { if (is_anything_overridden && print_wipe_extrusions == 0) gcode+="; PURGING FINISHED\n"; - + for (InstanceToPrint &instance_to_print : instances_to_print) { const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; // To control print speed of the 1st object layer printed over raft interface. @@ -3071,6 +3082,12 @@ std::string GCode::travel_to(const Point &point, ExtrusionRole role, std::string this->origin in order to get G-code coordinates. */ Polyline travel { this->last_pos(), point }; + if (this->config().avoid_curled_filament_during_travels) { + Point scaled_origin = Point(scaled(this->origin())); + travel = m_avoid_curled_filaments.find_path(this->last_pos() + scaled_origin, point + scaled_origin); + travel.translate(-scaled_origin); + } + // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); // check whether wipe could be disabled without causing visible stringing diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 4592402e37..95153585d5 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GCode_hpp_ #define slic3r_GCode_hpp_ +#include "JumpPointSearch.hpp" #include "libslic3r.h" #include "ExPolygon.hpp" #include "GCodeWriter.hpp" @@ -374,6 +375,7 @@ private: OozePrevention m_ooze_prevention; Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; + JPSPathFinder m_avoid_curled_filaments; 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/JumpPointSearch.cpp b/src/libslic3r/JumpPointSearch.cpp new file mode 100644 index 0000000000..59bb1165e0 --- /dev/null +++ b/src/libslic3r/JumpPointSearch.cpp @@ -0,0 +1,364 @@ +#include "JumpPointSearch.hpp" +#include "BoundingBox.hpp" +#include "Point.hpp" +#include "libslic3r/AStar.hpp" +#include "libslic3r/KDTreeIndirect.hpp" +#include "libslic3r/Polygon.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//#define DEBUG_FILES +#ifdef DEBUG_FILES +#include "libslic3r/SVG.hpp" +#endif + +namespace Slic3r { + +template void dda(coord_t x0, coord_t y0, coord_t x1, coord_t y1, const PointFn &fn) +{ + coord_t dx = abs(x1 - x0); + coord_t dy = abs(y1 - y0); + coord_t x = x0; + coord_t y = y0; + coord_t n = 1 + dx + dy; + coord_t x_inc = (x1 > x0) ? 1 : -1; + coord_t y_inc = (y1 > y0) ? 1 : -1; + coord_t error = dx - dy; + dx *= 2; + dy *= 2; + + for (; n > 0; --n) { + fn(x, y); + + if (error > 0) { + x += x_inc; + error -= dy; + } else { + y += y_inc; + error += dx; + } + } +} + +// will draw the line twice, second time with and offset of 1 in the direction of normal +// may call the fn on the same coordiantes multiple times! +template void double_dda_with_offset(coord_t x0, coord_t y0, coord_t x1, coord_t y1, const PointFn &fn) +{ + Vec2d normal = Point{y1 - y0, x1 - x0}.cast().normalized(); + normal.x() = ceil(normal.x()); + normal.y() = ceil(normal.y()); + Point start_offset = Point(x0,y0) + (normal).cast(); + Point end_offset = Point(x1,y1) + (normal).cast(); + + dda(x0, y0, x1, y1, fn); + dda(start_offset.x(), start_offset.y(), end_offset.x(), end_offset.y(), fn); +} + +template class JPSTracer +{ +public: + // Use incoming_dir [0,0] for starting points, so that all directions are checked from that point + struct Node + { + CellPositionType position; + CellPositionType incoming_dir; + }; + + JPSTracer(CellPositionType target, CellQueryFn is_passable) : target(target), is_passable(is_passable) {} + +private: + CellPositionType target; + CellQueryFn is_passable; // should return boolean whether the cell is passable or not + + CellPositionType find_jump_point(CellPositionType start, CellPositionType forward_dir) const + { + CellPositionType next = start + forward_dir; + while (next != target && is_passable(next) && !(is_jump_point(next, forward_dir))) { next = next + forward_dir; } + + if (is_passable(next)) { + return next; + } else { + return start; + } + } + + bool is_jump_point(CellPositionType pos, CellPositionType forward_dir) const + { + if (abs(forward_dir.x()) + abs(forward_dir.y()) == 2) { + // diagonal + CellPositionType horizontal_check_dir = CellPositionType{forward_dir.x(), 0}; + CellPositionType vertical_check_dir = CellPositionType{0, forward_dir.y()}; + + if (!is_passable(pos - horizontal_check_dir) && is_passable(pos + forward_dir - 2 * horizontal_check_dir)) { return true; } + + if (!is_passable(pos - vertical_check_dir) && is_passable(pos + forward_dir - 2 * vertical_check_dir)) { return true; } + + if (find_jump_point(pos, horizontal_check_dir) != pos) { return true; } + + if (find_jump_point(pos, vertical_check_dir) != pos) { return true; } + + return false; + } else { // horizontal or vertical + CellPositionType side_dir = CellPositionType(forward_dir.y(), forward_dir.x()); + + if (!is_passable(pos + side_dir) && is_passable(pos + forward_dir + side_dir)) { return true; } + + if (!is_passable(pos - side_dir) && is_passable(pos + forward_dir - side_dir)) { return true; } + + return false; + } + } + +public: + template void foreach_reachable(const Node &from, Fn &&fn) const + { + const CellPositionType &pos = from.position; + const CellPositionType &forward_dir = from.incoming_dir; + std::vector dirs_to_check{}; + + if (abs(forward_dir.x()) + abs(forward_dir.y()) == 0) { // special case for starting point + dirs_to_check = all_directions; + } else if (abs(forward_dir.x()) + abs(forward_dir.y()) == 2) { + // diagonal + CellPositionType horizontal_check_dir = CellPositionType{forward_dir.x(), 0}; + CellPositionType vertical_check_dir = CellPositionType{0, forward_dir.y()}; + + if (!is_passable(pos - horizontal_check_dir) && is_passable(pos + forward_dir - 2 * horizontal_check_dir)) { + dirs_to_check.push_back(forward_dir - 2 * horizontal_check_dir); + } + + if (!is_passable(pos - vertical_check_dir) && is_passable(pos + forward_dir - 2 * vertical_check_dir)) { + dirs_to_check.push_back(forward_dir - 2 * vertical_check_dir); + } + + dirs_to_check.push_back(horizontal_check_dir); + dirs_to_check.push_back(vertical_check_dir); + dirs_to_check.push_back(forward_dir); + + } else { // horizontal or vertical + CellPositionType side_dir = CellPositionType(forward_dir.y(), forward_dir.x()); + + if (!is_passable(pos + side_dir) && is_passable(pos + forward_dir + side_dir)) { + dirs_to_check.push_back(forward_dir + side_dir); + } + + if (!is_passable(pos - side_dir) && is_passable(pos + forward_dir - side_dir)) { + dirs_to_check.push_back(forward_dir - side_dir); + } + dirs_to_check.push_back(forward_dir); + } + + for (const CellPositionType &dir : dirs_to_check) { + CellPositionType jp = find_jump_point(pos, dir); + if (jp != pos) fn(Node{jp, dir}); + } + } + + float distance(Node a, Node b) const { return (a.position - b.position).template cast().norm(); } + + float goal_heuristic(Node n) const { return n.position == target ? -1.f : (target - n.position).template cast().norm(); } + + size_t unique_id(Node n) const { return (static_cast(uint16_t(n.position.x())) << 16) + static_cast(uint16_t(n.position.y())); } + + const std::vector all_directions{{1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, {-1, -1}, {0, -1}, {1, -1}}; +}; + +void JPSPathFinder::clear() +{ + inpassable.clear(); + obstacle_max = Pixel(std::numeric_limits::min(), std::numeric_limits::min()); + obstacle_min = Pixel(std::numeric_limits::max(), std::numeric_limits::max()); +} + +void JPSPathFinder::add_obstacles(const Lines &obstacles) +{ + auto store_obstacle = [&](coord_t x, coord_t y) { + obstacle_max.x() = std::max(obstacle_max.x(), x); + obstacle_max.y() = std::max(obstacle_max.y(), y); + obstacle_min.x() = std::min(obstacle_min.x(), x); + obstacle_min.y() = std::min(obstacle_min.y(), y); + inpassable.insert(Pixel{x, y}); + }; + + for (const Line &l : obstacles) { + Pixel start = pixelize(l.a); + Pixel end = pixelize(l.b); + double_dda_with_offset(start.x(), start.y(), end.x(), end.y(), store_obstacle); + } +} + +void JPSPathFinder::add_obstacles(const Layer *layer, const Point &global_origin) +{ + if (layer != nullptr) { this->print_z = layer->print_z; } + + auto store_obstacle = [&](coord_t x, coord_t y) { + obstacle_max.x() = std::max(obstacle_max.x(), x); + obstacle_max.y() = std::max(obstacle_max.y(), y); + obstacle_min.x() = std::min(obstacle_min.x(), x); + obstacle_min.y() = std::min(obstacle_min.y(), y); + inpassable.insert(Pixel{x, y}); + }; + Lines obstacles; + for (size_t step = 0; step < 3; step++) { + if (layer != nullptr) { + obstacles.insert(obstacles.end(), layer->malformed_lines.begin(), layer->malformed_lines.end()); + layer = layer->lower_layer; + } else { + break; + } + } + + for (const Line &l : obstacles) { + Pixel start = pixelize(l.a + global_origin); + Pixel end = pixelize(l.b + global_origin); + double_dda_with_offset(start.x(), start.y(), end.x(), end.y(), store_obstacle); + } +#ifdef DEBUG_FILES + ::Slic3r::SVG svg(debug_out_path(("obstacles_jps" + std::to_string(print_z) + "_" + std::to_string(rand() % 1000)).c_str()).c_str(), + get_extents(obstacles)); + svg.draw(obstacles); + svg.Close(); +#endif +} + +Polyline JPSPathFinder::find_path(const Point &p0, const Point &p1) +{ + Pixel start = pixelize(p0); + Pixel end = pixelize(p1); + if (inpassable.empty() || (start - end).cast().norm() < 3.0) { return Polyline{p0, p1}; } + + BoundingBox search_box({start,end,obstacle_max,obstacle_min}); + search_box.max += Pixel(1,1); + search_box.min -= Pixel(1,1); + + + BoundingBox bounding_square(Points{start,end}); + bounding_square.max += Pixel(5,5); + bounding_square.min -= Pixel(5,5); + coord_t bounding_square_size = 2*std::max(bounding_square.size().x(),bounding_square.size().y()); + bounding_square.max.x() += (bounding_square_size - bounding_square.size().x()) / 2; + bounding_square.min.x() -= (bounding_square_size - bounding_square.size().x()) / 2; + bounding_square.max.y() += (bounding_square_size - bounding_square.size().y()) / 2; + bounding_square.min.y() -= (bounding_square_size - bounding_square.size().y()) / 2; + + // Intersection - limit the search box to a square area around the start and end, to fasten the path searching + search_box.max = search_box.max.cwiseMin(bounding_square.max); + search_box.min = search_box.min.cwiseMax(bounding_square.min); + + auto cell_query = [&](Pixel pixel) { + return search_box.contains(pixel) && (pixel == start || pixel == end || inpassable.find(pixel) == inpassable.end()); + }; + + JPSTracer tracer(end, cell_query); + using QNode = astar::QNode>; + + std::unordered_map astar_cache{}; + std::vector out_path; + std::vector out_nodes; + + if (!astar::search_route(tracer, {start, {0, 0}}, std::back_inserter(out_nodes), astar_cache)) { + // path not found - just reconstruct the best path from astar cache. + // Note that astar_cache is NOT empty - at least the starting point should always be there + auto coordiante_func = [&astar_cache](size_t idx, size_t dim) { return float(astar_cache[idx].node.position[dim]); }; + std::vector keys; + keys.reserve(astar_cache.size()); + for (const auto &pair : astar_cache) { keys.push_back(pair.first); } + KDTreeIndirect<2, float, decltype(coordiante_func)> kd_tree(coordiante_func, keys); + size_t closest_qnode = find_closest_point(kd_tree, end.cast()); + + out_path.push_back(end); + while (closest_qnode != astar::Unassigned) { + out_path.push_back(astar_cache[closest_qnode].node.position); + closest_qnode = astar_cache[closest_qnode].parent; + } + } else { + for (const auto& node : out_nodes) { + out_path.push_back(node.position); + } + out_path.push_back(start); + } + +#ifdef DEBUG_FILES + auto scaled_points = [](const Points &ps) { + Points r; + for (const Point &p : ps) { r.push_back(Point::new_scale(p.x(), p.y())); } + return r; + }; + auto scaled_point = [](const Point &p) { return Point::new_scale(p.x(), p.y()); }; + ::Slic3r::SVG svg(debug_out_path(("path_jps" + std::to_string(print_z) + "_" + std::to_string(rand() % 1000)).c_str()).c_str(), + BoundingBox(scaled_point(search_box.min), scaled_point(search_box.max))); + for (const auto &p : inpassable) { svg.draw(scaled_point(p), "black", scale_(0.4)); } + for (const auto &qn : astar_cache) { svg.draw(scaled_point(qn.second.node.position), "blue", scale_(0.3)); } + svg.draw(Polyline(scaled_points(out_path)), "yellow", scale_(0.25)); + svg.draw(scaled_point(end), "purple", scale_(0.4)); + svg.draw(scaled_point(start), "green", scale_(0.4)); +#endif + + std::vector tmp_path; + tmp_path.reserve(out_path.size()); + // Some path found, reverse and remove points that do not change direction + std::reverse(out_path.begin(), out_path.end()); + { + tmp_path.push_back(out_path.front()); // first point + for (size_t i = 1; i < out_path.size() - 1; i++) { + if ((out_path[i] - out_path[i - 1]).cast().normalized() != (out_path[i + 1] - out_path[i]).cast().normalized()) { + tmp_path.push_back(out_path[i]); + } + } + tmp_path.push_back(out_path.back()); // last_point + out_path = tmp_path; + } + +#ifdef DEBUG_FILES + svg.draw(Polyline(scaled_points(out_path)), "orange", scale_(0.20)); +#endif + + tmp_path.clear(); + // remove redundant jump points - there are points that change direction but are not needed - this inefficiency arises from the + // usage of grid search The removal alg tries to find the longest Px Px+k path without obstacles. If Px Px+k+1 is blocked, it will + // insert the Px+k point to result and continue search from Px+k + { + tmp_path.push_back(out_path.front()); // first point + size_t index_of_last_stored_point = 0; + for (size_t i = 1; i < out_path.size(); i++) { + if (i - index_of_last_stored_point < 2) continue; + bool passable = true; + auto store_obstacle = [&](coord_t x, coord_t y) { + if (Pixel(x, y) != start && Pixel(x, y) != end && inpassable.find(Pixel(x, y)) != inpassable.end()) { passable = false; }; + }; + dda(tmp_path.back().x(), tmp_path.back().y(), out_path[i].x(), out_path[i].y(), store_obstacle); + if (!passable) { + tmp_path.push_back(out_path[i - 1]); + index_of_last_stored_point = i - 1; + } + } + tmp_path.push_back(out_path.back()); // last_point + out_path = tmp_path; + } + +#ifdef DEBUG_FILES + svg.draw(Polyline(scaled_points(out_path)), "red", scale_(0.15)); + svg.Close(); +#endif + + // before returing the path, transform it from pixels back to points. + // Also replace the first and last pixel by input points so that result path patches input params exactly. + for (Pixel &p : out_path) { p = unpixelize(p); } + out_path.front() = p0; + out_path.back() = p1; + + return Polyline(out_path); +} + +} // namespace Slic3r diff --git a/src/libslic3r/JumpPointSearch.hpp b/src/libslic3r/JumpPointSearch.hpp new file mode 100644 index 0000000000..b09cc77477 --- /dev/null +++ b/src/libslic3r/JumpPointSearch.hpp @@ -0,0 +1,36 @@ +#ifndef SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ +#define SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ + +#include "BoundingBox.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Point.hpp" +#include "libslic3r/Polyline.hpp" +#include "libslic3r/libslic3r.h" +#include +#include + +namespace Slic3r { + +class JPSPathFinder +{ + using Pixel = Point; + std::unordered_set inpassable; + coordf_t print_z; + Pixel obstacle_min; + Pixel obstacle_max; + + const coord_t resolution = scaled(1.5); + Pixel pixelize(const Point &p) { return p / resolution; } + Point unpixelize(const Pixel &p) { return p * resolution; } + +public: + JPSPathFinder() { clear(); }; + void clear(); + void add_obstacles(const Lines &obstacles); + void add_obstacles(const Layer* layer, const Point& global_origin); + Polyline find_path(const Point &start, const Point &end); +}; + +} // namespace Slic3r + +#endif /* SRC_LIBSLIC3R_JUMPPOINTSEARCH_HPP_ */ diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index e11c740e6f..147012dceb 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -123,6 +123,9 @@ public: coordf_t height; // layer height in unscaled coordinates coordf_t bottom_z() const { return this->print_z - this->height; } + //Lines estimated to be seriously malformed, info from the IssueSearch algorithm. These lines should probably be avoided during fast travels. + Lines malformed_lines; + // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry // (with possibly differing extruder ID and slicing parameters) and merged. // For the first layer, if the Elephant foot compensation is applied, this lslice is uncompensated, therefore diff --git a/src/libslic3r/Measure.cpp b/src/libslic3r/Measure.cpp index 26de9f0a6e..6965357c37 100644 --- a/src/libslic3r/Measure.cpp +++ b/src/libslic3r/Measure.cpp @@ -4,6 +4,7 @@ #include "libslic3r/Geometry/Circle.hpp" #include "libslic3r/SurfaceMesh.hpp" +#include namespace Slic3r { namespace Measure { diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ac065e7448..bd70ee739f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -420,7 +420,7 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) static std::vector s_Preset_print_options { "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "slicing_mode", "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", - "extra_perimeters","extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", + "extra_perimeters", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "avoid_curled_filament_during_travels", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position","staggered_inner_seams", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "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", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 2979b3557b..2fee768289 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -58,6 +58,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n // Cache the plenty of parameters, which influence the G-code generator only, // or they are only notes not influencing the generated G-code. static std::unordered_set steps_gcode = { + "avoid_curled_filament_during_travels", "avoid_crossing_perimeters", "avoid_crossing_perimeters_max_detour", "bed_shape", @@ -829,6 +830,8 @@ void Print::process() obj->generate_support_spots(); for (PrintObject *obj : m_objects) obj->generate_support_material(); + for (PrintObject *obj : m_objects) + obj->estimate_curled_extrusions(); if (this->set_started(psWipeTower)) { m_wipe_tower_data.clear(); m_tool_ordering.clear(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9e21111ce1..ec069996e8 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -62,7 +62,7 @@ enum PrintStep : unsigned int { enum PrintObjectStep : unsigned int { posSlice, posPerimeters, posPrepareInfill, - posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posCount, + posInfill, posIroning, posSupportSpotsSearch, posSupportMaterial, posEstimateCurledExtrusions, posCount, }; // A PrintRegion object represents a group of volumes to print @@ -358,6 +358,7 @@ private: void ironing(); void generate_support_spots(); void generate_support_material(); + void estimate_curled_extrusions(); void slice_volumes(); // Has any support (not counting the raft). diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 73583e192a..fe35161b04 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -399,6 +399,13 @@ void PrintConfigDef::init_fff_params() // Maximum extruder temperature, bumped to 1500 to support printing of glass. const int max_temp = 1500; + def = this->add("avoid_curled_filament_during_travels", coBool); + def->label = L("Avoid curled filament during travels"); + def->tooltip = L("Plan travel moves such that the extruder avoids areas where filament may be curled up. " + "This is mostly happening on steeper rounded overhangs and may cause crash or borken print. " + "This feature slows down both the print and the G-code generation."); + def->mode = comExpert; + def->set_default_value(new ConfigOptionBool(false)); def = this->add("avoid_crossing_perimeters", coBool); def->label = L("Avoid crossing perimeters"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 6808560d9d..3258de005b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -729,6 +729,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( PrintConfig, (MachineEnvelopeConfig, GCodeConfig), + ((ConfigOptionBool, avoid_curled_filament_during_travels)) ((ConfigOptionBool, avoid_crossing_perimeters)) ((ConfigOptionFloatOrPercent, avoid_crossing_perimeters_max_detour)) ((ConfigOptionPoints, bed_shape)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index a53c719050..48f9bec093 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -423,7 +423,8 @@ void PrintObject::generate_support_spots() [](const ModelVolume* mv){return mv->supported_facets.empty();}) ) { SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; - SupportSpotsGenerator::Issues issues = SupportSpotsGenerator::full_search(this, params); + auto [issues, malformations] = SupportSpotsGenerator::full_search(this, params); + auto obj_transform = this->trafo_centered(); for (ModelVolume *model_volume : this->model_object()->volumes) { if (model_volume->is_model_part()) { @@ -477,6 +478,26 @@ void PrintObject::generate_support_material() } } +void PrintObject::estimate_curled_extrusions() +{ + if (this->set_started(posEstimateCurledExtrusions)) { + if (this->print()->config().avoid_curled_filament_during_travels) { + BOOST_LOG_TRIVIAL(debug) << "Estimating areas with curled extrusions - start"; + m_print->set_status(88, L("Estimating curled extrusions")); + + // Estimate curling of support material and add it to the malformaition lines of each layer + float support_flow_width = support_material_flow(this, this->config().layer_height).width(); + SupportSpotsGenerator::Params params{this->print()->m_config.filament_type.values}; + SupportSpotsGenerator::estimate_supports_malformations(this->support_layers(), support_flow_width, params); + SupportSpotsGenerator::estimate_malformations(this->layers(), params); + + m_print->throw_if_canceled(); + BOOST_LOG_TRIVIAL(debug) << "Estimating areas with curled extrusions - end"; + } + this->set_done(posEstimateCurledExtrusions); + } +} + std::pair PrintObject::prepare_adaptive_infill_data() { using namespace FillAdaptive; @@ -785,7 +806,7 @@ bool PrintObject::invalidate_step(PrintObjectStep step) // propagate to dependent steps if (step == posPerimeters) { - invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning }); + invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning, posEstimateCurledExtrusions }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posPrepareInfill) { invalidated |= this->invalidate_steps({ posInfill, posIroning }); @@ -793,11 +814,12 @@ bool PrintObject::invalidate_step(PrintObjectStep step) invalidated |= this->invalidate_steps({ posIroning }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); } else if (step == posSlice) { - invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial }); + invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial, posEstimateCurledExtrusions }); invalidated |= m_print->invalidate_steps({ psSkirtBrim }); m_slicing_params.valid = false; } else if (step == posSupportMaterial) { - invalidated |= m_print->invalidate_steps({ psSkirtBrim }); + invalidated |= m_print->invalidate_steps({ psSkirtBrim, }); + invalidated |= this->invalidate_steps({ posEstimateCurledExtrusions }); m_slicing_params.valid = false; } diff --git a/src/libslic3r/SupportSpotsGenerator.cpp b/src/libslic3r/SupportSpotsGenerator.cpp index 09b2bc5aea..899fe427b7 100644 --- a/src/libslic3r/SupportSpotsGenerator.cpp +++ b/src/libslic3r/SupportSpotsGenerator.cpp @@ -2,14 +2,20 @@ #include "ExPolygon.hpp" #include "ExtrusionEntity.hpp" +#include "ExtrusionEntityCollection.hpp" #include "Line.hpp" +#include "Point.hpp" #include "Polygon.hpp" +#include "libslic3r.h" #include "tbb/parallel_for.h" #include "tbb/blocked_range.h" #include "tbb/blocked_range2d.h" #include "tbb/parallel_reduce.h" #include #include +#include +#include +#include #include #include @@ -20,7 +26,7 @@ #include "Geometry/ConvexHull.hpp" // #define DETAILED_DEBUG_LOGS -// #define DEBUG_FILES +//#define DEBUG_FILES #ifdef DEBUG_FILES #include @@ -333,7 +339,7 @@ std::vector to_short_lines(const ExtrusionEntity *e, float length std::vector lines; lines.reserve(pl.points.size() * 1.5f); lines.emplace_back(unscaled(pl.points[0]).cast(), unscaled(pl.points[0]).cast(), e); - for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { + for (int point_idx = 0; point_idx < int(pl.points.size()) - 1; ++point_idx) { Vec2f start = unscaled(pl.points[point_idx]).cast(); Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); Vec2f v = next - start; // vector from next to current @@ -367,12 +373,6 @@ void check_extrusion_entity_stability(const ExtrusionEntity *entity, const auto to_vec3f = [layer_z](const Vec2f &point) { return Vec3f(point.x(), point.y(), layer_z); }; - float overhang_dist = tan(params.overhang_angle_deg * PI / 180.0f) * layer_region->layer()->height; - float min_malformation_dist = tan(params.malformation_angle_span_deg.first * PI / 180.0f) - * layer_region->layer()->height; - float max_malformation_dist = tan(params.malformation_angle_span_deg.second * PI / 180.0f) - * layer_region->layer()->height; - std::vector lines = to_short_lines(entity, params.bridge_distance); if (lines.empty()) return; @@ -380,6 +380,9 @@ void check_extrusion_entity_stability(const ExtrusionEntity *entity, ExtrusionPropertiesAccumulator malformation_acc { }; bridging_acc.add_distance(params.bridge_distance + 1.0f); const float flow_width = get_flow_width(layer_region, entity->role()); + float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; + float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; + for (size_t line_idx = 0; line_idx < lines.size(); ++line_idx) { ExtrusionLine ¤t_line = lines[line_idx]; @@ -395,9 +398,12 @@ void check_extrusion_entity_stability(const ExtrusionEntity *entity, bridging_acc.add_angle(curr_angle); // malformation in concave angles does not happen malformation_acc.add_angle(std::max(0.0f, curr_angle)); + if (curr_angle < -20.0 * PI / 180.0) { + malformation_acc.reset(); + } auto [dist_from_prev_layer, nearest_line_idx, nearest_point] = prev_layer_lines.signed_distance_from_lines_extra(current_line.b); - if (fabs(dist_from_prev_layer) < overhang_dist) { + if (fabs(dist_from_prev_layer) < flow_width) { bridging_acc.reset(); } else { bridging_acc.add_distance(current_line.len); @@ -416,15 +422,16 @@ void check_extrusion_entity_stability(const ExtrusionEntity *entity, } //malformation - if (fabs(dist_from_prev_layer) < 3.0f * flow_width) { + if (fabs(dist_from_prev_layer) < 2.0f * flow_width) { const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); - current_line.malformation += 0.9 * nearest_line.malformation; + current_line.malformation += 0.85 * nearest_line.malformation; } if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { + float factor = std::abs(dist_from_prev_layer - (max_malformation_dist + min_malformation_dist) * 0.5) / + (max_malformation_dist - min_malformation_dist); malformation_acc.add_distance(current_line.len); - current_line.malformation += layer_region->layer()->height * - (0.5f + 1.5f * (malformation_acc.max_curvature / PI) * - gauss(malformation_acc.distance, 5.0f, 1.0f, 0.2f)); + current_line.malformation += layer_region->layer()->height * factor * (2.0f + 3.0f * (malformation_acc.max_curvature / PI)); + current_line.malformation = std::min(current_line.malformation, float(layer_region->layer()->height * params.max_malformation_factor)); } else { malformation_acc.reset(); } @@ -1023,7 +1030,7 @@ Issues check_global_stability(SupportGridFilter supports_presence_grid, return issues; } -std::tuple> check_extrusions_and_build_graph(const PrintObject *po, +std::tuple> check_extrusions_and_build_graph(const PrintObject *po, const Params ¶ms) { #ifdef DEBUG_FILES FILE *segmentation_f = boost::nowide::fopen(debug_out_path("segmentation.obj").c_str(), "w"); @@ -1031,6 +1038,7 @@ std::tuple> check_extrusions_and_build_graph(c #endif Issues issues { }; + Malformations malformations{}; std::vector islands_graph; std::vector layer_lines; float flow_width = get_flow_width(po->layers()[po->layer_count() - 1]->regions()[0], erExternalPerimeter); @@ -1038,6 +1046,7 @@ std::tuple> check_extrusions_and_build_graph(c // PREPARE BASE LAYER const Layer *layer = po->layers()[0]; + malformations.layers.push_back({}); // no malformations to be expected at first layer for (const LayerRegion *layer_region : layer->regions()) { for (const ExtrusionEntity *ex_entity : layer_region->perimeters.entities) { for (const ExtrusionEntity *perimeter : static_cast(ex_entity)->entities) { @@ -1106,6 +1115,12 @@ std::tuple> check_extrusions_and_build_graph(c layer_lines, params); islands_graph.push_back(std::move(layer_islands)); + Lines malformed_lines{}; + for (const auto &line : layer_lines) { + if (line.malformation > 0.3f) { malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); } + } + malformations.layers.push_back(malformed_lines); + #ifdef DEBUG_FILES for (size_t x = 0; x < size_t(layer_grid.get_pixel_count().x()); ++x) { for (size_t y = 0; y < size_t(layer_grid.get_pixel_count().y()); ++y) { @@ -1122,7 +1137,7 @@ std::tuple> check_extrusions_and_build_graph(c } for (const auto &line : layer_lines) { if (line.malformation > 0.0f) { - Vec3f color = value_to_rgbf(0, 1.0f, line.malformation); + Vec3f color = value_to_rgbf(-EPSILON, layer->height*params.max_malformation_factor, line.malformation); fprintf(malform_f, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], layer->slice_z, color[0], color[1], color[2]); } @@ -1138,7 +1153,7 @@ std::tuple> check_extrusions_and_build_graph(c fclose(malform_f); #endif - return {issues, islands_graph}; + return {issues, malformations, islands_graph}; } #ifdef DEBUG_FILES @@ -1167,8 +1182,8 @@ void debug_export(Issues issues, std::string file_name) { // return {}; // } -Issues full_search(const PrintObject *po, const Params ¶ms) { - auto [local_issues, graph] = check_extrusions_and_build_graph(po, params); +std::tuple full_search(const PrintObject *po, const Params ¶ms) { + auto [local_issues, malformations, graph] = check_extrusions_and_build_graph(po, params); Issues global_issues = check_global_stability( { po, params.min_distance_between_support_points }, graph, params); #ifdef DEBUG_FILES debug_export(local_issues, "local_issues"); @@ -1178,7 +1193,146 @@ Issues full_search(const PrintObject *po, const Params ¶ms) { global_issues.support_points.insert(global_issues.support_points.end(), local_issues.support_points.begin(), local_issues.support_points.end()); - return global_issues; + return {global_issues, malformations}; +} + +struct LayerCurlingEstimator +{ + LD prev_layer_lines = LD({}); + Params params; + std::function flow_width_getter; + + LayerCurlingEstimator(std::function flow_width_getter, const Params ¶ms) + : flow_width_getter(flow_width_getter), params(params) + {} + + void estimate_curling(std::vector &extrusion_lines, Layer *l) + { + ExtrusionPropertiesAccumulator malformation_acc{}; + for (size_t line_idx = 0; line_idx < extrusion_lines.size(); ++line_idx) { + ExtrusionLine ¤t_line = extrusion_lines[line_idx]; + + float flow_width = flow_width_getter(current_line); + + float min_malformation_dist = flow_width - params.malformation_overlap_factor.first * flow_width; + float max_malformation_dist = flow_width - params.malformation_overlap_factor.second * flow_width; + + float curr_angle = 0; + if (line_idx + 1 < extrusion_lines.size()) { + const Vec2f v1 = current_line.b - current_line.a; + const Vec2f v2 = extrusion_lines[line_idx + 1].b - extrusion_lines[line_idx + 1].a; + curr_angle = angle(v1, v2); + } + // malformation in concave angles does not happen + malformation_acc.add_angle(std::max(0.0f, curr_angle)); + if (curr_angle < -20.0 * PI / 180.0) { malformation_acc.reset(); } + + auto [dist_from_prev_layer, nearest_line_idx, nearest_point] = prev_layer_lines.signed_distance_from_lines_extra(current_line.b); + + if (fabs(dist_from_prev_layer) < 2.0f * flow_width) { + const ExtrusionLine &nearest_line = prev_layer_lines.get_line(nearest_line_idx); + current_line.malformation += 0.85 * nearest_line.malformation; + } + if (dist_from_prev_layer > min_malformation_dist && dist_from_prev_layer < max_malformation_dist) { + float factor = std::abs(dist_from_prev_layer - (max_malformation_dist + min_malformation_dist) * 0.5) / + (max_malformation_dist - min_malformation_dist); + malformation_acc.add_distance(current_line.len); + current_line.malformation += l->height * factor * (2.0f + 3.0f * (malformation_acc.max_curvature / PI)); + current_line.malformation = std::min(current_line.malformation, float(l->height * params.max_malformation_factor)); + } else { + malformation_acc.reset(); + } + } + + for (const ExtrusionLine &line : extrusion_lines) { + if (line.malformation > 0.3f) { l->malformed_lines.push_back(Line{Point::new_scale(line.a), Point::new_scale(line.b)}); } + } + prev_layer_lines = LD(extrusion_lines); + } +}; + + +void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms) +{ +#ifdef DEBUG_FILES + FILE *debug_file = boost::nowide::fopen(debug_out_path("supports_malformations.obj").c_str(), "w"); +#endif + auto flow_width_getter = [=](const ExtrusionLine& l) { + return supports_flow_width; + }; + + LayerCurlingEstimator lce{flow_width_getter, params}; + + for (SupportLayer *l : layers) { + std::vector extrusion_lines; + for (const ExtrusionEntity *extrusion : l->support_fills.flatten().entities) { + Polyline pl = extrusion->as_polyline(); + Polygon pol(pl.points); + pol.make_counter_clockwise(); + pl = pol.split_at_first_point(); + for (int point_idx = 0; point_idx < int(pl.points.size() - 1); ++point_idx) { + Vec2f start = unscaled(pl.points[point_idx]).cast(); + Vec2f next = unscaled(pl.points[point_idx + 1]).cast(); + ExtrusionLine line{start, next, extrusion}; + extrusion_lines.push_back(line); + } + } + + lce.estimate_curling(extrusion_lines, l); + +#ifdef DEBUG_FILES + for (const ExtrusionLine &line : extrusion_lines) { + if (line.malformation > 0.3f) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_malformation_factor, line.malformation); + fprintf(debug_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } + } +#endif + } + +#ifdef DEBUG_FILES + fclose(debug_file); +#endif +} + +void estimate_malformations(LayerPtrs &layers, const Params ¶ms) +{ +#ifdef DEBUG_FILES + FILE *debug_file = boost::nowide::fopen(debug_out_path("object_malformations.obj").c_str(), "w"); +#endif + auto flow_width_getter = [](const ExtrusionLine &l) { return 0.0; }; + LayerCurlingEstimator lce{flow_width_getter, params}; + + for (Layer *l : layers) { + if (l->regions().empty()) { + continue; + } + std::unordered_map extrusions_widths; + std::vector extrusion_lines; + for (const LayerRegion *region : l->regions()) { + for (const ExtrusionEntity *extrusion : region->perimeters.flatten().entities) { + auto lines = to_short_lines(extrusion, params.bridge_distance); + extrusion_lines.insert(extrusion_lines.end(), lines.begin(), lines.end()); + extrusions_widths.emplace(extrusion, get_flow_width(region, extrusion->role())); + } + } + lce.flow_width_getter = [&](const ExtrusionLine &l) { return extrusions_widths[l.origin_entity]; }; + + lce.estimate_curling(extrusion_lines, l); + +#ifdef DEBUG_FILES + for (const ExtrusionLine &line : extrusion_lines) { + if (line.malformation > 0.3f) { + Vec3f color = value_to_rgbf(-EPSILON, l->height * params.max_malformation_factor, line.malformation); + fprintf(debug_file, "v %f %f %f %f %f %f\n", line.b[0], line.b[1], l->print_z, color[0], color[1], color[2]); + } + } +#endif + } + +#ifdef DEBUG_FILES + fclose(debug_file); +#endif } } //SupportableIssues End diff --git a/src/libslic3r/SupportSpotsGenerator.hpp b/src/libslic3r/SupportSpotsGenerator.hpp index 61a1bc5385..6cc82f4c26 100644 --- a/src/libslic3r/SupportSpotsGenerator.hpp +++ b/src/libslic3r/SupportSpotsGenerator.hpp @@ -26,8 +26,8 @@ struct Params { // the algorithm should use the following units for all computations: distance [mm], mass [g], time [s], force [g*mm/s^2] const float bridge_distance = 12.0f; //mm const float bridge_distance_decrease_by_curvature_factor = 5.0f; // allowed bridge distance = bridge_distance / (1 + this factor * (curvature / PI) ) - const float overhang_angle_deg = 80.0f; - const std::pair malformation_angle_span_deg = std::pair { 45.0f, 80.0f }; + const std::pair malformation_overlap_factor = std::pair { 0.45, -0.1 }; + const float max_malformation_factor = 10.0f; const float min_distance_between_support_points = 3.0f; //mm const float support_points_interface_radius = 1.5f; // mm @@ -72,11 +72,17 @@ struct Issues { std::vector support_points; }; +struct Malformations { + std::vector layers; //for each layer +}; + // std::vector quick_search(const PrintObject *po, const Params ¶ms); -Issues full_search(const PrintObject *po, const Params ¶ms); +std::tuple full_search(const PrintObject *po, const Params ¶ms); -} +void estimate_supports_malformations(SupportLayerPtrs &layers, float supports_flow_width, const Params ¶ms); +void estimate_malformations(LayerPtrs &layers, const Params ¶ms); +} // namespace SupportSpotsGenerator } #endif /* SRC_LIBSLIC3R_SUPPORTABLEISSUESSEARCH_HPP_ */ diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 00e0dae438..47a06ab2a0 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1438,6 +1438,7 @@ void TabPrint::build() optgroup->append_single_option_line("extra_perimeters", category_path + "extra-perimeters-if-needed"); optgroup->append_single_option_line("extra_perimeters_on_overhangs", category_path + "extra-perimeters-on-overhangs"); optgroup->append_single_option_line("ensure_vertical_shell_thickness", category_path + "ensure-vertical-shell-thickness"); + optgroup->append_single_option_line("avoid_curled_filament_during_travels", category_path + "avoid-curled-filament-during-travels"); 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"); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index ed7d8a92be..c482759fdc 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(${_TEST_NAME}_tests test_timeutils.cpp test_indexed_triangle_set.cpp test_astar.cpp + test_jump_point_search.cpp ../libnest2d/printer_parts.cpp ) diff --git a/tests/libslic3r/test_jump_point_search.cpp b/tests/libslic3r/test_jump_point_search.cpp new file mode 100644 index 0000000000..76d2aac980 --- /dev/null +++ b/tests/libslic3r/test_jump_point_search.cpp @@ -0,0 +1,35 @@ +#include +#include "libslic3r/BoundingBox.hpp" +#include "libslic3r/JumpPointSearch.hpp" + +using namespace Slic3r; + +TEST_CASE("Test jump point search path finding", "[JumpPointSearch]") +{ + Lines obstacles{}; + obstacles.push_back(Line(Point::new_scale(0, 0), Point::new_scale(50, 50))); + obstacles.push_back(Line(Point::new_scale(0, 100), Point::new_scale(50, 50))); + obstacles.push_back(Line(Point::new_scale(0, 0), Point::new_scale(100, 0))); + obstacles.push_back(Line(Point::new_scale(0, 100), Point::new_scale(100, 100))); + obstacles.push_back(Line(Point::new_scale(25, -25), Point::new_scale(25, 125))); + + JPSPathFinder jps; + jps.add_obstacles(obstacles); + + Polyline path = jps.find_path(Point::new_scale(5, 50), Point::new_scale(100, 50)); + path = jps.find_path(Point::new_scale(5, 50), Point::new_scale(150, 50)); + path = jps.find_path(Point::new_scale(5, 50), Point::new_scale(25, 15)); + path = jps.find_path(Point::new_scale(25, 25), Point::new_scale(125, 125)); + + // SECTION("Output is empty when source is also the destination") { + // bool found = astar::search_route(DummyTracer{}, 0, std::back_inserter(out)); + // REQUIRE(out.empty()); + // REQUIRE(found); + // } + + // SECTION("Return false when there is no route to destination") { + // bool found = astar::search_route(DummyTracer{}, 1, std::back_inserter(out)); + // REQUIRE(!found); + // REQUIRE(out.empty()); + // } +}