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()); + // } +}