From 24e325469743a852704b5a68f135007542e75738 Mon Sep 17 00:00:00 2001 From: SachCZ Date: Wed, 3 Jan 2024 13:22:16 +0100 Subject: [PATCH 01/11] Smooth z-hop curve to avoid unreasonably high jerk setting. * Replace the ramping travel with a smooth ramping travel on marlin 2 under the right circumstances. --- src/libslic3r/GCode/Travels.cpp | 167 ++++++++++++++++++++----- src/libslic3r/GCode/Travels.hpp | 41 ++++++ tests/fff_print/test_gcode_travels.cpp | 19 +++ 3 files changed, 199 insertions(+), 28 deletions(-) diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 17f4344742..c894debbf4 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -2,6 +2,45 @@ namespace Slic3r::GCode::Impl::Travels { +ElevatedTravelFormula::ElevatedTravelFormula(const ElevatedTravelParams ¶ms) + : smoothing_from(params.slope_end - params.blend_width / 2.0) + , smoothing_to(params.slope_end + params.blend_width / 2.0) + , blend_width(params.blend_width) + , lift_height(params.lift_height) + , slope_end(params.slope_end) { + if (smoothing_from < 0) { + smoothing_from = params.slope_end; + smoothing_to = params.slope_end; + } +} + +double parabola(const double x, const double a, const double b, const double c) { + return a * x * x + b * x + c; +} + +double ElevatedTravelFormula::slope_function(double distance_from_start) const { + if (distance_from_start < this->slope_end) { + const double lift_percent = distance_from_start / this->slope_end; + return lift_percent * this->lift_height; + } else { + return this->lift_height; + } +} + +double ElevatedTravelFormula::operator()(const double distance_from_start) const { + if (distance_from_start > this->smoothing_from && distance_from_start < this->smoothing_to) { + const double slope = this->lift_height / this->slope_end; + + // This is a part of a parabola going over a specific + // range and with specific end slopes. + const double a = -slope / 2.0 / this->blend_width; + const double b = slope * this->smoothing_to / this->blend_width; + const double c = this->lift_height + a * boost::math::pow<2>(this->smoothing_to); + return parabola(distance_from_start, a, b, c); + } + return slope_function(distance_from_start); +} + Points3 generate_flat_travel(tcb::span xy_path, const float elevation) { Points3 result; result.reserve(xy_path.size() - 1); @@ -52,26 +91,6 @@ std::vector slice_xy_path( return result; } -struct ElevatedTravelParams -{ - double lift_height{}; - double slope_end{}; -}; - -struct ElevatedTravelFormula -{ - double operator()(double distance_from_start) const { - if (distance_from_start < this->params.slope_end) { - const double lift_percent = distance_from_start / this->params.slope_end; - return lift_percent * this->params.lift_height; - } else { - return this->params.lift_height; - } - } - - ElevatedTravelParams params{}; -}; - Points3 generate_elevated_travel( const tcb::span xy_path, const std::vector &ensure_points_at_distances, @@ -136,8 +155,64 @@ std::optional get_obstacle_adjusted_slope_end( return *first_obstacle_distance; } +struct SmoothingParams +{ + double blend_width{}; + unsigned points_count{1}; +}; + +SmoothingParams get_smoothing_params( + const double lift_height, + const double slope_end, + unsigned extruder_id, + const double travel_length, + const FullPrintConfig &config +) { + if (config.gcode_flavor != gcfMarlinFirmware) + // Smoothing is supported only on Marlin. + return {0, 1}; + + const double slope = lift_height / slope_end; + const double max_machine_z_velocity = config.machine_max_feedrate_z.get_at(extruder_id); + const double max_xy_velocity = + Vec2d{ + config.machine_max_feedrate_x.get_at(extruder_id), + config.machine_max_feedrate_y.get_at(extruder_id)} + .norm(); + + const double xy_acceleration = config.machine_max_acceleration_travel.get_at(extruder_id); + + const double xy_acceleration_time = max_xy_velocity / xy_acceleration; + const double xy_acceleration_distance = 1.0 / 2.0 * xy_acceleration * + boost::math::pow<2>(xy_acceleration_time); + + if (travel_length < xy_acceleration_distance) { + return {0, 1}; + } + + const double max_z_velocity = std::min(max_xy_velocity * slope, max_machine_z_velocity); + const double deceleration_time = max_z_velocity / + config.machine_max_acceleration_z.get_at(extruder_id); + const double deceleration_xy_distance = deceleration_time * max_xy_velocity; + + const double blend_width = slope_end > deceleration_xy_distance / 2.0 ? deceleration_xy_distance : + slope_end * 2.0; + + const unsigned points_count = blend_width > 0 ? + std::ceil(max_z_velocity / config.machine_max_jerk_z.get_at(extruder_id)) : + 1; + + if (blend_width <= 0 // When there is no blend with, there is no need for smoothing. + || points_count > 6 // That would be way to many points. Do not do it at all. + || points_count <= 0 // Always return at least one point. + ) + return {0, 1}; + + return {blend_width, points_count}; +} + ElevatedTravelParams get_elevated_traval_params( - const Lines &xy_path, + const Polyline &xy_path, const FullPrintConfig &config, const unsigned extruder_id, const std::optional> &previous_layer_distancer @@ -146,6 +221,7 @@ ElevatedTravelParams get_elevated_traval_params( if (!config.travel_ramping_lift.get_at(extruder_id)) { elevation_params.slope_end = 0; elevation_params.lift_height = config.retract_lift.get_at(extruder_id); + elevation_params.blend_width = 0; return elevation_params; } elevation_params.lift_height = config.travel_max_lift.get_at(extruder_id); @@ -160,15 +236,45 @@ ElevatedTravelParams get_elevated_traval_params( } std::optional obstacle_adjusted_slope_end{ - get_obstacle_adjusted_slope_end(xy_path, previous_layer_distancer)}; + get_obstacle_adjusted_slope_end(xy_path.lines(), previous_layer_distancer)}; if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) { elevation_params.slope_end = *obstacle_adjusted_slope_end; } + SmoothingParams smoothing_params{get_smoothing_params( + elevation_params.lift_height, elevation_params.slope_end, extruder_id, + unscaled(xy_path.length()), config + )}; + + elevation_params.blend_width = smoothing_params.blend_width; + elevation_params.parabola_points_count = smoothing_params.points_count; return elevation_params; } +/** + * @brief Generate regulary spaced points on 1 axis. Includes both from and to. + * + * If count is 1, the point is in the middle of the range. + */ +std::vector linspace(const double from, const double to, const unsigned count) { + if (count == 0) { + return {}; + } + std::vector result; + result.reserve(count); + if (count == 1) { + result.emplace_back((from + to) / 2.0); + return result; + } + const double step = (to - from) / count; + for (unsigned i = 0; i < count - 1; ++i) { + result.emplace_back(from + i * step); + } + result.emplace_back(to); // Make sure the last value is exactly equal to the value of "to". + return result; +} + Points3 generate_travel_to_extrusion( const Polyline &xy_path, const FullPrintConfig &config, @@ -184,15 +290,20 @@ Points3 generate_travel_to_extrusion( return generate_flat_travel(xy_path.points, initial_elevation); } - Lines global_xy_path; - for (const Line &line : xy_path.lines()) { - global_xy_path.emplace_back(line.a + xy_path_coord_origin, line.b + xy_path_coord_origin); + Points global_xy_path; + for (const Point &point : xy_path.points) { + global_xy_path.emplace_back(point + xy_path_coord_origin); } - ElevatedTravelParams elevation_params{ - get_elevated_traval_params(global_xy_path, config, extruder_id, previous_layer_distancer)}; + ElevatedTravelParams elevation_params{get_elevated_traval_params( + Polyline{std::move(global_xy_path)}, config, extruder_id, previous_layer_distancer + )}; - const std::vector ensure_points_at_distances{elevation_params.slope_end}; + const std::vector ensure_points_at_distances = linspace( + elevation_params.slope_end - elevation_params.blend_width / 2.0, + elevation_params.slope_end + elevation_params.blend_width / 2.0, + elevation_params.parabola_points_count + ); Points3 result{generate_elevated_travel( xy_path.points, ensure_points_at_distances, initial_elevation, diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp index eeb783d0a1..84020bb2cc 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -11,18 +11,59 @@ #include #include +#include + #include "libslic3r/Line.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/AABBTreeLines.hpp" #include "libslic3r/PrintConfig.hpp" namespace Slic3r::GCode::Impl::Travels { +/** + * @brief A point on a curve with a distance from start. + */ struct DistancedPoint { Point point; double distance_from_start; }; +struct ElevatedTravelParams +{ + /** Maximal value of nozzle lift. */ + double lift_height{}; + + /** Distance from travel to the middle of the smoothing parabola. */ + double slope_end{}; + + /** Width of the smoothing parabola */ + double blend_width{}; + + /** How many points should be used to approximate the parabola */ + unsigned parabola_points_count{}; +}; + +/** + * @brief A mathematical formula for a smooth function. + * + * It starts lineary increasing than there is a parabola part and + * at the end it is flat. + */ +struct ElevatedTravelFormula +{ + ElevatedTravelFormula(const ElevatedTravelParams ¶ms); + double operator()(const double distance_from_start) const; + +private: + double slope_function(double distance_from_start) const; + + double smoothing_from; + double smoothing_to; + double blend_width; + double lift_height; + double slope_end; +}; + /** * @brief Takes a path described as a list of points and adds points to it. * diff --git a/tests/fff_print/test_gcode_travels.cpp b/tests/fff_print/test_gcode_travels.cpp index d8bbf4c0e7..a5ec848df5 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 using namespace Slic3r; using namespace Slic3r::GCode::Impl::Travels; @@ -179,3 +180,21 @@ TEST_CASE("Get first crossed line distance", "[GCode]") { CHECK_FALSE(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer)); } + +TEST_CASE("Elevated travel formula", "[GCode]") { + const double lift_height{10}; + const double slope_end{10}; + const double blend_width{10}; + const ElevatedTravelParams params{lift_height, slope_end, blend_width}; + + ElevatedTravelFormula f{params}; + + const double distance = slope_end - blend_width / 2; + const double slope = (f(distance) - f(0)) / distance; + // At the begining it has given slope. + CHECK(slope == lift_height / slope_end); + // At the end it is flat. + CHECK(f(slope_end + blend_width / 2) == f(slope_end + blend_width)); + // Should be smoothed. + CHECK(f(slope_end) < lift_height); +} From 7f397cd7b3291a9d6a8c7d2696c07c14b2fadcb9 Mon Sep 17 00:00:00 2001 From: SachCZ Date: Fri, 5 Jan 2024 12:17:03 +0100 Subject: [PATCH 02/11] Implement ramping layer change using a tag in gcode During layer change, instead of generating the gcode, generate a placeholder tag. Then at the end of layer processing replace this tag with a ramping travel move. This solves the issue, that one does not know the starting point of the current layer where the layer change gcode would be originally generate. The ramping layer changes uses smoothing of the ramping travel. Also it is adjusted in such a way that it increases the ramp angle when the travel is too short, to always reach the next layer. --- src/libslic3r/CMakeLists.txt | 2 - src/libslic3r/GCode.cpp | 216 +++++++++++-------- src/libslic3r/GCode.hpp | 38 ++-- src/libslic3r/GCode/GCodeProcessor.cpp | 1 + src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/libslic3r/GCode/GCodeWriter.cpp | 60 ++++-- src/libslic3r/GCode/GCodeWriter.hpp | 17 +- src/libslic3r/GCode/LayerChanges.cpp | 53 ----- src/libslic3r/GCode/LayerChanges.hpp | 52 ----- src/libslic3r/GCode/Travels.cpp | 31 +-- src/libslic3r/GCode/Travels.hpp | 14 ++ src/libslic3r/GCode/WipeTowerIntegration.cpp | 20 +- tests/fff_print/CMakeLists.txt | 1 - tests/fff_print/test_gcode_layer_changes.cpp | 55 ----- 14 files changed, 241 insertions(+), 320 deletions(-) delete mode 100644 src/libslic3r/GCode/LayerChanges.cpp delete mode 100644 src/libslic3r/GCode/LayerChanges.hpp delete mode 100644 tests/fff_print/test_gcode_layer_changes.cpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 7921fccab1..ce24457cd1 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -193,8 +193,6 @@ set(SLIC3R_SOURCES GCode/AvoidCrossingPerimeters.hpp GCode/Travels.cpp GCode/Travels.hpp - GCode/LayerChanges.cpp - GCode/LayerChanges.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 30df60c786..1a8efc7a35 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -35,7 +35,6 @@ #include "GCode/WipeTower.hpp" #include "GCode/WipeTowerIntegration.hpp" #include "GCode/Travels.hpp" -#include "GCode/LayerChanges.hpp" #include "Point.hpp" #include "Polygon.hpp" #include "PrintConfig.hpp" @@ -2081,6 +2080,50 @@ AABBTreeLines::LinesDistancer get_previous_layer_distancer( } } +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>()), + this->gcode_to_point(to.head<2>()) + }; + + using namespace GCode::Impl::Travels; + + ElevatedTravelParams elevation_params{ + get_elevated_traval_params(xy_path, this->m_config, extruder_id)}; + + const double initial_elevation = from.z(); + const double z_change = to.z() - from.z(); + elevation_params.lift_height = std::max(z_change, elevation_params.lift_height); + + const double path_length = unscaled(xy_path.length()); + const double lift_at_travel_end = + (elevation_params.lift_height / elevation_params.slope_end * path_length); + if (lift_at_travel_end < z_change) { + elevation_params.lift_height = z_change; + elevation_params.slope_end = path_length; + } + + const std::vector ensure_points_at_distances = linspace( + elevation_params.slope_end - elevation_params.blend_width / 2.0, + elevation_params.slope_end + elevation_params.blend_width / 2.0, + elevation_params.parabola_points_count + ); + + Points3 travel{generate_elevated_travel( + xy_path.points, ensure_points_at_distances, initial_elevation, + ElevatedTravelFormula{elevation_params} + )}; + + std::string travel_gcode; + Vec3d previous_point{this->point_to_gcode(travel.front())}; + for (const Vec3crd& point : tcb::span{travel}.subspan(1)) { + const Vec3d gcode_point{this->point_to_gcode(point)}; + travel_gcode += this->m_writer.get_travel_to_xyz_gcode(previous_point, gcode_point, "layer change"); + previous_point = gcode_point; + } + return travel_gcode; +} + // 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. @@ -2150,6 +2193,7 @@ LayerResult GCodeGenerator::process_layer( m_enable_loop_clipping = !enable; } + std::string gcode; assert(is_decimal_separator_point()); // for the sprintfs @@ -2168,6 +2212,7 @@ LayerResult GCodeGenerator::process_layer( m_last_layer_z = static_cast(print_z); m_max_layer_z = std::max(m_max_layer_z, m_last_layer_z); m_last_height = height; + m_current_layer_first_position = std::nullopt; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (! print.config().before_layer_gcode.value.empty()) { @@ -2179,7 +2224,7 @@ LayerResult GCodeGenerator::process_layer( print.config().before_layer_gcode.value, m_writer.extruder()->id(), &config) + "\n"; } - gcode += this->change_layer(previous_layer_z, print_z, result.spiral_vase_enable); // this will increase m_layer_index + 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); @@ -2305,6 +2350,33 @@ LayerResult GCodeGenerator::process_layer( is_anything_overridden, false /* print_wipe_extrusions */); } + + // During layer change the starting position of next layer is now known. + // The solution is thus to emplace a temporary tag to the gcode, cache the postion and + // replace the tag later. The tag is Layer_Change_Travel, the cached position is + // m_current_layer_first_position and it is replaced here. + const std::string tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); + std::string layer_change_gcode; + const bool do_ramping_layer_change = ( + m_previous_layer_last_position + && m_current_layer_first_position + && m_layer_change_extruder_id + && !result.spiral_vase_enable + && print_z > previous_layer_z + && EXTRUDER_CONFIG(travel_ramping_lift) + && EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90 + ); + if (do_ramping_layer_change) { + layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id); + } else { + if (!m_current_layer_first_position) { + throw std::runtime_error("Destination is required for layer change!"); + } + layer_change_gcode = this->writer().get_travel_to_z_gcode(m_current_layer_first_position->z(), "simple layer change"); + } + + boost::algorithm::replace_first(gcode, tag, layer_change_gcode); + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2604,63 +2676,10 @@ std::string GCodeGenerator::preamble() return gcode; } - -std::optional GCodeGenerator::get_helical_layer_change_gcode( - const coordf_t previous_layer_z, - const coordf_t print_z, - const std::string& comment -) { - - if (!this->last_pos_defined()) { - return std::nullopt; - } - - const double circle_radius{2}; - const unsigned n_gon_points_count{16}; - - const Point n_gon_start_point{this->last_pos()}; - - GCode::Impl::LayerChanges::Bed bed{ - this->m_config.bed_shape.values, - circle_radius * 2 - }; - if (!bed.contains_within_padding(this->point_to_gcode(n_gon_start_point))) { - return std::nullopt; - } - - const Vec2crd n_gon_vector{scaled(Vec2d{ - (bed.centroid - this->point_to_gcode(n_gon_start_point)).normalized() * circle_radius - })}; - const Point n_gon_centeroid{n_gon_start_point + n_gon_vector}; - - const Polygon n_gon{GCode::Impl::LayerChanges::generate_regular_polygon( - n_gon_centeroid, - n_gon_start_point, - n_gon_points_count - )}; - - const double n_gon_circumference = unscaled(n_gon.length()); - - const double z_change{print_z - previous_layer_z}; - Points3 helix{GCode::Impl::Travels::generate_elevated_travel( - n_gon.points, - {}, - previous_layer_z, - [&](const double distance){ - return distance / n_gon_circumference * z_change; - } - )}; - - helix.emplace_back(to_3d(this->last_pos(), scaled(print_z))); - - return this->generate_travel_gcode(helix, comment); -} - // called by GCodeGenerator::process_layer() std::string GCodeGenerator::change_layer( coordf_t previous_layer_z, - coordf_t print_z, - const bool spiral_vase_enabled + coordf_t print_z ) { std::string gcode; if (m_layer_count > 0) @@ -2670,31 +2689,16 @@ std::string GCodeGenerator::change_layer( if (EXTRUDER_CONFIG(retract_layer_change)) gcode += this->retract_and_wipe(); - const std::string comment{"move to next layer (" + std::to_string(m_layer_index) + ")"}; + Vec3d new_position = this->writer().get_position(); + new_position.z() = print_z; + this->writer().update_position(new_position); - bool do_helical_layer_change{ - !spiral_vase_enabled - && print_z > previous_layer_z - && EXTRUDER_CONFIG(retract_layer_change) - && EXTRUDER_CONFIG(retract_length) > 0 - && EXTRUDER_CONFIG(travel_ramping_lift) - && EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90 - }; + m_previous_layer_last_position = this->m_last_pos_defined ? + std::optional{to_3d(this->point_to_gcode(this->last_pos()), previous_layer_z)} : + std::nullopt; - const std::optional helix_gcode{ - do_helical_layer_change ? - this->get_helical_layer_change_gcode( - m_config.z_offset.value + previous_layer_z, - m_config.z_offset.value + print_z, - comment - ) : - std::nullopt - }; - gcode += ( - helix_gcode ? - *helix_gcode : - m_writer.travel_to_z(m_config.z_offset.value + print_z, comment) - ); + gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); + this->m_layer_change_extruder_id = m_writer.extruder()->id(); // forget last wiping path as wiping after raising Z is pointless m_wipe.reset_path(); @@ -2963,19 +2967,31 @@ std::string GCodeGenerator::_extrude( std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; - // go to first point of extrusion path - if (!m_last_pos_defined) { - const double z = this->m_last_layer_z + this->m_config.z_offset.value; - const std::string comment{"move to print after unknown position"}; - gcode += this->retract_and_wipe(); - gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); - gcode += this->m_writer.get_travel_to_z_gcode(z, comment); - } else if ( m_last_pos != path.front().point) { - std::string comment = "move to first "; - comment += description; - comment += description_bridge; - comment += " point"; - gcode += this->travel_to(path.front().point, path_attr.role, comment); + if (!m_current_layer_first_position) { + // Make the first travel just one G1. + const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z + this->m_config.z_offset.value)); + const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); + this->set_last_pos(path.front().point); + this->writer().update_position(gcode_point); + gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point"); + gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point"); + m_current_layer_first_position = gcode_point; + } else { + // go to first point of extrusion path + if (!m_last_pos_defined) { + const double z = this->m_last_layer_z + this->m_config.z_offset.value; + const std::string comment{"move to print after unknown position"}; + gcode += this->retract_and_wipe(); + gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); + gcode += this->m_writer.get_travel_to_z_gcode(z, comment); + } else if ( m_last_pos != path.front().point) { + std::string comment = "move to first "; + comment += description; + comment += description_bridge; + comment += " point"; + const std::string travel_gcode{this->travel_to(path.front().point, path_attr.role, comment)}; + gcode += travel_gcode; + } } // compensate retraction @@ -3217,9 +3233,13 @@ std::string GCodeGenerator::generate_travel_gcode( // use G1 because we rely on paths being straight (G0 may make round paths) gcode += this->m_writer.set_travel_acceleration(acceleration); - for (const Vec3crd& point : travel) { - gcode += this->m_writer.travel_to_xyz(to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())), comment); + Vec3d previous_point{this->point_to_gcode(travel.front())}; + for (const Vec3crd& point : tcb::span{travel}.subspan(1)) { + const Vec3d gcode_point{this->point_to_gcode(point)}; + + gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment); this->set_last_pos(point.head<2>()); + previous_point = gcode_point; } if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) { @@ -3351,6 +3371,14 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st const double retract_length = this->m_config.retract_length.get_at(extruder_id); bool can_be_flat{!needs_retraction || retract_length == 0}; const double initial_elevation = this->m_last_layer_z + this->m_config.z_offset.value; + + const double upper_limit = this->m_config.retract_lift_below.get_at(extruder_id); + const double lower_limit = this->m_config.retract_lift_above.get_at(extruder_id); + if ((lower_limit > 0 && initial_elevation < lower_limit) || + (upper_limit > 0 && initial_elevation > upper_limit)) { + can_be_flat = true; + } + const Points3 travel = ( can_be_flat ? GCode::Impl::Travels::generate_flat_travel(xy_path.points, initial_elevation) : diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 8890ec6125..92586e8d16 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -127,11 +127,25 @@ public: const Point& last_pos() const { return m_last_pos; } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. template - Vec2d point_to_gcode(const Eigen::MatrixBase &point) const { - static_assert(Derived::IsVectorAtCompileTime && int(Derived::SizeAtCompileTime) == 2, "GCodeGenerator::point_to_gcode(): first parameter is not a 2D vector"); - return Vec2d(unscaled(point.x()), unscaled(point.y())) + m_origin - - m_config.extruder_offset.get_at(m_writer.extruder()->id()); + Eigen::Matrix point_to_gcode(const Eigen::MatrixBase &point) const { + static_assert( + Derived::IsVectorAtCompileTime, + "GCodeGenerator::point_to_gcode(): first parameter is not a vector" + ); + static_assert( + int(Derived::SizeAtCompileTime) == 2 || int(Derived::SizeAtCompileTime) == 3, + "GCodeGenerator::point_to_gcode(): first parameter is not a 2D or 3D vector" + ); + + if constexpr (Derived::SizeAtCompileTime == 2) { + return Vec2d(unscaled(point.x()), unscaled(point.y())) + m_origin + - m_config.extruder_offset.get_at(m_writer.extruder()->id()); + } else { + const Vec2d gcode_point_xy{this->point_to_gcode(point.template head<2>())}; + return to_3d(gcode_point_xy, unscaled(point.z())); + } } + // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution. template Vec2d point_to_gcode_quantized(const Eigen::MatrixBase &point) const { @@ -216,6 +230,9 @@ private: static ObjectsLayerToPrint collect_layers_to_print(const PrintObject &object); static std::vector> collect_layers_to_print(const Print &print); + /** @brief Generates ramping travel gcode for layer change. */ + std::string get_layer_change_gcode(const Vec3d& from, const Vec3d& to, const unsigned extruder_id); + LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. @@ -253,15 +270,9 @@ private: bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); - std::optional get_helical_layer_change_gcode( - const coordf_t previous_layer_z, - const coordf_t print_z, - const std::string& comment - ); std::string change_layer( coordf_t previous_layer_z, - coordf_t print_z, - const bool spiral_vase_enabled + coordf_t print_z ); std::string extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); std::string extrude_loop(const ExtrusionLoop &loop, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); @@ -412,7 +423,10 @@ private: Point m_last_pos; bool m_last_pos_defined; - + std::optional m_previous_layer_last_position; + // This needs to be populated during the layer processing! + std::optional m_current_layer_first_position; + std::optional m_layer_change_extruder_id; std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; std::unique_ptr m_find_replace; diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 87e319b3b9..e3a576e520 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -57,6 +57,7 @@ const std::vector GCodeProcessor::Reserved_Tags = { "HEIGHT:", "WIDTH:", "LAYER_CHANGE", + "LAYER_CHANGE_TRAVEL", "COLOR_CHANGE", "PAUSE_PRINT", "CUSTOM_GCODE", diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index a055eaa347..f5658fcec8 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -192,6 +192,7 @@ namespace Slic3r { Height, Width, Layer_Change, + Layer_Change_Travel, Color_Change, Pause_Print, Custom_Code, diff --git a/src/libslic3r/GCode/GCodeWriter.cpp b/src/libslic3r/GCode/GCodeWriter.cpp index 5231f97cd8..4983b59374 100644 --- a/src/libslic3r/GCode/GCodeWriter.cpp +++ b/src/libslic3r/GCode/GCodeWriter.cpp @@ -275,10 +275,8 @@ std::string GCodeWriter::set_speed(double F, const std::string_view comment, con return w.string(); } -std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment) +std::string GCodeWriter::get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const { - m_pos.head<2>() = point.head<2>(); - GCodeG1Formatter w; w.emit_xy(point); w.emit_f(this->config.travel_speed.value * 60.0); @@ -286,6 +284,12 @@ std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view return w.string(); } +std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string_view comment) +{ + m_pos.head<2>() = point.head<2>(); + return this->get_travel_to_xy_gcode(point, comment); +} + std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment) { assert(std::abs(point.x()) < 1200.); @@ -303,35 +307,49 @@ std::string GCodeWriter::travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij return w.string(); } -std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string_view comment) +std::string GCodeWriter::travel_to_xyz(const Vec3d& from, const Vec3d &to, const std::string_view comment) { - if (std::abs(point.x() - m_pos.x()) < EPSILON && std::abs(point.y() - m_pos.y()) < EPSILON) { - return this->travel_to_z(point.z(), comment); - } else if (std::abs(point.z() - m_pos.z()) < EPSILON) { - return this->travel_to_xy(point.head<2>(), comment); + if (std::abs(to.x() - m_pos.x()) < EPSILON && std::abs(to.y() - m_pos.y()) < EPSILON) { + return this->travel_to_z(to.z(), comment); + } else if (std::abs(to.z() - m_pos.z()) < EPSILON) { + return this->travel_to_xy(to.head<2>(), comment); } else { - m_pos = point; - - GCodeG1Formatter w; - w.emit_xyz(point); - - Vec2f speed {this->config.travel_speed_z.value, this->config.travel_speed.value}; - w.emit_f(speed.norm() * 60.0); - w.emit_comment(this->config.gcode_comments, comment); - return w.string(); + m_pos = to; + return this->get_travel_to_xyz_gcode(from, to, comment); } } +std::string GCodeWriter::get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const { + GCodeG1Formatter w; + w.emit_xyz(to); + + const double distance_xy{(to.head<2>() - from.head<2>()).norm()}; + const double distnace_z{std::abs(to.z() - from.z())}; + const double time_z = distnace_z / this->config.travel_speed_z.value; + const double time_xy = distance_xy / this->config.travel_speed.value; + const double factor = time_z > 0 ? time_xy / time_z : 1; + if (factor < 1) { + w.emit_f((this->config.travel_speed.value * factor + (1 - factor) * this->config.travel_speed_z.value) * 60.0); + } else { + w.emit_f(this->config.travel_speed.value * 60.0); + } + + w.emit_comment(this->config.gcode_comments, comment); + return w.string(); +} std::string GCodeWriter::travel_to_z(double z, const std::string_view comment) { - return std::abs(m_pos.z() - z) < EPSILON ? "" : this->get_travel_to_z_gcode(z, comment); + if (std::abs(m_pos.z() - z) < EPSILON) { + return ""; + } else { + m_pos.z() = z; + return this->get_travel_to_z_gcode(z, comment); + } } -std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) +std::string GCodeWriter::get_travel_to_z_gcode(double z, const std::string_view comment) const { - m_pos.z() = z; - double speed = this->config.travel_speed_z.value; if (speed == 0.) speed = this->config.travel_speed.value; diff --git a/src/libslic3r/GCode/GCodeWriter.hpp b/src/libslic3r/GCode/GCodeWriter.hpp index d91e67728d..f857d07e4f 100644 --- a/src/libslic3r/GCode/GCodeWriter.hpp +++ b/src/libslic3r/GCode/GCodeWriter.hpp @@ -66,10 +66,23 @@ public: std::string toolchange_prefix() const; std::string toolchange(unsigned int extruder_id); std::string set_speed(double F, const std::string_view comment = {}, const std::string_view cooling_marker = {}) const; + + std::string get_travel_to_xy_gcode(const Vec2d &point, const std::string_view comment) const; std::string travel_to_xy(const Vec2d &point, const std::string_view comment = {}); std::string travel_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, const std::string_view comment = {}); - std::string travel_to_xyz(const Vec3d &point, const std::string_view comment = {}); - std::string get_travel_to_z_gcode(double z, const std::string_view comment); + + /** + * @brief Return gcode with all three axis defined. Optionally adds feedrate. + * + * Feedrate is added the starting point "from" is specified. + * + * @param from Optional starting point of the travel. + * @param to Where to travel to. + * @param comment Description of the travel purpose. + */ + std::string get_travel_to_xyz_gcode(const Vec3d &from, const Vec3d &to, const std::string_view comment) const; + std::string travel_to_xyz(const Vec3d &from, const Vec3d &to, const std::string_view comment = {}); + std::string get_travel_to_z_gcode(double z, const std::string_view comment) const; std::string travel_to_z(double z, const std::string_view comment = {}); std::string extrude_to_xy(const Vec2d &point, double dE, const std::string_view comment = {}); std::string extrude_to_xy_G2G3IJ(const Vec2d &point, const Vec2d &ij, const bool ccw, double dE, const std::string_view comment); diff --git a/src/libslic3r/GCode/LayerChanges.cpp b/src/libslic3r/GCode/LayerChanges.cpp deleted file mode 100644 index bb6656383a..0000000000 --- a/src/libslic3r/GCode/LayerChanges.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include "LayerChanges.hpp" -#include "libslic3r/ClipperUtils.hpp" - -namespace Slic3r::GCode::Impl::LayerChanges { - -Polygon generate_regular_polygon( - const Point ¢roid, const Point &start_point, const unsigned points_count -) { - Points points; - points.reserve(points_count); - const double part_angle{2 * M_PI / points_count}; - for (unsigned i = 0; i < points_count; ++i) { - const double current_angle{i * part_angle}; - points.emplace_back(scaled(std::cos(current_angle)), scaled(std::sin(current_angle))); - } - - Polygon regular_polygon{points}; - const Vec2d current_vector{unscaled(regular_polygon.points.front())}; - const Vec2d expected_vector{unscaled(start_point) - unscaled(centroid)}; - - const double current_scale = current_vector.norm(); - const double expected_scale = expected_vector.norm(); - regular_polygon.scale(expected_scale / current_scale); - - regular_polygon.rotate(angle(current_vector, expected_vector)); - - regular_polygon.translate(centroid); - - return regular_polygon; -} - -Bed::Bed(const std::vector &shape, const double padding) - : inner_offset(get_inner_offset(shape, padding)), centroid(unscaled(inner_offset.centroid())) {} - -bool Bed::contains_within_padding(const Vec2d &point) const { - return inner_offset.contains(scaled(point)); -} - -Polygon Bed::get_inner_offset(const std::vector &shape, const double padding) { - Points shape_scaled; - shape_scaled.reserve(shape.size()); - using std::begin, std::end, std::back_inserter, std::transform; - transform(begin(shape), end(shape), back_inserter(shape_scaled), [](const Vec2d &point) { - return scaled(point); - }); - const Polygons inner_offset{shrink({Polygon{shape_scaled}}, scaled(padding))}; - if (inner_offset.empty()) { - return Polygon{}; - } - return inner_offset.front(); -} - -} // namespace Slic3r::GCode::Impl::LayerChanges diff --git a/src/libslic3r/GCode/LayerChanges.hpp b/src/libslic3r/GCode/LayerChanges.hpp deleted file mode 100644 index 5eb178e3f1..0000000000 --- a/src/libslic3r/GCode/LayerChanges.hpp +++ /dev/null @@ -1,52 +0,0 @@ -/** - * @file - * @brief Utility functions for layer change gcode generation. - */ - -#ifndef slic3r_GCode_LayerChanges_hpp_ -#define slic3r_GCode_LayerChanges_hpp_ - -#include "libslic3r/Point.hpp" -#include "libslic3r/Polygon.hpp" - -namespace Slic3r::GCode::Impl::LayerChanges { -/** - * Generates a regular polygon - all angles are the same (e.g. typical hexagon). - * - * @param centroid Central point. - * @param start_point The polygon point are ordered. This is the first point. - * @param points_count Amount of nodes of the polygon (e.g. 6 for haxagon). - * - * Distance between centroid and start point sets the scale of the polygon. - */ -Polygon generate_regular_polygon( - const Point ¢roid, const Point &start_point, const unsigned points_count -); - -/** - * @brief A representation of the bed shape with inner padding. - * - * Its purpose is to facilitate the bed boundary checking. - */ -class Bed -{ -private: - Polygon inner_offset; - static Polygon get_inner_offset(const std::vector &shape, const double padding); - -public: - /** - * Bed shape with inner padding. - */ - Bed(const std::vector &shape, const double padding); - - Vec2d centroid; - - /** - * Returns true if the point is within the bed shape including inner padding. - */ - bool contains_within_padding(const Vec2d &point) const; -}; -} // namespace Slic3r::GCode::Impl::LayerChanges - -#endif // slic3r_GCode_LayerChanges_hpp_ diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index c894debbf4..1253b88845 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -43,8 +43,8 @@ double ElevatedTravelFormula::operator()(const double distance_from_start) const Points3 generate_flat_travel(tcb::span xy_path, const float elevation) { Points3 result; - result.reserve(xy_path.size() - 1); - for (const Point &point : xy_path.subspan(1)) { + result.reserve(xy_path.size()); + for (const Point &point : xy_path) { result.emplace_back(point.x(), point.y(), scaled(elevation)); } return result; @@ -140,21 +140,6 @@ 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; -} - struct SmoothingParams { double blend_width{}; @@ -212,7 +197,7 @@ SmoothingParams get_smoothing_params( } ElevatedTravelParams get_elevated_traval_params( - const Polyline &xy_path, + const Polyline& xy_path, const FullPrintConfig &config, const unsigned extruder_id, const std::optional> &previous_layer_distancer @@ -236,7 +221,10 @@ ElevatedTravelParams get_elevated_traval_params( } std::optional obstacle_adjusted_slope_end{ - get_obstacle_adjusted_slope_end(xy_path.lines(), previous_layer_distancer)}; + previous_layer_distancer ? + get_first_crossed_line_distance(xy_path.lines(), *previous_layer_distancer) : + std::nullopt + }; if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) { elevation_params.slope_end = *obstacle_adjusted_slope_end; @@ -252,11 +240,6 @@ ElevatedTravelParams get_elevated_traval_params( return elevation_params; } -/** - * @brief Generate regulary spaced points on 1 axis. Includes both from and to. - * - * If count is 1, the point is in the middle of the range. - */ std::vector linspace(const double from, const double to, const unsigned count) { if (count == 0) { return {}; diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp index 84020bb2cc..5ccf30ec2f 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -89,6 +89,20 @@ std::vector slice_xy_path( tcb::span xy_path, tcb::span sorted_distances ); +/** + * @brief Generate regulary spaced points on 1 axis. Includes both from and to. + * + * If count is 1, the point is in the middle of the range. + */ +std::vector linspace(const double from, const double to, const unsigned count); + +ElevatedTravelParams get_elevated_traval_params( + const Polyline& xy_path, + const FullPrintConfig &config, + const unsigned extruder_id, + const std::optional> &previous_layer_distancer = std::nullopt +); + /** * @brief Simply return the xy_path with z coord set to elevation. */ diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 3b2bb4ddd3..04c398e078 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -57,12 +57,24 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || is_ramming || will_go_down); // don't dig into the print if (should_travel_to_tower) { + + const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); - gcode += gcodegen.travel_to( - wipe_tower_point_to_object_point(gcodegen, start_pos), - ExtrusionRole::Mixed, - "Travel to a Wipe Tower"); + if (gcodegen.m_current_layer_first_position) { + gcode += gcodegen.travel_to( + xy_point, + ExtrusionRole::Mixed, + "Travel to a Wipe Tower"); + } else { + const Vec3crd point = to_3d(xy_point, scaled(z)); + const Vec3d gcode_point = to_3d(gcodegen.point_to_gcode(point.head<2>()), z); + gcodegen.set_last_pos(point.head<2>()); + gcodegen.writer().update_position(gcode_point); + gcode += gcodegen.writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point"); + gcode += gcodegen.writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point"); + gcodegen.m_current_layer_first_position = gcode_point; + } gcode += gcodegen.unretract(); } else { // When this is multiextruder printer without any ramming, we can just change diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 43d314a01c..23be4ddedc 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -14,7 +14,6 @@ add_executable(${_TEST_NAME}_tests test_gaps.cpp test_gcode.cpp test_gcode_travels.cpp - test_gcode_layer_changes.cpp test_gcodefindreplace.cpp test_gcodewriter.cpp test_layers.cpp diff --git a/tests/fff_print/test_gcode_layer_changes.cpp b/tests/fff_print/test_gcode_layer_changes.cpp deleted file mode 100644 index 6621d38cbe..0000000000 --- a/tests/fff_print/test_gcode_layer_changes.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include -#include - -using namespace Slic3r; -using namespace Slic3r::GCode::Impl::LayerChanges; - -TEST_CASE("Generate regular polygon", "[GCode]") { - const unsigned points_count{32}; - const Point centroid{scaled(Vec2d{5, -2})}; - const Polygon result{generate_regular_polygon(centroid, scaled(Vec2d{0, 0}), points_count)}; - const Point oposite_point{centroid * 2}; - - REQUIRE(result.size() == 32); - CHECK(result[16].x() == Approx(oposite_point.x())); - CHECK(result[16].y() == Approx(oposite_point.y())); - - std::vector angles; - angles.reserve(points_count); - for (unsigned index = 0; index < points_count; index++) { - const unsigned previous_index{index == 0 ? points_count - 1 : index - 1}; - const unsigned next_index{index == points_count - 1 ? 0 : index + 1}; - - const Point previous_point = result.points[previous_index]; - const Point current_point = result.points[index]; - const Point next_point = result.points[next_index]; - - angles.emplace_back(angle(Vec2crd{previous_point - current_point}, Vec2crd{next_point - current_point})); - } - - std::vector expected; - angles.reserve(points_count); - std::generate_n(std::back_inserter(expected), points_count, [&](){ - return angles.front(); - }); - - CHECK_THAT(angles, Catch::Matchers::Approx(expected)); -} - -TEST_CASE("Square bed with padding", "[GCode]") { - const Bed bed{ - { - Vec2d{0, 0}, - Vec2d{100, 0}, - Vec2d{100, 100}, - Vec2d{0, 100} - }, - 10.0 - }; - - CHECK(bed.centroid.x() == 50); - CHECK(bed.centroid.y() == 50); - CHECK(bed.contains_within_padding(Vec2d{10, 10})); - CHECK_FALSE(bed.contains_within_padding(Vec2d{9, 10})); - -} From ff30d7aad3981d5736625571a2f0230c8c78c95c Mon Sep 17 00:00:00 2001 From: SachCZ Date: Fri, 5 Jan 2024 16:36:09 +0100 Subject: [PATCH 03/11] Replace last_pos and helpers with optional in GCode.cpp --- src/libslic3r/GCode.cpp | 55 +++++++++---------- src/libslic3r/GCode.hpp | 15 ++--- .../GCode/AvoidCrossingPerimeters.cpp | 2 +- src/libslic3r/GCode/Wipe.cpp | 4 +- src/libslic3r/GCode/WipeTowerIntegration.cpp | 26 +++++---- 5 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 1a8efc7a35..5678ce6f78 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1213,7 +1213,7 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail m_enable_cooling_markers = false; // we're not filtering these moves through CoolingBuffer m_avoid_crossing_perimeters.use_external_mp_once(); file.write(this->retract_and_wipe()); - file.write(this->travel_to(Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); + file.write(this->travel_to(*this->last_position, Point(0, 0), ExtrusionRole::None, "move to origin position for next object")); m_enable_cooling_markers = true; // Disable motion planner when traveling to first object point. m_avoid_crossing_perimeters.disable_once(); @@ -1636,7 +1636,7 @@ std::string GCodeGenerator::placeholder_parser_process( if (const std::vector &pos = ppi.opt_position->values; ppi.position != pos) { // Update G-code writer. m_writer.update_position({ pos[0], pos[1], pos[2] }); - this->set_last_pos(this->gcode_to_point({ pos[0], pos[1] })); + this->last_position = this->gcode_to_point({ pos[0], pos[1] }); } for (const Extruder &e : m_writer.extruders()) { @@ -2534,9 +2534,10 @@ void GCodeGenerator::process_layer_single_object( init_layer_delayed(); m_config.apply(region.config()); const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, &m_last_pos)) + const Point* start_near = this->last_position ? &(*(this->last_position)) : nullptr; + for (const ExtrusionEntityReference &fill : chain_extrusion_references(temp_fill_extrusions, start_near)) if (auto *eec = dynamic_cast(&fill.extrusion_entity()); eec) { - for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, &m_last_pos, fill.flipped())) + for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped())) gcode += this->extrude_entity(ee, smooth_path_cache, extrusion_name); } else gcode += this->extrude_entity(fill, smooth_path_cache, extrusion_name); @@ -2658,7 +2659,7 @@ void GCodeGenerator::set_origin(const Vec2d &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left const auto offset = Point::new_scale(m_origin - pointf); - m_last_pos += offset; + *(this->last_position) += offset; m_wipe.offset_path(offset); m_origin = pointf; } @@ -2693,8 +2694,8 @@ std::string GCodeGenerator::change_layer( new_position.z() = print_z; this->writer().update_position(new_position); - m_previous_layer_last_position = this->m_last_pos_defined ? - std::optional{to_3d(this->point_to_gcode(this->last_pos()), previous_layer_z)} : + m_previous_layer_last_position = this->last_position ? + std::optional{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)} : std::nullopt; gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Travel); @@ -2724,10 +2725,10 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC { // Extrude all loops CCW. bool is_hole = loop_src.is_clockwise(); - Point seam_point = this->last_pos(); + Point seam_point = *this->last_position; if (! m_config.spiral_vase && comment_is_perimeter(description)) { assert(m_layer != nullptr); - seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, this->last_pos()); + seam_point = m_seam_placer.place_seam(m_layer, loop_src, m_config.external_perimeters_first, *this->last_position); } // Because the G-code export has 1um resolution, don't generate segments shorter than 1.5 microns, // thus empty path segments will not be produced by G-code export. @@ -2766,7 +2767,7 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC if (std::optional pt = wipe_hide_seam(smooth_path, is_hole, scale_(EXTRUDER_CONFIG(nozzle_diameter))); pt) { // Generate the seam hiding travel move. gcode += m_writer.travel_to_xy(this->point_to_gcode(*pt), "move inwards before travel"); - this->set_last_pos(*pt); + this->last_position = *pt; } } @@ -2779,7 +2780,7 @@ std::string GCodeGenerator::extrude_skirt( { assert(loop_src.is_counter_clockwise()); GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam( - loop_src, false, m_scaled_resolution, this->last_pos(), scaled(0.0015)); + loop_src, false, m_scaled_resolution, *this->last_position, scaled(0.0015)); // Clip the path to avoid the extruder to get exactly on the first point of the loop; // if polyline was shorter than the clipping distance we'd get a null polyline, so @@ -2971,25 +2972,25 @@ std::string GCodeGenerator::_extrude( // Make the first travel just one G1. const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z + this->m_config.z_offset.value)); const Vec3d gcode_point = to_3d(this->point_to_gcode(point.head<2>()), unscaled(point.z())); - this->set_last_pos(path.front().point); + this->last_position = path.front().point; this->writer().update_position(gcode_point); gcode += this->writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point"); gcode += this->writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point"); m_current_layer_first_position = gcode_point; } else { // go to first point of extrusion path - if (!m_last_pos_defined) { + if (!this->last_position) { const double z = this->m_last_layer_z + this->m_config.z_offset.value; const std::string comment{"move to print after unknown position"}; gcode += this->retract_and_wipe(); gcode += this->m_writer.travel_to_xy(this->point_to_gcode(path.front().point), comment); gcode += this->m_writer.get_travel_to_z_gcode(z, comment); - } else if ( m_last_pos != path.front().point) { + } else if ( this->last_position != path.front().point) { std::string comment = "move to first "; comment += description; comment += description_bridge; comment += " point"; - const std::string travel_gcode{this->travel_to(path.front().point, path_attr.role, comment)}; + const std::string travel_gcode{this->travel_to(*this->last_position, path.front().point, path_attr.role, comment)}; gcode += travel_gcode; } } @@ -3213,7 +3214,7 @@ std::string GCodeGenerator::_extrude( if (dynamic_speed_and_fan_speed.second >= 0) gcode += ";_RESET_FAN_SPEED\n"; - this->set_last_pos(path.back().point); + this->last_position = path.back().point; return gcode; } @@ -3238,7 +3239,7 @@ std::string GCodeGenerator::generate_travel_gcode( const Vec3d gcode_point{this->point_to_gcode(point)}; gcode += this->m_writer.travel_to_xyz(previous_point, gcode_point, comment); - this->set_last_pos(point.head<2>()); + this->last_position = point.head<2>(); previous_point = gcode_point; } @@ -3331,18 +3332,16 @@ Polyline GCodeGenerator::generate_travel_xy_path( } // This method accepts &point in print coordinates. -std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, std::string comment) -{ - - const Point start_point = this->last_pos(); - +std::string GCodeGenerator::travel_to( + const Point &start_point, const Point &end_point, ExtrusionRole role, const std::string &comment +) { // check whether a straight travel move would need retraction bool could_be_wipe_disabled {false}; - bool needs_retraction = this->needs_retraction(Polyline{start_point, point}, role); + bool needs_retraction = this->needs_retraction(Polyline{start_point, end_point}, role); Polyline xy_path{generate_travel_xy_path( - start_point, point, needs_retraction, could_be_wipe_disabled + start_point, end_point, needs_retraction, could_be_wipe_disabled )}; needs_retraction = this->needs_retraction(xy_path, role); @@ -3353,12 +3352,12 @@ std::string GCodeGenerator::travel_to(const Point &point, ExtrusionRole role, st m_wipe.reset_path(); } - Point position_before_wipe{this->last_pos()}; + Point position_before_wipe{*this->last_position}; wipe_retract_gcode = this->retract_and_wipe(); - if (this->last_pos() != position_before_wipe) { + if (*this->last_position != position_before_wipe) { xy_path = generate_travel_xy_path( - this->last_pos(), point, needs_retraction, could_be_wipe_disabled + *this->last_position, end_point, needs_retraction, could_be_wipe_disabled ); } } else { @@ -3522,7 +3521,7 @@ std::string GCodeGenerator::set_extruder(unsigned int extruder_id, double print_ gcode += m_ooze_prevention.post_toolchange(*this); // The position is now known after the tool change. - this->m_last_pos_defined = false; + this->last_position = std::nullopt; return gcode; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 92586e8d16..42e0e36647 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -103,7 +103,6 @@ public: m_layer(nullptr), m_object_layer_over_raft(false), m_volumetric_speed(0), - m_last_pos_defined(false), m_last_extrusion_role(GCodeExtrusionRole::None), m_last_width(0.0f), #if ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -124,7 +123,6 @@ public: const Vec2d& origin() const { return m_origin; } void set_origin(const Vec2d &pointf); void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); } - const Point& last_pos() const { return m_last_pos; } // Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset. template Eigen::Matrix point_to_gcode(const Eigen::MatrixBase &point) const { @@ -186,6 +184,8 @@ public: }; using ObjectsLayerToPrint = std::vector; + std::optional last_position; + private: class GCodeOutputStream { public: @@ -266,8 +266,6 @@ private: const GCode::SmoothPathCache &smooth_path_cache_global, GCodeOutputStream &output_stream); - void set_last_pos(const Point &pos) { m_last_pos = pos; m_last_pos_defined = true; } - bool last_pos_defined() const { return m_last_pos_defined; } void set_extruders(const std::vector &extruder_ids); std::string preamble(); std::string change_layer( @@ -332,7 +330,12 @@ private: const bool needs_retraction, bool& could_be_wipe_disabled ); - std::string travel_to(const Point &point, ExtrusionRole role, std::string comment); + std::string travel_to( + const Point &start_point, + const Point &end_point, + ExtrusionRole role, + const std::string &comment + ); bool needs_retraction(const Polyline &travel, ExtrusionRole role = ExtrusionRole::None); std::string retract_and_wipe(bool toolchange = false); @@ -421,8 +424,6 @@ private: double m_last_mm3_per_mm; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING - Point m_last_pos; - bool m_last_pos_defined; std::optional m_previous_layer_last_position; // This needs to be populated during the layer processing! std::optional m_current_layer_first_position; diff --git a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp index 782ebc306f..0b678ae4f7 100644 --- a/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp +++ b/src/libslic3r/GCode/AvoidCrossingPerimeters.cpp @@ -1177,7 +1177,7 @@ Polyline AvoidCrossingPerimeters::travel_to(const GCodeGenerator &gcodegen, cons // Otherwise perform the path planning in the coordinate system of the active object. bool use_external = m_use_external_mp || m_use_external_mp_once; Point scaled_origin = use_external ? Point::new_scale(gcodegen.origin()(0), gcodegen.origin()(1)) : Point(0, 0); - const Point start = gcodegen.last_pos() + scaled_origin; + const Point start = *gcodegen.last_position + scaled_origin; const Point end = point + scaled_origin; const Line travel(start, end); diff --git a/src/libslic3r/GCode/Wipe.cpp b/src/libslic3r/GCode/Wipe.cpp index e75c599297..774512199a 100644 --- a/src/libslic3r/GCode/Wipe.cpp +++ b/src/libslic3r/GCode/Wipe.cpp @@ -164,7 +164,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) return done; }; // Start with the current position, which may be different from the wipe path start in case of loop clipping. - Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos()); + Vec2d prev = gcodegen.point_to_gcode_quantized(*gcodegen.last_position); auto it = this->path().begin(); Vec2d p = gcodegen.point_to_gcode(it->point + m_offset); ++ it; @@ -192,7 +192,7 @@ std::string Wipe::wipe(GCodeGenerator &gcodegen, bool toolchange) // add tag for processor assert(p == GCodeFormatter::quantize(p)); gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n"; - gcodegen.set_last_pos(gcodegen.gcode_to_point(p)); + gcodegen.last_position = gcodegen.gcode_to_point(p); } } diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 04c398e078..3cccbde78d 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -57,22 +57,28 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip || is_ramming || will_go_down); // don't dig into the print if (should_travel_to_tower) { - const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); if (gcodegen.m_current_layer_first_position) { - gcode += gcodegen.travel_to( - xy_point, - ExtrusionRole::Mixed, - "Travel to a Wipe Tower"); + const std::string comment{"Travel to a Wipe Tower"}; + if (gcodegen.last_position) { + gcode += gcodegen.travel_to( + *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment + ); + } else { + gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); + gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); + } } else { const Vec3crd point = to_3d(xy_point, scaled(z)); const Vec3d gcode_point = to_3d(gcodegen.point_to_gcode(point.head<2>()), z); - gcodegen.set_last_pos(point.head<2>()); + gcodegen.last_position = point.head<2>(); gcodegen.writer().update_position(gcode_point); - gcode += gcodegen.writer().get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point"); - gcode += gcodegen.writer().get_travel_to_z_gcode(gcode_point.z(), "move to first layer point"); + gcode += gcodegen.writer() + .get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point"); + gcode += gcodegen.writer() + .get_travel_to_z_gcode(gcode_point.z(), "move to first layer point"); gcodegen.m_current_layer_first_position = gcode_point; } gcode += gcodegen.unretract(); @@ -110,7 +116,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(end_pos.cast()); - gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, end_pos)); + gcodegen.last_position = wipe_tower_point_to_object_point(gcodegen, end_pos); if (!is_approx(z, current_z)) { gcode += gcodegen.writer().retract(); gcode += gcodegen.writer().travel_to_z(current_z, "Travel back up to the topmost object layer."); @@ -259,7 +265,7 @@ std::string WipeTowerIntegration::finalize(GCodeGenerator &gcodegen) std::string gcode; if (std::abs(gcodegen.writer().get_position().z() - m_final_purge.print_z) > EPSILON) gcode += gcodegen.generate_travel_gcode( - {{gcodegen.last_pos().x(), gcodegen.last_pos().y(), scaled(m_final_purge.print_z)}}, + {{gcodegen.last_position->x(), gcodegen.last_position->y(), scaled(m_final_purge.print_z)}}, "move to safe place for purging" ); gcode += append_tcr(gcodegen, m_final_purge, -1); From 5858cbf0f71fa6821b3eec33ac378f23a8400e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Dec 2023 11:22:52 +0100 Subject: [PATCH 04/11] Fix warnings in Travels tests. --- tests/fff_print/test_gcode_travels.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/fff_print/test_gcode_travels.cpp b/tests/fff_print/test_gcode_travels.cpp index a5ec848df5..af947958d2 100644 --- a/tests/fff_print/test_gcode_travels.cpp +++ b/tests/fff_print/test_gcode_travels.cpp @@ -16,8 +16,8 @@ struct ApproxEqualsPoints : public Catch::MatcherBase { const Point& point = points[i]; const Point& expected_point = this->expected[i]; if ( - std::abs(point.x() - expected_point.x()) > this->tolerance - || std::abs(point.y() - expected_point.y()) > this->tolerance + std::abs(point.x() - expected_point.x()) > int(this->tolerance) + || std::abs(point.y() - expected_point.y()) > int(this->tolerance) ) { return false; } @@ -117,10 +117,10 @@ TEST_CASE("Generate elevated travel", "[GCode]") { Points3 result{generate_elevated_travel(xy_path, ensure_points_at_distances, 2.0, [](double x){return 1 + x;})}; CHECK(result == Points3{ - scaled(Vec3f{0, 0, 3.0}), - scaled(Vec3f{0.2, 0, 3.2}), - scaled(Vec3f{0.5, 0, 3.5}), - scaled(Vec3f{1, 0, 4.0}) + scaled(Vec3f{ 0.f, 0.f, 3.f}), + scaled(Vec3f{0.2f, 0.f, 3.2f}), + scaled(Vec3f{0.5f, 0.f, 3.5f}), + scaled(Vec3f{ 1.f, 0.f, 4.f}) }); } From 317db5fab4b62952a477d9bc91e77316d75e0c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Dec 2023 11:23:30 +0100 Subject: [PATCH 05/11] Move ObjectLayerToPrint from GCodeGenerator to outside to allow using forward declaration. --- src/libslic3r/GCode.hpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 42e0e36647..986cf6d92e 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -89,6 +89,21 @@ struct LayerResult { static LayerResult make_nop_layer_result() { return {"", std::numeric_limits::max(), false, false, true}; } }; +namespace GCode { +// Object and support extrusions of the same PrintObject at the same print_z. +// public, so that it could be accessed by free helper functions from GCode.cpp +struct ObjectLayerToPrint +{ + ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} + const Layer* object_layer; + const SupportLayer* support_layer; + const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } + const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } + coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } +}; + +} // namespace GCode + class GCodeGenerator { public: @@ -171,18 +186,8 @@ public: // translate full config into a list of items static void encode_full_config(const Print& print, std::vector>& config); - // Object and support extrusions of the same PrintObject at the same print_z. - // public, so that it could be accessed by free helper functions from GCode.cpp - struct ObjectLayerToPrint - { - ObjectLayerToPrint() : object_layer(nullptr), support_layer(nullptr) {} - const Layer* object_layer; - const SupportLayer* support_layer; - const Layer* layer() const { return (object_layer != nullptr) ? object_layer : support_layer; } - const PrintObject* object() const { return (this->layer() != nullptr) ? this->layer()->object() : nullptr; } - coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } - }; - using ObjectsLayerToPrint = std::vector; + using ObjectLayerToPrint = GCode::ObjectLayerToPrint; + using ObjectsLayerToPrint = std::vector; std::optional last_position; From 1b0ba60280eba992bdb460b98a9699a496e0496d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Dec 2023 11:24:29 +0100 Subject: [PATCH 06/11] Rename m_last_obj_copy to m_current_instance and use struct instead of std::pair. Also, instead of storing the shift of the instance, store the instance index. --- src/libslic3r/GCode.cpp | 6 +++--- src/libslic3r/GCode.hpp | 15 ++++++++++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5678ce6f78..032f04dfef 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2422,10 +2422,10 @@ void GCodeGenerator::process_layer_single_object( m_avoid_crossing_perimeters.init_layer(*m_layer); // When starting a new object, use the external motion planner for the first travel move. const Point &offset = print_object.instances()[print_instance.instance_id].shift; - std::pair this_object_copy(&print_object, offset); - if (m_last_obj_copy != this_object_copy) + GCode::PrintObjectInstance next_instance = {&print_object, int(print_instance.instance_id)}; + if (m_current_instance != next_instance) m_avoid_crossing_perimeters.use_external_mp_once(); - m_last_obj_copy = this_object_copy; + m_current_instance = next_instance; this->set_origin(unscale(offset)); gcode += m_label_objects.start_object(print_instance.print_object.instances()[print_instance.instance_id], GCode::LabelObjects::IncludeName::No); } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 986cf6d92e..91dc8e75c9 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -102,6 +102,15 @@ struct ObjectLayerToPrint coordf_t print_z() const { return (object_layer != nullptr && support_layer != nullptr) ? 0.5 * (object_layer->print_z + support_layer->print_z) : this->layer()->print_z; } }; +struct PrintObjectInstance +{ + const PrintObject *print_object = nullptr; + int instance_idx = -1; + + bool operator==(const PrintObjectInstance &other) const {return print_object == other.print_object && instance_idx == other.instance_idx; } + bool operator!=(const PrintObjectInstance &other) const { return *this == other; } +}; + } // namespace GCode class GCodeGenerator { @@ -126,7 +135,7 @@ public: m_brim_done(false), m_second_layer_things_done(false), m_silent_time_estimator_enabled(false), - m_last_obj_copy(nullptr, Point(std::numeric_limits::max(), std::numeric_limits::max())) + m_current_instance({nullptr, -1}) {} ~GCodeGenerator() = default; @@ -445,8 +454,8 @@ private: bool m_brim_done; // Flag indicating whether the nozzle temperature changes from 1st to 2nd layer were performed. bool m_second_layer_things_done; - // Index of a last object copy extruded. - std::pair m_last_obj_copy; + // Pointer to currently exporting PrintObject and instance index. + GCode::PrintObjectInstance m_current_instance; bool m_silent_time_estimator_enabled; From dbd036976794b8ac2c8ebbe13e0fd9436cd794ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Dec 2023 11:25:17 +0100 Subject: [PATCH 07/11] Use forward declarations in Travel.hpp. --- src/libslic3r/GCode/Travels.cpp | 2 ++ src/libslic3r/GCode/Travels.hpp | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 1253b88845..45b443ce66 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -1,5 +1,7 @@ #include "Travels.hpp" +#include "libslic3r/PrintConfig.hpp" + namespace Slic3r::GCode::Impl::Travels { ElevatedTravelFormula::ElevatedTravelFormula(const ElevatedTravelParams ¶ms) diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp index 5ccf30ec2f..bbaccd46ee 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -13,10 +13,16 @@ #include -#include "libslic3r/Line.hpp" -#include "libslic3r/Point.hpp" #include "libslic3r/AABBTreeLines.hpp" -#include "libslic3r/PrintConfig.hpp" + +// Forward declarations. +namespace Slic3r { +class Point; +class Linef; +class Polyline; +class FullPrintConfig; + +} // namespace Slic3r namespace Slic3r::GCode::Impl::Travels { /** From 2fc9299c65c64d058bb3a7c65b7a263bd1f6f22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Dec 2023 11:28:45 +0100 Subject: [PATCH 08/11] Use max value of double instead of std::optional in get_first_crossed_line_distance() and get_obstacle_adjusted_slope_end(). --- src/libslic3r/GCode/Travels.cpp | 20 +++++++++----------- src/libslic3r/GCode/Travels.hpp | 2 +- tests/fff_print/test_gcode_travels.cpp | 14 +++++++------- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/libslic3r/GCode/Travels.cpp b/src/libslic3r/GCode/Travels.cpp index 45b443ce66..1146d096f4 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -114,13 +114,12 @@ Points3 generate_elevated_travel( return result; } -std::optional get_first_crossed_line_distance( +double get_first_crossed_line_distance( tcb::span xy_path, const AABBTreeLines::LinesDistancer &distancer ) { assert(!xy_path.empty()); - if (xy_path.empty()) { - return {}; - } + if (xy_path.empty()) + return std::numeric_limits::max(); double traversed_distance = 0; for (const Line &line : xy_path) { @@ -139,7 +138,7 @@ std::optional get_first_crossed_line_distance( traversed_distance += (unscaled_line.a - unscaled_line.b).norm(); } - return {}; + return std::numeric_limits::max(); } struct SmoothingParams @@ -222,15 +221,14 @@ ElevatedTravelParams get_elevated_traval_params( elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad); } - std::optional obstacle_adjusted_slope_end{ + const double obstacle_adjusted_slope_end{ previous_layer_distancer ? - get_first_crossed_line_distance(xy_path.lines(), *previous_layer_distancer) : - std::nullopt + get_first_crossed_line_distance(xy_path.lines(), *previous_layer_distancer) : + std::numeric_limits::max() }; - if (obstacle_adjusted_slope_end && obstacle_adjusted_slope_end < elevation_params.slope_end) { - elevation_params.slope_end = *obstacle_adjusted_slope_end; - } + if (obstacle_adjusted_slope_end < elevation_params.slope_end) + elevation_params.slope_end = obstacle_adjusted_slope_end; SmoothingParams smoothing_params{get_smoothing_params( elevation_params.lift_height, elevation_params.slope_end, extruder_id, diff --git a/src/libslic3r/GCode/Travels.hpp b/src/libslic3r/GCode/Travels.hpp index bbaccd46ee..c0a9c4442d 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -141,7 +141,7 @@ Points3 generate_elevated_travel( * * **Ignores intersection with xy_path starting point.** */ -std::optional get_first_crossed_line_distance( +double get_first_crossed_line_distance( tcb::span xy_path, const AABBTreeLines::LinesDistancer &distancer ); diff --git a/tests/fff_print/test_gcode_travels.cpp b/tests/fff_print/test_gcode_travels.cpp index af947958d2..9d43c3f06e 100644 --- a/tests/fff_print/test_gcode_travels.cpp +++ b/tests/fff_print/test_gcode_travels.cpp @@ -171,13 +171,13 @@ TEST_CASE("Get first crossed line distance", "[GCode]") { // Try different cases by skipping lines in the travel. 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)); - 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)); + 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(get_first_crossed_line_distance(tcb::span{travel}.subspan(6), distancer) == std::numeric_limits::max()); } From da57489874a3812cbe517f310897c6be8c2b4709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Fri, 1 Dec 2023 11:31:38 +0100 Subject: [PATCH 09/11] Implement lift before obstacles that take into account already extruder extrusions on the current layer. Ignore the first intersection of the travel path with the object from which the travel starts. Created a new class TravelObstacleTracker, for wrapping all data structures related to lift before obstacles. --- src/libslic3r/ExPolygon.hpp | 2 +- src/libslic3r/GCode.cpp | 38 ++--- src/libslic3r/GCode.hpp | 5 +- src/libslic3r/GCode/Travels.cpp | 188 ++++++++++++++++++++++--- src/libslic3r/GCode/Travels.hpp | 86 ++++++++++- src/libslic3r/Line.hpp | 1 + src/slic3r/GUI/Tab.cpp | 6 +- tests/fff_print/test_gcode_travels.cpp | 5 +- 8 files changed, 270 insertions(+), 61 deletions(-) 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)); From 5ddcea806b6761c2ec153705d19294a1e682300c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Tue, 9 Jan 2024 10:53:43 +0100 Subject: [PATCH 10/11] Add to GCodeGenerator::last_position only when it has an assigned value. This behavior was there for a long time, but it was uncovered when std::optional was used. --- src/libslic3r/GCode.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5f8e5df2b5..ff7b341807 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2641,7 +2641,9 @@ void GCodeGenerator::set_origin(const Vec2d &pointf) { // if origin increases (goes towards right), last_pos decreases because it goes towards left const auto offset = Point::new_scale(m_origin - pointf); - *(this->last_position) += offset; + if (last_position.has_value()) + *(this->last_position) += offset; + m_wipe.offset_path(offset); m_origin = pointf; } From f9283728d693840abe19335601ab2e4f5fb79b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C5=A0ach?= Date: Mon, 8 Jan 2024 14:45:37 +0100 Subject: [PATCH 11/11] During first layer change, do not actually move z. This enables the user to set his own z in start gcode. Fixes #11843. --- src/libslic3r/GCode.cpp | 22 ++++++++++++-------- src/libslic3r/GCode/WipeTowerIntegration.cpp | 8 +++---- src/libslic3r/GCode/WipeTowerIntegration.hpp | 5 +++-- tests/fff_print/test_cooling.cpp | 6 +++--- tests/fff_print/test_custom_gcode.cpp | 14 ++++++++++--- 5 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ff7b341807..9061d8bc27 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1249,7 +1249,12 @@ void GCodeGenerator::_do_export(Print& print, GCodeOutputStream &file, Thumbnail // Prusa Multi-Material wipe tower. if (has_wipe_tower && ! layers_to_print.empty()) { m_wipe_tower = std::make_unique(print.config(), *print.wipe_tower_data().priming.get(), print.wipe_tower_data().tool_changes, *print.wipe_tower_data().final_purge.get()); - file.write(m_writer.travel_to_z(first_layer_height + m_config.z_offset.value, "Move to the first layer height")); + + // Set position for wipe tower generation. + Vec3d new_position = this->writer().get_position(); + new_position.z() = first_layer_height + m_config.z_offset.value; + this->writer().update_position(new_position); + if (print.config().single_extruder_multi_material_priming) { file.write(m_wipe_tower->prime(*this)); // Verify, whether the print overaps the priming extrusions. @@ -2195,7 +2200,7 @@ LayerResult GCodeGenerator::process_layer( m_current_layer_first_position = std::nullopt; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. - if (! print.config().before_layer_gcode.value.empty()) { + if (!first_layer && ! print.config().before_layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); @@ -2210,7 +2215,7 @@ LayerResult GCodeGenerator::process_layer( m_travel_obstacle_tracker.init_layer(layer, layers); m_object_layer_over_raft = false; - if (! print.config().layer_gcode.value.empty()) { + if (!first_layer && ! print.config().layer_gcode.value.empty()) { DynamicConfig config; config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); @@ -2346,13 +2351,12 @@ LayerResult GCodeGenerator::process_layer( && EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(travel_slope) > 0 && EXTRUDER_CONFIG(travel_slope) < 90 ); - if (do_ramping_layer_change) { + if (first_layer) { + layer_change_gcode = ""; // Explicit for readability. + } else if (do_ramping_layer_change) { layer_change_gcode = this->get_layer_change_gcode(*m_previous_layer_last_position, *m_current_layer_first_position, *m_layer_change_extruder_id); } else { - if (!m_current_layer_first_position) { - throw std::runtime_error("Destination is required for layer change!"); - } - layer_change_gcode = this->writer().get_travel_to_z_gcode(m_current_layer_first_position->z(), "simple layer change"); + layer_change_gcode = this->writer().get_travel_to_z_gcode(m_config.z_offset.value + print_z, "simple layer change"); } boost::algorithm::replace_first(gcode, tag, layer_change_gcode); @@ -2675,7 +2679,7 @@ std::string GCodeGenerator::change_layer( gcode += this->retract_and_wipe(); Vec3d new_position = this->writer().get_position(); - new_position.z() = print_z; + new_position.z() = print_z + m_config.z_offset.value; this->writer().update_position(new_position); m_previous_layer_last_position = this->last_position ? diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index 3cccbde78d..56c2f4af93 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -60,8 +60,8 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip const Point xy_point = wipe_tower_point_to_object_point(gcodegen, start_pos); gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once(); + const std::string comment{"Travel to a Wipe Tower"}; if (gcodegen.m_current_layer_first_position) { - const std::string comment{"Travel to a Wipe Tower"}; if (gcodegen.last_position) { gcode += gcodegen.travel_to( *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment @@ -72,13 +72,13 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip } } else { const Vec3crd point = to_3d(xy_point, scaled(z)); - const Vec3d gcode_point = to_3d(gcodegen.point_to_gcode(point.head<2>()), z); + const Vec3d gcode_point = gcodegen.point_to_gcode(point); gcodegen.last_position = point.head<2>(); gcodegen.writer().update_position(gcode_point); gcode += gcodegen.writer() - .get_travel_to_xy_gcode(gcode_point.head<2>(), "move to first layer point"); + .get_travel_to_xy_gcode(gcode_point.head<2>(), comment); gcode += gcodegen.writer() - .get_travel_to_z_gcode(gcode_point.z(), "move to first layer point"); + .get_travel_to_z_gcode(gcode_point.z(), comment); gcodegen.m_current_layer_first_position = gcode_point; } gcode += gcodegen.unretract(); diff --git a/src/libslic3r/GCode/WipeTowerIntegration.hpp b/src/libslic3r/GCode/WipeTowerIntegration.hpp index 52b27625bb..231e6659c6 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.hpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.hpp @@ -26,7 +26,8 @@ public: m_tool_changes(tool_changes), m_final_purge(final_purge), m_layer_idx(-1), - m_tool_change_idx(0) + m_tool_change_idx(0), + m_last_wipe_tower_print_z(print_config.z_offset.value) {} std::string prime(GCodeGenerator &gcodegen); @@ -56,7 +57,7 @@ private: // Current layer index. int m_layer_idx; int m_tool_change_idx; - double m_last_wipe_tower_print_z = 0.f; + double m_last_wipe_tower_print_z; }; } // namespace GCode diff --git a/tests/fff_print/test_cooling.cpp b/tests/fff_print/test_cooling.cpp index 1f560d53f0..187d7f4394 100644 --- a/tests/fff_print/test_cooling.cpp +++ b/tests/fff_print/test_cooling.cpp @@ -250,9 +250,9 @@ SCENARIO("Cooling integration tests", "[Cooling]") { if (l == 0) l = line.dist_Z(self); if (l > 0.) { - if (layer_times.empty()) - layer_times.emplace_back(0.); - layer_times.back() += 60. * std::abs(l) / line.new_F(self); + if (!layer_times.empty()) { // Ignore anything before first z move. + layer_times.back() += 60. * std::abs(l) / line.new_F(self); + } } if (line.has('F') && line.f() == external_perimeter_speed) ++ layer_external[scaled(self.z())]; diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp index 0d109070bf..2dd97e9c35 100644 --- a/tests/fff_print/test_custom_gcode.cpp +++ b/tests/fff_print/test_custom_gcode.cpp @@ -45,13 +45,21 @@ SCENARIO("Custom G-code", "[CustomGCode]") }); GCodeReader parser; bool last_move_was_z_change = false; + bool first_z_move = true; // First z move is not a layer change. int num_layer_changes_not_applied = 0; parser.parse_buffer(Slic3r::Test::slice({ Test::TestMesh::cube_2x20x10 }, config), - [&last_move_was_z_change, &num_layer_changes_not_applied](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) + [&](Slic3r::GCodeReader &self, const Slic3r::GCodeReader::GCodeLine &line) { - if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) + if (last_move_was_z_change != line.cmd_is("_MY_CUSTOM_LAYER_GCODE_")) { ++ num_layer_changes_not_applied; - last_move_was_z_change = line.dist_Z(self) > 0; + } + if (line.dist_Z(self) > 0 && first_z_move) { + first_z_move = false; + } else if (line.dist_Z(self) > 0){ + last_move_was_z_change = true; + } else { + last_move_was_z_change = false; + } }); THEN("custom layer G-code is applied after Z move and before other moves") { REQUIRE(num_layer_changes_not_applied == 0);