From 384d245be735bc5f2e9419d140d16212d8463c02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 6 Nov 2023 20:25:42 +0100 Subject: [PATCH] Add basic implementation of an obstacle finding algorithm during travels. A line distancer over previous layer is constructed. It is then queried during travel planing. If an obstacle is found in the travel path, the travel algorithm ensures minimal elevation before the obstacle. --- src/libslic3r/GCode.cpp | 62 ++++++++++++++++++++++++++---- src/libslic3r/GCode.hpp | 28 ++++++++++++++ src/libslic3r/GCode/SpiralVase.hpp | 1 + tests/fff_print/test_gcode.cpp | 49 +++++++++++++++++++++++ 4 files changed, 133 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 91bab8e93d..3e0e371886 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -21,6 +21,7 @@ ///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher ///|/ #include "Config.hpp" +#include "Geometry/Circle.hpp" #include "libslic3r.h" #include "GCode/ExtrusionProcessor.hpp" #include "I18N.hpp" @@ -2089,6 +2090,21 @@ namespace Skirt { } // namespace Skirt +bool GCodeGenerator::line_distancer_is_required(const std::vector& extruder_ids) { + for (const unsigned id : extruder_ids) { + const double travel_slope{this->m_config.travel_slope.get_at(id)}; + if ( + this->m_config.travel_lift_before_obstacle.get_at(id) + && this->m_config.travel_max_lift.get_at(id) > 0 + && travel_slope > 0 + && travel_slope < 90 + ) { + return true; + } + } + return false; +} + // In sequential mode, process_layer is called once per each object and its copy, // therefore layers will contain a single entry and single_object_instance_idx will point to the copy of the object. // In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated. @@ -2189,6 +2205,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_expolygons_distancer(m_layer->lower_layer->lslices); + } m_object_layer_over_raft = false; if (! print.config().layer_gcode.value.empty()) { DynamicConfig config; @@ -3382,11 +3401,28 @@ std::optional get_first_crossed_line_distance( return {}; } +std::optional get_obstacle_adjusted_slope_end( + const Lines& xy_path, + const std::optional>& previous_layer_distancer +) { + if (!previous_layer_distancer) { + return std::nullopt; + } + std::optional first_obstacle_distance = get_first_crossed_line_distance( + xy_path, *previous_layer_distancer + ); + if (!first_obstacle_distance) { + return std::nullopt; + } + return *first_obstacle_distance; +} + ElevatedTravelParams get_elevated_traval_params( + const Lines& xy_path, const FullPrintConfig& config, - const unsigned extruder_id -) -{ + const unsigned extruder_id, + const std::optional>& previous_layer_distancer +) { ElevatedTravelParams elevation_params{}; if (!config.travel_ramping_lift.get_at(extruder_id)) { elevation_params.slope_end = 0; @@ -3404,6 +3440,15 @@ ElevatedTravelParams get_elevated_traval_params( elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad); } + std::optional obstacle_adjusted_slope_end{get_obstacle_adjusted_slope_end( + xy_path, + previous_layer_distancer + )}; + + if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) { + elevation_params.slope_end = *obstacle_adjusted_slope_end; + } + return elevation_params; } @@ -3411,7 +3456,8 @@ Points3 generate_travel_to_extrusion( const Polyline& xy_path, const FullPrintConfig& config, const unsigned extruder_id, - const double initial_elevation + const double initial_elevation, + const std::optional>& previous_layer_distancer ) { const double upper_limit = config.retract_lift_below.get_at(extruder_id); const double lower_limit = config.retract_lift_above.get_at(extruder_id); @@ -3423,8 +3469,10 @@ Points3 generate_travel_to_extrusion( } ElevatedTravelParams elevation_params{get_elevated_traval_params( + xy_path.lines(), config, - extruder_id + extruder_id, + previous_layer_distancer )}; const std::vector ensure_points_at_distances{elevation_params.slope_end}; @@ -3600,7 +3648,8 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st xy_path, this->m_config, extruder_id, - initial_elevation + initial_elevation, + this->m_previous_layer_distancer ) ); @@ -3747,7 +3796,6 @@ Point GCodeGenerator::gcode_to_point(const Vec2d &point) const // This function may be called at the very start from toolchange G-code when the extruder is not assigned yet. pt += m_config.extruder_offset.get_at(extruder->id()); return scaled(pt); - } } // namespace Slic3r diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 71c5c1ff13..bf47716f82 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -135,6 +135,31 @@ Points3 generate_elevated_travel( const std::function& elevation ); +/** + * @brief Takes a list o polygons and builds a AABBTree over all unscaled lines. + * + * @param polygons A list of polygons. + * @return AABB Tree over all lines of the polygons. + * + * Unscales the lines in the process! + */ +AABBTreeLines::LinesDistancer get_expolygons_distancer(const ExPolygons& polygons); + +/** + * @brief Given a AABB tree over lines find intersection with xy_path closest to the xy_path start. + * + * @param xy_path A path in 2D. + * @param distancer AABB Tree over lines. + * @return Distance to the first intersection if there is one. + * + * **Ignores intersection with xy_path starting point.** + */ +std::optional get_first_crossed_line_distance( + tcb::span xy_path, + const AABBTreeLines::LinesDistancer& distancer +); + + /** * Generates a regular polygon - all angles are the same (e.g. typical hexagon). * @@ -169,6 +194,7 @@ class Bed { bool contains_within_padding(const Vec2d& point) const; }; } + class GCodeGenerator { public: @@ -405,6 +431,7 @@ private: std::string retract_and_wipe(bool toolchange = false); std::string unretract() { return m_writer.unretract(); } std::string set_extruder(unsigned int extruder_id, double print_z); + bool line_distancer_is_required(const std::vector& extruder_ids); // Cache for custom seam enforcers/blockers for each layer. SeamPlacer m_seam_placer; @@ -473,6 +500,7 @@ 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/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index ac1543f21c..6beaf83453 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -14,6 +14,7 @@ namespace Slic3r { + class SpiralVase { public: SpiralVase(const PrintConfig &config) : m_config(config) diff --git a/tests/fff_print/test_gcode.cpp b/tests/fff_print/test_gcode.cpp index f8d33e1461..7cccfe54f8 100644 --- a/tests/fff_print/test_gcode.cpp +++ b/tests/fff_print/test_gcode.cpp @@ -140,6 +140,55 @@ TEST_CASE("Generate elevated travel", "[GCode]") { }); } +TEST_CASE("Get first crossed line distance", "[GCode]") { + // A 2x2 square at 0, 0, with 1x1 square hole in its center. + ExPolygon square_with_hole{ + { + scaled(Vec2f{-1, -1}), + scaled(Vec2f{1, -1}), + scaled(Vec2f{1, 1}), + scaled(Vec2f{-1, 1}) + }, + { + scaled(Vec2f{-0.5, -0.5}), + scaled(Vec2f{0.5, -0.5}), + scaled(Vec2f{0.5, 0.5}), + scaled(Vec2f{-0.5, 0.5}) + } + }; + // A 2x2 square above the previous square at (0, 3). + ExPolygon square_above{ + { + scaled(Vec2f{-1, 2}), + scaled(Vec2f{1, 2}), + scaled(Vec2f{1, 4}), + scaled(Vec2f{-1, 4}) + } + }; + + // Bottom-up travel intersecting the squares. + Lines travel{Polyline{ + scaled(Vec2f{0, -2}), + scaled(Vec2f{0, -0.7}), + scaled(Vec2f{0, 0}), + scaled(Vec2f{0, 1}), + scaled(Vec2f{0, 1.3}), + scaled(Vec2f{0, 2.4}), + scaled(Vec2f{0, 4.5}), + scaled(Vec2f{0, 5}), + }.lines()}; + + // Try different cases by skipping lines in the travel. + AABBTreeLines::LinesDistancer distancer = get_expolygons_distancer({square_with_hole, square_above}); + 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)); + CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(2), distancer) == Approx(0.5)); + CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(3), distancer) == Approx(1.0)); //Edge case + CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(4), distancer) == Approx(0.7)); + CHECK(*get_first_crossed_line_distance(tcb::span{travel}.subspan(5), distancer) == Approx(1.6)); + CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer)); +} + TEST_CASE("Generate regular polygon", "[GCode]") { const unsigned points_count{32}; const Point centroid{scaled(Vec2d{5, -2})};