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/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 30df60c786..9061d8bc27 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" @@ -1214,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(); @@ -1250,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. @@ -1637,7 +1641,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()) { @@ -2061,24 +2065,48 @@ 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})); - } - } +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, this->m_travel_obstacle_tracker)}; + + 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; } - return AABBTreeLines::LinesDistancer{std::move(lines)}; -} + + 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, @@ -2150,6 +2178,7 @@ LayerResult GCodeGenerator::process_layer( m_enable_loop_clipping = !enable; } + std::string gcode; assert(is_decimal_separator_point()); // for the sprintfs @@ -2168,9 +2197,10 @@ 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()) { + 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)); @@ -2179,13 +2209,13 @@ 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); - } + 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()) { + 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)); @@ -2305,6 +2335,32 @@ 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 (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 { + 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); + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2350,10 +2406,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); } @@ -2462,9 +2518,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); @@ -2493,9 +2550,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); + } } } }; @@ -2586,7 +2645,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); - m_last_pos += offset; + if (last_position.has_value()) + *(this->last_position) += offset; + m_wipe.offset_path(offset); m_origin = pointf; } @@ -2604,63 +2665,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 +2678,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 + m_config.z_offset.value; + 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->last_position ? + std::optional{to_3d(this->point_to_gcode(*this->last_position), 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(); @@ -2720,10 +2713,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. @@ -2762,7 +2755,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; } } @@ -2775,7 +2768,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 @@ -2963,19 +2956,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->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 (!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 ( 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(*this->last_position, path.front().point, path_attr.role, comment)}; + gcode += travel_gcode; + } } // compensate retraction @@ -3197,7 +3202,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; } @@ -3217,9 +3222,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); - this->set_last_pos(point.head<2>()); + 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->last_position = point.head<2>(); + previous_point = gcode_point; } if (! GCodeWriter::supports_separate_travel_acceleration(config().gcode_flavor)) { @@ -3311,18 +3320,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); @@ -3333,12 +3340,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 { @@ -3351,15 +3358,23 @@ 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) : 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) ) ); @@ -3494,7 +3509,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 8890ec6125..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 @@ -89,6 +90,30 @@ 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; } +}; + +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 { public: @@ -103,7 +128,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 @@ -112,7 +136,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; @@ -124,14 +148,27 @@ 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 - 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 { @@ -159,18 +196,10 @@ 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; private: class GCodeOutputStream { @@ -216,6 +245,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. @@ -249,19 +281,11 @@ 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::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.); @@ -321,7 +345,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); @@ -377,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 @@ -396,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? @@ -410,9 +439,10 @@ 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; + 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; @@ -425,8 +455,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; 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/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 17f4344742..e1f9ddcd37 100644 --- a/src/libslic3r/GCode/Travels.cpp +++ b/src/libslic3r/GCode/Travels.cpp @@ -1,11 +1,150 @@ #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 { +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); - 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; @@ -52,26 +191,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, @@ -93,59 +212,153 @@ Points3 generate_elevated_travel( return result; } -std::optional get_first_crossed_line_distance( - tcb::span xy_path, const AABBTreeLines::LinesDistancer &distancer +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, + const ObjectsLayerToPrint &objects_to_print, + const std::function &predicate, + const bool ignore_starting_object_intersection ) { assert(!xy_path.empty()); - if (xy_path.empty()) { - return {}; - } + 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 {}; + return std::numeric_limits::max(); } -std::optional get_obstacle_adjusted_slope_end( - const Lines &xy_path, - const std::optional> &previous_layer_distancer +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{}; + 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 (!previous_layer_distancer) { - return std::nullopt; + 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}; } - 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; + + 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 + const GCode::TravelObstacleTracker &obstacle_tracker ) { ElevatedTravelParams elevation_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); @@ -159,22 +372,44 @@ ElevatedTravelParams get_elevated_traval_params( elevation_params.slope_end = elevation_params.lift_height / std::tan(slope_rad); } - std::optional obstacle_adjusted_slope_end{ - get_obstacle_adjusted_slope_end(xy_path, previous_layer_distancer)}; + 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; - 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; } +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, 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); @@ -184,15 +419,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, obstacle_tracker + )}; - 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..7f7c017840 100644 --- a/src/libslic3r/GCode/Travels.hpp +++ b/src/libslic3r/GCode/Travels.hpp @@ -11,18 +11,137 @@ #include #include -#include "libslic3r/Line.hpp" -#include "libslic3r/Point.hpp" +#include +#include + #include "libslic3r/AABBTreeLines.hpp" -#include "libslic3r/PrintConfig.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. + */ 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. * @@ -48,6 +167,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 GCode::TravelObstacleTracker &obstacle_tracker +); + /** * @brief Simply return the xy_path with z coord set to elevation. */ @@ -76,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.** */ -std::optional get_first_crossed_line_distance( - tcb::span xy_path, const AABBTreeLines::LinesDistancer &distancer -); +double get_first_crossed_line_distance( + 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. @@ -93,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/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 3b2bb4ddd3..56c2f4af93 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -57,12 +57,30 @@ 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"); + const std::string comment{"Travel to a Wipe Tower"}; + if (gcodegen.m_current_layer_first_position) { + 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 = 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>(), comment); + gcode += gcodegen.writer() + .get_travel_to_z_gcode(gcode_point.z(), comment); + gcodegen.m_current_layer_first_position = gcode_point; + } gcode += gcodegen.unretract(); } else { // When this is multiextruder printer without any ramming, we can just change @@ -98,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."); @@ -247,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); 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/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/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_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); 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})); - -} diff --git a/tests/fff_print/test_gcode_travels.cpp b/tests/fff_print/test_gcode_travels.cpp index d8bbf4c0e7..895c415c86 100644 --- a/tests/fff_print/test_gcode_travels.cpp +++ b/tests/fff_print/test_gcode_travels.cpp @@ -1,6 +1,8 @@ #include #include #include +#include +#include using namespace Slic3r; using namespace Slic3r::GCode::Impl::Travels; @@ -15,8 +17,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; } @@ -116,10 +118,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}) }); } @@ -161,21 +163,39 @@ 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)); - 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()); } + +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); +}