diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index 96c8ac735f..ce6750308a 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -446,7 +446,7 @@ inline void expolygons_rotate(ExPolygons &expolys, double angle) expoly.rotate(angle); } -inline bool expolygons_contain(ExPolygons &expolys, const Point &pt, bool border_result = true) +inline bool expolygons_contain(const ExPolygons &expolys, const Point &pt, bool border_result = true) { for (const ExPolygon &expoly : expolys) if (expoly.contains(pt, border_result)) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 032f04dfef..5f8e5df2b5 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2060,26 +2060,6 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector& return false; } -namespace GCode::Impl { -AABBTreeLines::LinesDistancer get_previous_layer_distancer( - const GCodeGenerator::ObjectsLayerToPrint& objects_to_print, - const ExPolygons& slices -) { - std::vector lines; - for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) { - for (const PrintInstance& instance: object_to_print.object()->instances()) { - for (const ExPolygon& polygon : slices) { - for (const Line& line : polygon.lines()) { - lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift})); - } - } - - } - } - return AABBTreeLines::LinesDistancer{std::move(lines)}; -} -} - std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id) { const Polyline xy_path{ this->gcode_to_point(from.head<2>()), @@ -2089,7 +2069,7 @@ std::string GCodeGenerator::get_layer_change_gcode(const Vec3d& from, const Vec3 using namespace GCode::Impl::Travels; ElevatedTravelParams elevation_params{ - get_elevated_traval_params(xy_path, this->m_config, extruder_id)}; + get_elevated_traval_params(xy_path, this->m_config, extruder_id, this->m_travel_obstacle_tracker)}; const double initial_elevation = from.z(); const double z_change = to.z() - from.z(); @@ -2226,9 +2206,9 @@ LayerResult GCodeGenerator::process_layer( } gcode += this->change_layer(previous_layer_z, print_z); // this will increase m_layer_index m_layer = &layer; - if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) { - this->m_previous_layer_distancer = GCode::Impl::get_previous_layer_distancer(layers, layer.lower_layer->lslices); - } + if (this->line_distancer_is_required(layer_tools.extruders) && this->m_layer != nullptr && this->m_layer->lower_layer != nullptr) + m_travel_obstacle_tracker.init_layer(layer, layers); + m_object_layer_over_raft = false; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; @@ -2566,9 +2546,11 @@ void GCodeGenerator::process_layer_single_object( init_layer_delayed(); m_config.apply(region.config()); } - for (const ExtrusionEntity *ee : *eec) + for (const ExtrusionEntity *ee : *eec) { // Don't reorder, don't flip. - gcode += this->extrude_entity({ *ee, false }, smooth_path_cache, comment_perimeter, -1.); + gcode += this->extrude_entity({*ee, false}, smooth_path_cache, comment_perimeter, -1.); + m_travel_obstacle_tracker.mark_extruded(ee, print_instance.object_layer_to_print_id, print_instance.instance_id); + } } } }; @@ -3383,10 +3365,10 @@ std::string GCodeGenerator::travel_to( GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) : GCode::Impl::Travels::generate_travel_to_extrusion( xy_path, - this->m_config, + m_config, extruder_id, initial_elevation, - this->m_previous_layer_distancer, + m_travel_obstacle_tracker, scaled(m_origin) ) ); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 91dc8e75c9..fc63bf2af2 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -38,8 +38,9 @@ #include "GCode/WipeTowerIntegration.hpp" #include "GCode/SeamPlacer.hpp" #include "GCode/GCodeProcessor.hpp" -#include "EdgeGrid.hpp" #include "GCode/ThumbnailData.hpp" +#include "GCode/Travels.hpp" +#include "EdgeGrid.hpp" #include "tcbspan/span.hpp" #include @@ -405,6 +406,7 @@ private: AvoidCrossingPerimeters m_avoid_crossing_perimeters; JPSPathFinder m_avoid_crossing_curled_overhangs; RetractWhenCrossingPerimeters m_retract_when_crossing_perimeters; + GCode::TravelObstacleTracker m_travel_obstacle_tracker; 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 @@ -424,7 +426,6 @@ private: // In non-sequential mode, all its copies will be printed. const Layer* m_layer; // m_layer is an object layer and it is being printed over raft surface. - std::optional> m_previous_layer_distancer; bool m_object_layer_over_raft; double m_volumetric_speed; // Support for the extrusion role markers. Which marker is active? diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 1146d096f4..e1f9ddcd37 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -1,6 +1,104 @@ #include "Travels.hpp" #include "libslic3r/PrintConfig.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/Print.hpp" + +#include "../GCode.hpp" + +namespace Slic3r::GCode { + +static Lines extrusion_entity_to_lines(const ExtrusionEntity &e_entity) +{ + if (const auto *path = dynamic_cast(&e_entity)) { + return to_lines(path->as_polyline()); + } else if (const auto *multipath = dynamic_cast(&e_entity)) { + return to_lines(multipath->as_polyline()); + } else if (const auto *loop = dynamic_cast(&e_entity)) { + return to_lines(loop->polygon()); + } else { + throw Slic3r::InvalidArgument("Invalid argument supplied to TODO()"); + } + + return {}; +} + +AABBTreeLines::LinesDistancer get_previous_layer_distancer( + const GCodeGenerator::ObjectsLayerToPrint &objects_to_print, const ExPolygons &slices +) { + std::vector lines; + for (const GCodeGenerator::ObjectLayerToPrint &object_to_print : objects_to_print) { + if (const PrintObject *object = object_to_print.object(); object) { + const size_t object_layer_idx = &object_to_print - &objects_to_print.front(); + for (const PrintInstance &instance : object->instances()) { + const size_t instance_idx = &instance - &object->instances().front(); + for (const ExPolygon &polygon : slices) + for (const Line &line : polygon.lines()) + lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx); + } + } + } + + return AABBTreeLines::LinesDistancer{std::move(lines)}; +} + +std::pair, size_t> get_current_layer_distancer(const ObjectsLayerToPrint &objects_to_print) +{ + std::vector lines; + size_t extrusion_entity_cnt = 0; + for (const ObjectLayerToPrint &object_to_print : objects_to_print) { + const size_t object_layer_idx = &object_to_print - &objects_to_print.front(); + if (const Layer *layer = object_to_print.object_layer; layer) { + for (const PrintInstance &instance : layer->object()->instances()) { + const size_t instance_idx = &instance - &layer->object()->instances().front(); + for (const LayerSlice &lslice : layer->lslices_ex) { + for (const LayerIsland &island : lslice.islands) { + const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); + for (uint32_t perimeter_id : island.perimeters) { + assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); + const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + for (const ExtrusionEntity *ee : *eec) { + if (ee->role().is_external_perimeter()) { + for (const Line &line : extrusion_entity_to_lines(*ee)) + lines.emplace_back(unscaled(Point{line.a + instance.shift}), unscaled(Point{line.b + instance.shift}), object_layer_idx, instance_idx, ee); + } + + ++extrusion_entity_cnt; + } + } + } + } + } + } + } + + return {AABBTreeLines::LinesDistancer{std::move(lines)}, extrusion_entity_cnt}; +} + +void TravelObstacleTracker::init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print) +{ + size_t extrusion_entity_cnt = 0; + m_extruded_extrusion.clear(); + + m_objects_to_print = objects_to_print; + m_previous_layer_distancer = get_previous_layer_distancer(m_objects_to_print, layer.lower_layer->lslices); + + std::tie(m_current_layer_distancer, extrusion_entity_cnt) = get_current_layer_distancer(m_objects_to_print); + m_extruded_extrusion.reserve(extrusion_entity_cnt); +} + +void TravelObstacleTracker::mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx) +{ + if (extrusion_entity->role().is_external_perimeter()) + this->m_extruded_extrusion.insert({int(object_layer_idx), int(instance_idx), extrusion_entity}); +} + +bool TravelObstacleTracker::is_extruded(const ObjectOrExtrusionLinef &line) const +{ + return m_extruded_extrusion.find({line.object_layer_idx, line.instance_idx, line.extrusion_entity}) != m_extruded_extrusion.end(); +} + +} // namespace Slic3r::GCode namespace Slic3r::GCode::Impl::Travels { @@ -114,33 +212,86 @@ Points3 generate_elevated_travel( return result; } +struct Intersection +{ + int object_layer_idx = -1; + int instance_idx = -1; + bool is_inside = false; + + bool is_print_instance_equal(const ObjectOrExtrusionLinef &print_istance) { + return this->object_layer_idx == print_istance.object_layer_idx && this->instance_idx == print_istance.instance_idx; + } +}; + double get_first_crossed_line_distance( - tcb::span xy_path, const AABBTreeLines::LinesDistancer &distancer + tcb::span xy_path, + const AABBTreeLines::LinesDistancer &distancer, + const ObjectsLayerToPrint &objects_to_print, + const std::function &predicate, + const bool ignore_starting_object_intersection ) { assert(!xy_path.empty()); if (xy_path.empty()) return std::numeric_limits::max(); + const Point path_first_point = xy_path.front().a; double traversed_distance = 0; + bool skip_intersection = ignore_starting_object_intersection; + Intersection first_intersection; + for (const Line &line : xy_path) { - const Linef unscaled_line = {unscaled(line.a), unscaled(line.b)}; - auto intersections = distancer.intersections_with_line(unscaled_line); - if (!intersections.empty()) { - const Vec2d intersection = intersections.front().first; - const double distance = traversed_distance + (unscaled_line.a - intersection).norm(); - if (distance > EPSILON) { - return distance; - } else if (intersections.size() >= 2) { // Edge case - const Vec2d second_intersection = intersections[1].first; - return traversed_distance + (unscaled_line.a - second_intersection).norm(); - } + const ObjectOrExtrusionLinef unscaled_line = {unscaled(line.a), unscaled(line.b)}; + const std::vector> intersections = distancer.intersections_with_line(unscaled_line); + + if (intersections.empty()) + continue; + + if (!objects_to_print.empty() && ignore_starting_object_intersection && first_intersection.object_layer_idx == -1) { + const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersections.front().second); + const Point shift = objects_to_print[intersection_line.object_layer_idx].layer()->object()->instances()[intersection_line.instance_idx].shift; + const Point shifted_first_point = path_first_point - shift; + const bool contain_first_point = expolygons_contain(objects_to_print[intersection_line.object_layer_idx].layer()->lslices, shifted_first_point); + + first_intersection = {intersection_line.object_layer_idx, intersection_line.instance_idx, contain_first_point}; } + + for (const auto &intersection : intersections) { + const ObjectOrExtrusionLinef &intersection_line = distancer.get_line(intersection.second); + const double distance = traversed_distance + (unscaled_line.a - intersection.first).norm(); + if (distance <= EPSILON) + continue; + + // There is only one external border for each object, so when we cross this border, + // we definitely know that we are outside the object. + if (skip_intersection && first_intersection.is_print_instance_equal(intersection_line) && first_intersection.is_inside) { + skip_intersection = false; + continue; + } + + if (!predicate(intersection_line)) + continue; + + return distance; + } + traversed_distance += (unscaled_line.a - unscaled_line.b).norm(); } return std::numeric_limits::max(); } +double get_obstacle_adjusted_slope_end(const Lines &xy_path, const GCode::TravelObstacleTracker &obstacle_tracker) { + const double previous_layer_crossed_line = get_first_crossed_line_distance( + xy_path, obstacle_tracker.previous_layer_distancer(), obstacle_tracker.objects_to_print() + ); + const double current_layer_crossed_line = get_first_crossed_line_distance( + xy_path, obstacle_tracker.current_layer_distancer(), obstacle_tracker.objects_to_print(), + [&obstacle_tracker](const ObjectOrExtrusionLinef &line) { return obstacle_tracker.is_extruded(line); } + ); + + return std::min(previous_layer_crossed_line, current_layer_crossed_line); +} + struct SmoothingParams { double blend_width{}; @@ -201,7 +352,7 @@ ElevatedTravelParams get_elevated_traval_params( const Polyline& xy_path, const FullPrintConfig &config, const unsigned extruder_id, - const std::optional> &previous_layer_distancer + const GCode::TravelObstacleTracker &obstacle_tracker ) { ElevatedTravelParams elevation_params{}; if (!config.travel_ramping_lift.get_at(extruder_id)) { @@ -221,12 +372,7 @@ ElevatedTravelParams get_elevated_traval_params( elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad); } - const double obstacle_adjusted_slope_end{ - previous_layer_distancer ? - get_first_crossed_line_distance(xy_path.lines(), *previous_layer_distancer) : - std::numeric_limits::max() - }; - + const double obstacle_adjusted_slope_end = get_obstacle_adjusted_slope_end(xy_path.lines(), obstacle_tracker); if (obstacle_adjusted_slope_end < elevation_params.slope_end) elevation_params.slope_end = obstacle_adjusted_slope_end; @@ -263,7 +409,7 @@ Points3 generate_travel_to_extrusion( const FullPrintConfig &config, const unsigned extruder_id, const double initial_elevation, - const std::optional> &previous_layer_distancer, + const GCode::TravelObstacleTracker &obstacle_tracker, const Point &xy_path_coord_origin ) { const double upper_limit = config.retract_lift_below.get_at(extruder_id); @@ -279,7 +425,7 @@ Points3 generate_travel_to_extrusion( } ElevatedTravelParams elevation_params{get_elevated_traval_params( - Polyline{std::move(global_xy_path)}, config, extruder_id, previous_layer_distancer + Polyline{std::move(global_xy_path)}, config, extruder_id, obstacle_tracker )}; const std::vector ensure_points_at_distances = linspace( diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp index c0a9c4442d..7f7c017840 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -11,19 +11,91 @@ #include #include +#include #include #include "libslic3r/AABBTreeLines.hpp" // Forward declarations. namespace Slic3r { +class Layer; class Point; class Linef; class Polyline; class FullPrintConfig; +class ExtrusionEntity; } // namespace Slic3r +namespace Slic3r::GCode { +struct ObjectLayerToPrint; +using ObjectsLayerToPrint = std::vector; + +class ObjectOrExtrusionLinef : public Linef +{ +public: + ObjectOrExtrusionLinef() = delete; + ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b) : Linef(a, b) {} + explicit ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx) + : Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)) {} + ObjectOrExtrusionLinef(const Vec2d &a, const Vec2d &b, size_t object_layer_idx, size_t instance_idx, const ExtrusionEntity *extrusion_entity) + : Linef(a, b), object_layer_idx(int(object_layer_idx)), instance_idx(int(instance_idx)), extrusion_entity(extrusion_entity) {} + + virtual ~ObjectOrExtrusionLinef() = default; + + const int object_layer_idx = -1; + const int instance_idx = -1; + const ExtrusionEntity *extrusion_entity = nullptr; +}; + +struct ExtrudedExtrusionEntity +{ + const int object_layer_idx = -1; + const int instance_idx = -1; + const ExtrusionEntity *extrusion_entity = nullptr; + + bool operator==(const ExtrudedExtrusionEntity &other) const + { + return extrusion_entity == other.extrusion_entity && object_layer_idx == other.object_layer_idx && + instance_idx == other.instance_idx; + } +}; + +struct ExtrudedExtrusionEntityHash +{ + size_t operator()(const ExtrudedExtrusionEntity &eee) const noexcept + { + std::size_t seed = std::hash{}(eee.extrusion_entity); + boost::hash_combine(seed, std::hash{}(eee.object_layer_idx)); + boost::hash_combine(seed, std::hash{}(eee.instance_idx)); + return seed; + } +}; + +class TravelObstacleTracker +{ +public: + void init_layer(const Layer &layer, const ObjectsLayerToPrint &objects_to_print); + + void mark_extruded(const ExtrusionEntity *extrusion_entity, size_t object_layer_idx, size_t instance_idx); + + bool is_extruded(const ObjectOrExtrusionLinef &line) const; + + const AABBTreeLines::LinesDistancer &previous_layer_distancer() const { return m_previous_layer_distancer; } + + const AABBTreeLines::LinesDistancer ¤t_layer_distancer() const { return m_current_layer_distancer; } + + const ObjectsLayerToPrint &objects_to_print() const { return m_objects_to_print; } + +private: + ObjectsLayerToPrint m_objects_to_print; + AABBTreeLines::LinesDistancer m_previous_layer_distancer; + + AABBTreeLines::LinesDistancer m_current_layer_distancer; + std::unordered_set m_extruded_extrusion; +}; +} // namespace Slic3r::GCode + namespace Slic3r::GCode::Impl::Travels { /** * @brief A point on a curve with a distance from start. @@ -106,7 +178,7 @@ ElevatedTravelParams get_elevated_traval_params( const Polyline& xy_path, const FullPrintConfig &config, const unsigned extruder_id, - const std::optional> &previous_layer_distancer = std::nullopt + const GCode::TravelObstacleTracker &obstacle_tracker ); /** @@ -137,13 +209,19 @@ Points3 generate_elevated_travel( * * @param xy_path A path in 2D. * @param distancer AABB Tree over lines. + * @param objects_to_print Objects to print are used to determine in which object xy_path starts. + + * @param ignore_starting_object_intersection When it is true, then the first intersection during traveling from the object out is ignored. * @return Distance to the first intersection if there is one. * * **Ignores intersection with xy_path starting point.** */ double get_first_crossed_line_distance( - tcb::span xy_path, const AABBTreeLines::LinesDistancer &distancer -); + tcb::span xy_path, + const AABBTreeLines::LinesDistancer &distancer, + const ObjectsLayerToPrint &objects_to_print = {}, + const std::function &predicate = [](const ObjectOrExtrusionLinef &) { return true; }, + bool ignore_starting_object_intersection = true); /** * @brief Extract parameters and decide wheather the travel can be elevated. @@ -154,7 +232,7 @@ Points3 generate_travel_to_extrusion( const FullPrintConfig &config, const unsigned extruder_id, const double initial_elevation, - const std::optional> &previous_layer_distancer, + const GCode::TravelObstacleTracker &obstacle_tracker, const Point &xy_path_coord_origin ); } // namespace Slic3r::GCode::Impl::Travels diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index e3b1c81183..fdbac86a33 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -280,6 +280,7 @@ class Linef public: Linef() : a(Vec2d::Zero()), b(Vec2d::Zero()) {} Linef(const Vec2d& _a, const Vec2d& _b) : a(_a), b(_b) {} + virtual ~Linef() = default; Vec2d a; Vec2d b; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index fe15f36aea..47aeeff75f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1966,7 +1966,7 @@ std::vector>> option_keys { "filament_travel_ramping_lift", "filament_travel_max_lift", "filament_travel_slope", - //"filament_travel_lift_before_obstacle", + "filament_travel_lift_before_obstacle", "filament_retract_lift_above", "filament_retract_lift_below" }}, @@ -3283,7 +3283,7 @@ void TabPrinter::build_extruder_pages(size_t n_before_extruders) optgroup->append_single_option_line("travel_ramping_lift", "", extruder_idx); optgroup->append_single_option_line("travel_max_lift", "", extruder_idx); optgroup->append_single_option_line("travel_slope", "", extruder_idx); - //optgroup->append_single_option_line("travel_lift_before_obstacle", "", extruder_idx); + optgroup->append_single_option_line("travel_lift_before_obstacle", "", extruder_idx); line = { L("Only lift"), "" }; line.append_option(optgroup->get_option("retract_lift_above", extruder_idx)); @@ -3557,7 +3557,7 @@ void TabPrinter::toggle_options() load_config(new_conf); } - //toggle_option("travel_lift_before_obstacle", ramping_lift, i); + toggle_option("travel_lift_before_obstacle", ramping_lift, i); toggle_option("retract_length_toolchange", have_multiple_extruders, i); diff --git a/tests/fff_print/test_gcode_travels.cpp b/tests/fff_print/test_gcode_travels.cpp index 9d43c3f06e..895c415c86 100644 --- a/tests/fff_print/test_gcode_travels.cpp +++ b/tests/fff_print/test_gcode_travels.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include using namespace Slic3r; @@ -162,14 +163,14 @@ TEST_CASE("Get first crossed line distance", "[GCode]") { scaled(Vec2f{0, 5}), }.lines()}; - std::vector lines; + std::vector lines; for (const ExPolygon& polygon : {square_with_hole, square_above}) { for (const Line& line : polygon.lines()) { lines.emplace_back(unscale(line.a), unscale(line.b)); } } // Try different cases by skipping lines in the travel. - AABBTreeLines::LinesDistancer distancer{std::move(lines)}; + AABBTreeLines::LinesDistancer distancer{std::move(lines)}; CHECK(get_first_crossed_line_distance(travel, distancer) == Approx(1)); CHECK(get_first_crossed_line_distance(tcb::span{travel}.subspan(1), distancer) == Approx(0.2));