diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index a69e7b48b5..937d4db91c 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -213,6 +213,8 @@ set(SLIC3R_SOURCES GCode/AvoidCrossingPerimeters.hpp GCode/Travels.cpp GCode/Travels.hpp + GCode/ExtrusionOrder.cpp + GCode/ExtrusionOrder.hpp GCode.cpp GCode.hpp GCodeReader.cpp diff --git a/src/libslic3r/ExtrusionEntity.hpp b/src/libslic3r/ExtrusionEntity.hpp index 4007d421b1..0c704b65ce 100644 --- a/src/libslic3r/ExtrusionEntity.hpp +++ b/src/libslic3r/ExtrusionEntity.hpp @@ -298,7 +298,7 @@ class ExtrusionLoop : public ExtrusionEntity { public: ExtrusionPaths paths; - + ExtrusionLoop() = default; ExtrusionLoop(ExtrusionLoopRole role) : m_loop_role(role) {} ExtrusionLoop(const ExtrusionPaths &paths, ExtrusionLoopRole role = elrDefault) : paths(paths), m_loop_role(role) {} diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 468e56f425..a1812fe77f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1961,7 +1961,7 @@ void GCodeGenerator::_print_first_layer_extruder_temperatures(GCodeOutputStream } } -std::vector GCodeGenerator::sort_print_object_instances( +std::vector GCodeGenerator::sort_print_object_instances( const std::vector &object_layers, // Ordering must be defined for normal (non-sequential print). const std::vector *ordering, @@ -2174,25 +2174,11 @@ bool GCodeGenerator::line_distancer_is_required(const std::vector& } Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d &to) { - bool could_be_wipe_disabled{false}; const bool needs_retraction{true}; - const Point saved_last_position{*this->last_position}; - const bool saved_use_external_mp{this->m_avoid_crossing_perimeters.use_external_mp_once}; - const Vec2d saved_origin{this->origin()}; - const Layer* saved_layer{this->layer()}; - - this->m_avoid_crossing_perimeters.use_external_mp_once = m_layer_change_used_external_mp; - if (this->m_layer_change_origin) { - this->m_origin = *this->m_layer_change_origin; - } - this->m_layer = m_layer_change_layer; - this->m_avoid_crossing_perimeters.init_layer(*this->m_layer); - const Point start_point{this->gcode_to_point(from.head<2>())}; const Point end_point{this->gcode_to_point(to.head<2>())}; - this->last_position = start_point; Polyline xy_path{ this->generate_travel_xy_path(start_point, end_point, needs_retraction, could_be_wipe_disabled)}; @@ -2202,11 +2188,6 @@ Polyline GCodeGenerator::get_layer_change_xy_path(const Vec3d &from, const Vec3d gcode_xy_path.push_back(this->point_to_gcode(point)); } - this->last_position = saved_last_position; - this->m_avoid_crossing_perimeters.use_external_mp_once = saved_use_external_mp; - this->m_origin = saved_origin; - this->m_layer = saved_layer; - Polyline result; for (const Vec2d& point : gcode_xy_path) { result.points.push_back(gcode_to_point(point)); @@ -2283,6 +2264,129 @@ std::string GCodeGenerator::generate_ramping_layer_change_gcode( return travel_gcode; } +#ifndef NDEBUG +static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) +{ + for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) { + assert(it->path.size() >= 2); + assert(std::prev(it)->path.back().point == it->path.front().point); + } + assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point); + return true; +} +#endif //NDEBUG + +using GCode::ExtrusionOrder::InstancePoint; + +struct SmoothPathGenerator { + const Seams::Placer &seam_placer; + const GCode::SmoothPathCaches &smooth_path_caches; + double scaled_resolution; + const PrintConfig &config; + bool enable_loop_clipping; + + GCode::SmoothPath operator()(const Layer *layer, const ExtrusionEntityReference &extrusion_reference, const unsigned extruder_id, std::optional &previous_position) { + const ExtrusionEntity *extrusion_entity{&extrusion_reference.extrusion_entity()}; + + GCode::SmoothPath result; + + if (auto loop = dynamic_cast(extrusion_entity)) { + Point seam_point = previous_position ? previous_position->local_point : Point::Zero(); + if (loop->role().is_perimeter() && layer != nullptr) { + seam_point = this->seam_placer.place_seam(layer, *loop, seam_point); + } + + const GCode::SmoothPathCache &smooth_path_cache{loop->role().is_perimeter() ? smooth_path_caches.layer_local() : smooth_path_caches.global()}; + // 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. + GCode::SmoothPath smooth_path = + smooth_path_cache.resolve_or_fit_split_with_seam( + *loop, extrusion_reference.flipped(), scaled_resolution, seam_point, 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 we discard it in that case. + const auto nozzle_diameter{config.nozzle_diameter.get_at(extruder_id)}; + if (enable_loop_clipping) { + clip_end( + smooth_path, + scale_(nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, + scaled(GCode::ExtrusionOrder::min_gcode_segment_length) + ); + } + + assert(validate_smooth_path(smooth_path, !m_enable_loop_clipping)); + + result = smooth_path; + } else if (auto multipath = dynamic_cast(extrusion_entity)) { + result = smooth_path_caches.layer_local().resolve_or_fit(*multipath, extrusion_reference.flipped(), scaled_resolution); + } else if (auto path = dynamic_cast(extrusion_entity)) { + result = GCode::SmoothPath{GCode::SmoothPathElement{path->attributes(), smooth_path_caches.layer_local().resolve_or_fit(*path, extrusion_reference.flipped(), scaled_resolution)}}; + } + for (auto it{result.rbegin()}; it != result.rend(); ++it) { + if (!it->path.empty()) { + previous_position = InstancePoint{it->path.back().point}; + break; + } + } + + return result; + } + +}; + +std::vector GCodeGenerator::get_sorted_extrusions( + const Print &print, + const ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const GCode::SmoothPathCaches &smooth_path_caches, + const bool first_layer +) { + // Map from extruder ID to index of skirt loops to be extruded with that extruder. + // Extrude skirt at the print_z of the raft layers and normal object layers + // not at the print_z of the interlaced support material layers. + std::map> skirt_loops_per_extruder{ + first_layer ? + Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : + Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done)}; + + const SmoothPathGenerator smooth_path{ + m_seam_placer, + smooth_path_caches, + m_scaled_resolution, + m_config, + m_enable_loop_clipping + }; + + using GCode::ExtrusionOrder::ExtruderExtrusions; + using GCode::ExtrusionOrder::get_extrusions; + + const std::optional previous_position{ + this->last_position ? std::optional{scaled(this->point_to_gcode(*this->last_position))} : + std::nullopt}; + std::vector extrusions{ + get_extrusions( + print, + this->m_wipe_tower.get(), + layers, + first_layer, + layer_tools, + instances_to_print, + skirt_loops_per_extruder, + this->m_writer.extruder()->id(), + smooth_path, + !this->m_brim_done, + previous_position + ) + }; + this->m_brim_done = true; + + return extrusions; +} + + // 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. @@ -2331,9 +2435,6 @@ LayerResult GCodeGenerator::process_layer( unsigned int first_extruder_id = layer_tools.extruders.front(); const std::vector instances_to_print{sort_print_object_instances(layers, ordering, single_object_instance_idx)}; - const PrintInstance* first_instance{instances_to_print.empty() ? nullptr : &instances_to_print.front().print_object.instances()[instances_to_print.front().instance_id]}; - m_label_objects.update(first_instance); - // Initialize config with the 1st object to be printed at this layer. m_config.apply(layer.object()->config(), true); @@ -2357,8 +2458,19 @@ LayerResult GCodeGenerator::process_layer( m_enable_loop_clipping = !enable; } + using GCode::ExtrusionOrder::ExtruderExtrusions; + const std::vector extrusions{ + this->get_sorted_extrusions(print, layers, layer_tools, instances_to_print, smooth_path_caches, first_layer)}; + + if (extrusions.empty()) { + return result; + } + const Point first_point{*GCode::ExtrusionOrder::get_first_point(extrusions)}; + const PrintInstance* first_instance{get_first_instance(extrusions, instances_to_print)}; + m_label_objects.update(first_instance); std::string gcode; + assert(is_decimal_separator_point()); // for the sprintfs // add tag for processor @@ -2376,8 +2488,6 @@ 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; - m_already_unretracted = false; // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (!first_layer && ! print.config().before_layer_gcode.value.empty()) { @@ -2389,7 +2499,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, result.spiral_vase_enable, first_point, first_layer); // 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) m_travel_obstacle_tracker.init_layer(layer, layers); @@ -2424,15 +2534,6 @@ LayerResult GCodeGenerator::process_layer( m_second_layer_things_done = true; } - // Map from extruder ID to index of skirt loops to be extruded with that extruder. - std::map> skirt_loops_per_extruder; - - // Extrude skirt at the print_z of the raft layers and normal object layers - // not at the print_z of the interlaced support material layers. - skirt_loops_per_extruder = first_layer ? - Skirt::make_skirt_loops_per_extruder_1st_layer(print, layer_tools, m_skirt_done) : - Skirt::make_skirt_loops_per_extruder_other_layers(print, layer_tools, m_skirt_done); - if (this->config().avoid_crossing_curled_overhangs) { m_avoid_crossing_curled_overhangs.clear(); for (const ObjectLayerToPrint &layer_to_print : layers) { @@ -2462,157 +2563,124 @@ LayerResult GCodeGenerator::process_layer( } } + this->set_origin({0, 0}); + bool moved_to_first_point{false}; + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. - for (unsigned int extruder_id : layer_tools.extruders) + for (const ExtruderExtrusions &extruder_extrusions : extrusions) { gcode += (layer_tools.has_wipe_tower && m_wipe_tower) ? - m_wipe_tower->tool_change(*this, extruder_id, extruder_id == layer_tools.extruders.back()) : - this->set_extruder(extruder_id, print_z); + m_wipe_tower->tool_change(*this, extruder_extrusions.extruder_id, extruder_extrusions.extruder_id == layer_tools.extruders.back()) : + this->set_extruder(extruder_extrusions.extruder_id, print_z); // let analyzer tag generator aware of a role type change if (layer_tools.has_wipe_tower && m_wipe_tower) m_last_processor_extrusion_role = GCodeExtrusionRole::WipeTower; - if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == int(extruder_id)) { + if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == int(extruder_extrusions.extruder_id)) { assert(m_writer.extruder()->id() == extruder_id_for_custom_gcode); assert(m_pending_pre_extrusion_gcode.empty()); // Now we have picked the right extruder, so we can emit the custom g-code. gcode += ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); } - if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { - if (!this->m_config.complete_objects.value) { - gcode += this->m_label_objects.maybe_stop_instance(); - } + if (!extruder_extrusions.skirt.empty() || !extruder_extrusions.brim.empty()) { + gcode += m_label_objects.maybe_stop_instance(); + this->m_label_objects.update(nullptr); + } + + if (!moved_to_first_point) { + const Vec3crd point{to_3d(first_point, scaled(print_z))}; + + gcode += this->travel_to_first_position(point, print_z, ExtrusionRole::Mixed, [this]() { + if (m_writer.multiple_extruders) { + return std::string{""}; + } + return m_label_objects.maybe_change_instance(m_writer); + }); + moved_to_first_point = true; + } + + if (!extruder_extrusions.skirt.empty()) { this->m_label_objects.update(nullptr); - const std::pair loops = loops_it->second; - this->set_origin(0., 0.); m_avoid_crossing_perimeters.use_external_mp(); Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]))); double mm3_per_mm = layer_skirt_flow.mm3_per_mm(); - for (size_t i = loops.first; i < loops.second; ++i) { + for (const auto&[_, smooth_path] : extruder_extrusions.skirt) { // Adjust flow according to this layer's layer height. //FIXME using the support_material_speed of the 1st object printed. - gcode += this->extrude_skirt(dynamic_cast(*print.skirt().entities[i]), + gcode += this->extrude_skirt(smooth_path, // Override of skirt extrusion parameters. extrude_skirt() will fill in the extrusion width. - ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() }, - smooth_path_caches.global(), "skirt"sv, m_config.support_material_speed.value); + ExtrusionFlow{ mm3_per_mm, 0., layer_skirt_flow.height() } + ); } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point if this is the first layer (but don't in next layers). - if (first_layer && loops.first == 0) + if (first_layer && extruder_extrusions.skirt.front().first == 0) m_avoid_crossing_perimeters.disable_once(); } - // Extrude brim with the extruder of the 1st region. - if (! m_brim_done) { - - if (!this->m_config.complete_objects.value) { - gcode += this->m_label_objects.maybe_stop_instance(); - } - this->m_label_objects.update(nullptr); - - this->set_origin(0., 0.); + if (!extruder_extrusions.brim.empty()) { m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity *ee : print.brim().entities) - gcode += this->extrude_entity({ *ee, false }, smooth_path_caches.global(), "brim"sv, m_config.support_material_speed.value); - m_brim_done = true; + + for (const GCode::ExtrusionOrder::BrimPath &brim_path : extruder_extrusions.brim) { + gcode += this->extrude_smooth_path(brim_path.path, brim_path.is_loop, "brim", m_config.support_material_speed.value); + } m_avoid_crossing_perimeters.use_external_mp(false); // Allow a straight travel move to the first object point. m_avoid_crossing_perimeters.disable_once(); } - this->m_label_objects.update(first_instance); - // We are almost ready to print. However, we must go through all the objects twice to print the the overridden extrusions first (infill/perimeter wiping feature): - bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); - if (is_anything_overridden) { + m_label_objects.update(first_instance); + + if (!extruder_extrusions.overriden_extrusions.empty()) { // Extrude wipes. size_t gcode_size_old = gcode.size(); - for (const InstanceToPrint &instance : instances_to_print) - this->process_layer_single_object( - gcode, extruder_id, instance, - layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(), - is_anything_overridden, true /* print_wipe_extrusions */); - if (gcode_size_old < gcode.size()) + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + using GCode::ExtrusionOrder::OverridenExtrusions; + const OverridenExtrusions &overriden_extrusions{extruder_extrusions.overriden_extrusions[i]}; + if (is_empty(overriden_extrusions.slices_extrusions)) { + continue; + } + this->initialize_instance(instance, layers[instance.object_layer_to_print_id]); + gcode += this->extrude_slices( + instance, layers[instance.object_layer_to_print_id], + overriden_extrusions.slices_extrusions + ); + } + if (gcode_size_old < gcode.size()) { gcode+="; PURGING FINISHED\n"; + } } + // Extrude normal extrusions. - for (const InstanceToPrint &instance : instances_to_print) - this->process_layer_single_object( - gcode, extruder_id, instance, - layers[instance.object_layer_to_print_id], layer_tools, smooth_path_caches.layer_local(), - is_anything_overridden, false /* print_wipe_extrusions */); - } + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + using GCode::ExtrusionOrder::SupportPath; + const std::vector &support_extrusions{extruder_extrusions.normal_extrusions[i].support_extrusions}; + const ObjectLayerToPrint &layer_to_print{layers[instance.object_layer_to_print_id]}; + const std::vector &slices_extrusions{extruder_extrusions.normal_extrusions[i].slices_extrusions}; + if (support_extrusions.empty() && is_empty(slices_extrusions)) { + continue; + } + this->initialize_instance(instance, layers[instance.object_layer_to_print_id]); - // 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 - && this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) - && this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) > 0 - && this->m_config.travel_slope.get_at(*m_layer_change_extruder_id) < 90 - ); - if (first_layer) { - layer_change_gcode = ""; // Explicit for readability. - } else if (do_ramping_layer_change) { - const Vec3d &from{*m_previous_layer_last_position}; - const Vec3d &to{*m_current_layer_first_position}; - layer_change_gcode = this->get_ramping_layer_change_gcode(from, to, *m_layer_change_extruder_id); - } else { - layer_change_gcode = this->writer().get_travel_to_z_gcode(print_z, "simple layer change"); - } + if (!support_extrusions.empty()) { + m_layer = layer_to_print.support_layer; + m_object_layer_over_raft = false; + gcode += this->extrude_support(support_extrusions); + } - const auto keep_retraciton{[&](){ - if (!do_ramping_layer_change) { - return true; - } - const double travel_length{(*m_current_layer_first_position - *m_previous_layer_last_position_before_wipe).norm()}; - if (this->m_config.retract_before_travel.get_at(*m_layer_change_extruder_id) < travel_length) { - // Travel is long, keep retraction. - return true; - } - return false; - }}; - - bool removed_retraction{false}; - if (this->m_config.travel_ramping_lift.get_at(*m_layer_change_extruder_id) && !result.spiral_vase_enable) { - const std::string retraction_start_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_Start); - const std::string retraction_end_tag = GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); - - if (keep_retraciton()) { - boost::algorithm::replace_first(gcode, retraction_start_tag, ""); - boost::algorithm::replace_first(gcode, retraction_end_tag, ""); - } else { - const std::size_t start{gcode.find(retraction_start_tag)}; - const std::size_t end_tag_start{gcode.find(retraction_end_tag)}; - const std::size_t end{end_tag_start + retraction_end_tag.size()}; - gcode.replace(start, end - start, ""); - - layer_change_gcode = this->get_ramping_layer_change_gcode(*m_previous_layer_last_position_before_wipe, *m_current_layer_first_position, *m_layer_change_extruder_id); - - removed_retraction = true; + gcode += this->extrude_slices( + instance, layer_to_print, slices_extrusions + ); } + this->set_origin(0.0, 0.0); } - if (removed_retraction) { - const std::size_t start{gcode.find("FIRST_UNRETRACT")}; - const std::size_t end{gcode.find("\n", start)}; - gcode.replace(start, end - start, ""); - } else { - boost::algorithm::replace_first(gcode,"FIRST_UNRETRACT", ""); - } - - boost::algorithm::replace_first(gcode, tag, layer_change_gcode); BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); @@ -2623,228 +2691,57 @@ LayerResult GCodeGenerator::process_layer( } static const auto comment_perimeter = "perimeter"sv; -// Comparing string_view pointer & length for speed. -static inline bool comment_is_perimeter(const std::string_view comment) { - return comment.data() == comment_perimeter.data() && comment.size() == comment_perimeter.size(); -} - -void GCodeGenerator::process_layer_single_object( - // output - std::string &gcode, - // Index of the extruder currently active. - const unsigned int extruder_id, - // What object and instance is going to be printed. - const InstanceToPrint &print_instance, - // and the object & support layer of the above. - const ObjectLayerToPrint &layer_to_print, - // Container for extruder overrides (when wiping into object or infill). - const LayerTools &layer_tools, - // Optional smooth path interpolating extrusion polylines. - const GCode::SmoothPathCache &smooth_path_cache, - // Is any extrusion possibly marked as wiping extrusion? - const bool is_anything_overridden, - // Round 1 (wiping into object or infill) or round 2 (normal extrusions). - const bool print_wipe_extrusions) -{ - bool first = true; - // Delay layer initialization as many layers may not print with all extruders. - auto init_layer_delayed = [this, &print_instance, &layer_to_print, &first]() { - if (first) { - first = false; - const PrintObject &print_object = print_instance.print_object; - const Print &print = *print_object.print(); - m_config.apply(print_object.config(), true); - m_layer = layer_to_print.layer(); - if (print.config().avoid_crossing_perimeters) - 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; - 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 = true; - } - m_current_instance = next_instance; - this->set_origin(unscale(offset)); - m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]); - } - }; +void GCodeGenerator::initialize_instance( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print +) { const PrintObject &print_object = print_instance.print_object; const Print &print = *print_object.print(); - if (! print_wipe_extrusions && layer_to_print.support_layer != nullptr) - if (const SupportLayer &support_layer = *layer_to_print.support_layer; ! support_layer.support_fills.entities.empty()) { - ExtrusionRole role = support_layer.support_fills.role(); - bool has_support = role.is_mixed() || role.is_support_base(); - bool has_interface = role.is_mixed() || role.is_support_interface(); - // Extruder ID of the support base. -1 if "don't care". - unsigned int support_extruder = print_object.config().support_material_extruder.value - 1; - // Shall the support be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool support_dontcare = support_extruder == std::numeric_limits::max(); - // Extruder ID of the support interface. -1 if "don't care". - unsigned int interface_extruder = print_object.config().support_material_interface_extruder.value - 1; - // Shall the support interface be printed with the active extruder, preferably with non-soluble, to avoid tool changes? - bool interface_dontcare = interface_extruder == std::numeric_limits::max(); - if (support_dontcare || interface_dontcare) { - // Some support will be printed with "don't care" material, preferably non-soluble. - // Is the current extruder assigned a soluble filament? - auto it_nonsoluble = std::find_if(layer_tools.extruders.begin(), layer_tools.extruders.end(), - [&soluble = std::as_const(print.config().filament_soluble)](unsigned int extruder_id) { return ! soluble.get_at(extruder_id); }); - // There should be a non-soluble extruder available. - assert(it_nonsoluble != layer_tools.extruders.end()); - unsigned int dontcare_extruder = it_nonsoluble == layer_tools.extruders.end() ? layer_tools.extruders.front() : *it_nonsoluble; - if (support_dontcare) - support_extruder = dontcare_extruder; - if (interface_dontcare) - interface_extruder = dontcare_extruder; - } - bool extrude_support = has_support && support_extruder == extruder_id; - bool extrude_interface = has_interface && interface_extruder == extruder_id; - if (extrude_support || extrude_interface) { - init_layer_delayed(); - m_layer = layer_to_print.support_layer; - m_object_layer_over_raft = false; - ExtrusionEntitiesPtr entities_cache; - const ExtrusionEntitiesPtr &entities = extrude_support && extrude_interface ? support_layer.support_fills.entities : entities_cache; - if (! extrude_support || ! extrude_interface) { - auto role = extrude_support ? ExtrusionRole::SupportMaterial : ExtrusionRole::SupportMaterialInterface; - entities_cache.reserve(support_layer.support_fills.entities.size()); - for (ExtrusionEntity *ee : support_layer.support_fills.entities) - if (ee->role() == role) - entities_cache.emplace_back(ee); - } - gcode += this->extrude_support(chain_extrusion_references(entities), smooth_path_cache); - } - } + m_config.apply(print_object.config(), true); + m_layer = layer_to_print.layer(); + if (print.config().avoid_crossing_perimeters) + 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; + 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 = true; + } + m_current_instance = next_instance; + this->set_origin(unscale(offset)); + m_label_objects.update(&print_instance.print_object.instances()[print_instance.instance_id]); +} + +std::string GCodeGenerator::extrude_slices( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print, + const std::vector &slices_extrusions +) { + const PrintObject &print_object = print_instance.print_object; m_layer = layer_to_print.layer(); // To control print speed of the 1st object layer printed over raft interface. m_object_layer_over_raft = layer_to_print.object_layer && layer_to_print.object_layer->id() > 0 && print_object.slicing_parameters().raft_layers() == layer_to_print.object_layer->id(); - // Check whether this ExtrusionEntityCollection should be printed now with extruder_id, given print_wipe_extrusions - // (wipe extrusions are printed before regular extrusions). - auto shall_print_this_extrusion_collection = [extruder_id, instance_id = print_instance.instance_id, &layer_tools, is_anything_overridden, print_wipe_extrusions](const ExtrusionEntityCollection *eec, const PrintRegion ®ion) -> bool { - assert(eec != nullptr); - if (eec->entities.empty()) - // This shouldn't happen. FIXME why? but first_point() would fail. - return false; - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = layer_tools.extruder(*eec, region); - if (! layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - int extruder_override_id = is_anything_overridden ? layer_tools.wiping_extrusions().get_extruder_override(eec, instance_id) : -1; - return print_wipe_extrusions ? - extruder_override_id == int(extruder_id) : - extruder_override_id < 0 && int(extruder_id) == correct_extruder_id; - }; - - ExtrusionEntitiesPtr temp_fill_extrusions; - if (const Layer *layer = layer_to_print.object_layer; layer) - for (size_t idx : layer->lslice_indices_sorted_by_print_order) { - const LayerSlice &lslice = layer->lslices_ex[idx]; - auto extrude_infill_range = [&]( - const LayerRegion &layerm, const ExtrusionEntityCollection &fills, - LayerExtrusionRanges::const_iterator it_fill_ranges_begin, LayerExtrusionRanges::const_iterator it_fill_ranges_end, bool ironing) { - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); - temp_fill_extrusions.clear(); - for (auto it_fill_range = it_fill_ranges_begin; it_fill_range != it_fill_ranges_end; ++ it_fill_range) { - assert(it_fill_range->region() == it_fill_ranges_begin->region()); - for (uint32_t fill_id : *it_fill_range) { - assert(dynamic_cast(fills.entities[fill_id])); - if (auto *eec = static_cast(fills.entities[fill_id]); - (eec->role() == ExtrusionRole::Ironing) == ironing && shall_print_this_extrusion_collection(eec, region)) { - if (eec->can_reverse()) - // Flatten the infill collection for better path planning. - for (auto *ee : eec->entities) - temp_fill_extrusions.emplace_back(ee); - else - temp_fill_extrusions.emplace_back(eec); - } - } - } - if (! temp_fill_extrusions.empty()) { - init_layer_delayed(); - m_config.apply(region.config()); - const auto extrusion_name = ironing ? "ironing"sv : "infill"sv; - 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, 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); - } - }; - - //FIXME order islands? - // Sequential tool path ordering of multiple parts within the same object, aka. perimeter tracking (#5511) - for (const LayerIsland &island : lslice.islands) { - auto process_perimeters = [&]() { - const LayerRegion &layerm = *layer->get_region(island.perimeters.region()); - // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not - // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. - const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); - bool first = true; - for (uint32_t perimeter_id : island.perimeters) { - // Extrusions inside islands are expected to be ordered already. - // Don't reorder them. - assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); - if (const auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); - shall_print_this_extrusion_collection(eec, region)) { - // This may not apply to Arachne, but maybe the Arachne gap fill should disable reverse as well? - // assert(! eec->can_reverse()); - if (first) { - first = false; - init_layer_delayed(); - m_config.apply(region.config()); - } - for (const ExtrusionEntity *ee : *eec) { - // Don't reorder, don't flip. - 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); - } - } - } - }; - auto process_infill = [&]() { - for (auto it = island.fills.begin(); it != island.fills.end();) { - // Gather range of fill ranges with the same region. - auto it_end = it; - for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; - const LayerRegion &layerm = *layer->get_region(it->region()); - extrude_infill_range(layerm, layerm.fills(), it, it_end, false /* normal extrusions, not ironing */); - it = it_end; - } - }; - if (print.config().infill_first) { - process_infill(); - process_perimeters(); - } else { - process_perimeters(); - process_infill(); - } - } - // ironing - //FIXME move ironing into the loop above over LayerIslands? - // First Ironing changes extrusion rate quickly, second single ironing may be done over multiple perimeter regions. - // Ironing in a second phase is safer, but it may be less efficient. - for (const LayerIsland &island : lslice.islands) { - for (auto it = island.fills.begin(); it != island.fills.end();) { - // Gather range of fill ranges with the same region. - auto it_end = it; - for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; - const LayerRegion &layerm = *layer->get_region(it->region()); - extrude_infill_range(layerm, layerm.fills(), it, it_end, true /* ironing, not normal extrusions */); - it = it_end; - } + std::string gcode; + for (const SliceExtrusions &slice_extrusions : slices_extrusions) { + for (const IslandExtrusions &island_extrusions : slice_extrusions.common_extrusions) { + if (island_extrusions.infill_first) { + gcode += this->extrude_infill_ranges(island_extrusions.infill_ranges, "infill"); + gcode += this->extrude_perimeters(*island_extrusions.region, island_extrusions.perimeters, print_instance); + } else { + gcode += this->extrude_perimeters(*island_extrusions.region, island_extrusions.perimeters, print_instance); + gcode += this->extrude_infill_ranges(island_extrusions.infill_ranges, "infill"); } } + + gcode += this->extrude_infill_ranges(slice_extrusions.ironing_extrusions, "ironing"); + } + + return gcode; } void GCodeGenerator::apply_print_config(const PrintConfig &print_config) @@ -2921,7 +2818,9 @@ std::string GCodeGenerator::preamble() std::string GCodeGenerator::change_layer( coordf_t previous_layer_z, coordf_t print_z, - bool vase_mode + bool vase_mode, + const Point &first_point, + const bool first_layer ) { std::string gcode; if (m_layer_count > 0) @@ -2932,79 +2831,49 @@ std::string GCodeGenerator::change_layer( gcode += m_label_objects.maybe_change_instance(m_writer); } - if (!EXTRUDER_CONFIG(travel_ramping_lift) && EXTRUDER_CONFIG(retract_layer_change)) { - gcode += this->retract_and_wipe(); - } else if (EXTRUDER_CONFIG(travel_ramping_lift) && !vase_mode){ - m_previous_layer_last_position_before_wipe = 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_Retraction_Start); - gcode += this->retract_and_wipe(false, false); - gcode += GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Layer_Change_Retraction_End); - gcode += m_writer.reset_e(); + const unsigned extruder_id{m_writer.extruder()->id()}; + const bool do_ramping_layer_change = ( + this->last_position + && !vase_mode + && print_z > previous_layer_z + && this->m_config.travel_ramping_lift.get_at(extruder_id) + && this->m_config.travel_slope.get_at(extruder_id) > 0 + && this->m_config.travel_slope.get_at(extruder_id) < 90 + ); + if (do_ramping_layer_change) { + Vec3d from{to_3d(this->point_to_gcode(*this->last_position), previous_layer_z)}; + const Vec3d to{to_3d(unscaled(first_point), print_z)}; + const double travel_length{(to - from).norm()}; + + if (this->m_config.retract_before_travel.get_at(extruder_id) < travel_length) { + gcode += this->retract_and_wipe(); + } + + // Update from after wipe. + from = to_3d(this->point_to_gcode(*this->last_position), previous_layer_z); + + gcode += this->get_ramping_layer_change_gcode(from, to, extruder_id); + + this->writer().update_position(to); + this->last_position = this->gcode_to_point(unscaled(first_point)); + } else { + if (EXTRUDER_CONFIG(retract_layer_change)) { + gcode += this->retract_and_wipe(); + } + if (!first_layer) { + gcode += this->writer().get_travel_to_z_gcode(print_z, "simple layer change"); + } } - Vec3d new_position = this->writer().get_position(); - new_position.z() = print_z; - this->writer().update_position(new_position); - - 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); - 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(); return gcode; } -#ifndef NDEBUG -static inline bool validate_smooth_path(const GCode::SmoothPath &smooth_path, bool loop) -{ - for (auto it = std::next(smooth_path.begin()); it != smooth_path.end(); ++ it) { - assert(it->path.size() >= 2); - assert(std::prev(it)->path.back().point == it->path.front().point); - } - assert(! loop || smooth_path.front().path.front().point == smooth_path.back().path.back().point); - return true; -} -#endif //NDEBUG - -static constexpr const double min_gcode_segment_length = 0.002; - -std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) -{ - // Extrude all loops CCW unless CW movements are prefered. - const bool is_hole = loop_src.is_clockwise(); - const bool reverse_loop = m_config.prefer_clockwise_movements ? !is_hole : is_hole; - - Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); - if (!m_config.spiral_vase && comment_is_perimeter(description)) { - assert(m_layer != nullptr); - seam_point = m_seam_placer.place_seam(m_layer, loop_src, seam_point); - } - // 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. - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(loop_src, reverse_loop, m_scaled_resolution, seam_point, 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 - // we discard it in that case. - if (m_enable_loop_clipping) - clip_end(smooth_path, scaled(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled(min_gcode_segment_length)); - - if (smooth_path.empty()) - return {}; - - assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping)); - - // Apply the small perimeter speed. - if (loop_src.paths.front().role().is_perimeter() && loop_src.length() <= SMALL_PERIMETER_LENGTH && speed == -1) - speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); - +std::string GCodeGenerator::extrude_smooth_path( + const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed +) { // Extrude along the smooth path. std::string gcode; for (const GCode::SmoothPathElement &el : smooth_path) @@ -3013,140 +2882,95 @@ std::string GCodeGenerator::extrude_loop(const ExtrusionLoop &loop_src, const GC // reset acceleration gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); - if (m_wipe.enabled()) { - // Wipe will hide the seam. - m_wipe.set_path(std::move(smooth_path)); - } else if (loop_src.paths.back().role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { - // Only wipe inside if the wipe along the perimeter is disabled. - // Make a little move inwards before leaving loop. - if (std::optional pt = wipe_hide_seam(smooth_path, reverse_loop, 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->last_position = *pt; - } + if (is_loop) { + m_wipe.set_path(GCode::SmoothPath{smooth_path}); + } else { + GCode::SmoothPath reversed_smooth_path{smooth_path}; + GCode::reverse(reversed_smooth_path); + m_wipe.set_path(std::move(reversed_smooth_path)); } - return gcode; } std::string GCodeGenerator::extrude_skirt( - const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override, - const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) + GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override) { - assert(loop_src.is_counter_clockwise()); - const bool reverse_loop = m_config.prefer_clockwise_movements; - - Point seam_point = this->last_position.has_value() ? *this->last_position : Point::Zero(); - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit_split_with_seam(loop_src, reverse_loop, m_scaled_resolution, seam_point, 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 - // we discard it in that case. - if (m_enable_loop_clipping) - clip_end(smooth_path, scale_(EXTRUDER_CONFIG(nozzle_diameter)) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER, scaled(min_gcode_segment_length)); - - if (smooth_path.empty()) - return {}; - - assert(validate_smooth_path(smooth_path, ! m_enable_loop_clipping)); - // Extrude along the smooth path. std::string gcode; for (GCode::SmoothPathElement &el : smooth_path) { // Override extrusion parameters. el.path_attributes.mm3_per_mm = extrusion_flow_override.mm3_per_mm; el.path_attributes.height = extrusion_flow_override.height; - gcode += this->_extrude(el.path_attributes, el.path, description, speed); } - // reset acceleration - gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); - - if (m_wipe.enabled()) - // Wipe will hide the seam. - m_wipe.set_path(std::move(smooth_path)); + gcode += this->extrude_smooth_path(smooth_path, true, "skirt"sv, m_config.support_material_speed.value); return gcode; } -std::string GCodeGenerator::extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) -{ -#ifndef NDEBUG - for (auto it = std::next(multipath.paths.begin()); it != multipath.paths.end(); ++ it) { - assert(it->polyline.points.size() >= 2); - assert(std::prev(it)->polyline.last_point() == it->polyline.first_point()); +std::string GCodeGenerator::extrude_infill_ranges( + const std::vector &infill_ranges, + const std::string &comment +) { + std::string gcode{}; + for (const InfillRange &infill_range : infill_ranges) { + if (!infill_range.items.empty()) { + this->m_config.apply(infill_range.region->config()); + for (const GCode::SmoothPath &path : infill_range.items) { + gcode += this->extrude_smooth_path(path, false, comment, -1.0); + } + } } -#endif // NDEBUG - GCode::SmoothPath smooth_path = smooth_path_cache.resolve_or_fit(multipath, reverse, m_scaled_resolution); - - // extrude along the path - std::string gcode; - for (GCode::SmoothPathElement &el : smooth_path) - gcode += this->_extrude(el.path_attributes, el.path, description, speed); - - GCode::reverse(smooth_path); - m_wipe.set_path(std::move(smooth_path)); - - // reset acceleration - gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); return gcode; } -std::string GCodeGenerator::extrude_entity(const ExtrusionEntityReference &entity, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed) -{ - if (const ExtrusionPath *path = dynamic_cast(&entity.extrusion_entity())) - return this->extrude_path(*path, entity.flipped(), smooth_path_cache, description, speed); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(&entity.extrusion_entity())) - return this->extrude_multi_path(*multipath, entity.flipped(), smooth_path_cache, description, speed); - else if (const ExtrusionLoop *loop = dynamic_cast(&entity.extrusion_entity())) - return this->extrude_loop(*loop, smooth_path_cache, description, speed); - else - throw Slic3r::InvalidArgument("Invalid argument supplied to extrude()"); - return {}; -} +std::string GCodeGenerator::extrude_perimeters( + const PrintRegion ®ion, + const std::vector &perimeters, + const InstanceToPrint &print_instance +) { + if (!perimeters.empty()) { + m_config.apply(region.config()); + } -std::string GCodeGenerator::extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, std::string_view description, double speed) -{ - Geometry::ArcWelder::Path smooth_path = smooth_path_cache.resolve_or_fit(path, reverse, m_scaled_resolution); - std::string gcode = this->_extrude(path.attributes(), smooth_path, description, speed); - Geometry::ArcWelder::reverse(smooth_path); - m_wipe.set_path(std::move(smooth_path)); - // reset acceleration - gcode += m_writer.set_print_acceleration((unsigned int)floor(m_config.default_acceleration.value + 0.5)); + std::string gcode{}; + + for (const GCode::ExtrusionOrder::Perimeter &perimeter : perimeters) { + double speed{-1}; + // Apply the small perimeter speed. + if (perimeter.extrusion_entity->length() <= SMALL_PERIMETER_LENGTH) + speed = m_config.small_perimeter_speed.get_abs_value(m_config.perimeter_speed); + gcode += this->extrude_smooth_path(perimeter.smooth_path, true, comment_perimeter, speed); + this->m_travel_obstacle_tracker.mark_extruded( + perimeter.extrusion_entity, print_instance.object_layer_to_print_id, print_instance.instance_id + ); + + if (!m_wipe.enabled() && perimeter.extrusion_entity->role().is_external_perimeter() && m_layer != nullptr && m_config.perimeters.value > 1) { + // Only wipe inside if the wipe along the perimeter is disabled. + // Make a little move inwards before leaving loop. + if (std::optional pt = wipe_hide_seam(perimeter.smooth_path, perimeter.reversed, 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->last_position = *pt; + } + } + } return gcode; -} +}; -std::string GCodeGenerator::extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache) +std::string GCodeGenerator::extrude_support(const std::vector &support_extrusions) { static constexpr const auto support_label = "support material"sv; static constexpr const auto support_interface_label = "support material interface"sv; std::string gcode; - if (! support_fills.empty()) { + if (! support_extrusions.empty()) { const double support_speed = m_config.support_material_speed.value; const double support_interface_speed = m_config.support_material_interface_speed.get_abs_value(support_speed); - for (const ExtrusionEntityReference &eref : support_fills) { - ExtrusionRole role = eref.extrusion_entity().role(); - assert(role == ExtrusionRole::SupportMaterial || role == ExtrusionRole::SupportMaterialInterface); - const auto label = (role == ExtrusionRole::SupportMaterial) ? support_label : support_interface_label; - const double speed = (role == ExtrusionRole::SupportMaterial) ? support_speed : support_interface_speed; - const ExtrusionPath *path = dynamic_cast(&eref.extrusion_entity()); - if (path) - gcode += this->extrude_path(*path, eref.flipped(), smooth_path_cache, label, speed); - else if (const ExtrusionMultiPath *multipath = dynamic_cast(&eref.extrusion_entity()); multipath) - gcode += this->extrude_multi_path(*multipath, eref.flipped(), smooth_path_cache, label, speed); - else { - const ExtrusionEntityCollection *eec = dynamic_cast(&eref.extrusion_entity()); - assert(eec); - if (eec) { - //FIXME maybe order the support here? - ExtrusionEntityReferences refs; - refs.reserve(eec->entities.size()); - std::transform(eec->entities.begin(), eec->entities.end(), std::back_inserter(refs), - [flipped = eref.flipped()](const ExtrusionEntity *ee) { return ExtrusionEntityReference{ *ee, flipped }; }); - gcode += this->extrude_support(refs, smooth_path_cache); - } - } + for (const GCode::ExtrusionOrder::SupportPath &path : support_extrusions) { + const auto label = path.is_interface ? support_interface_label : support_label; + const double speed = path.is_interface ? support_interface_speed : support_speed; + gcode += this->extrude_smooth_path(path.path, false, label, speed); } } return gcode; @@ -3232,10 +3056,6 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const *this->last_position, point.head<2>(), role, "travel to first layer point", insert_gcode ); } else { - this->m_layer_change_used_external_mp = this->m_avoid_crossing_perimeters.use_external_mp_once; - this->m_layer_change_layer = this->layer(); - this->m_layer_change_origin = this->origin(); - double lift{ EXTRUDER_CONFIG(travel_ramping_lift) ? EXTRUDER_CONFIG(travel_max_lift) : EXTRUDER_CONFIG(retract_lift)}; @@ -3264,8 +3084,6 @@ std::string GCodeGenerator::travel_to_first_position(const Vec3crd& point, const this->writer().update_position(gcode_point); } - m_current_layer_first_position = gcode_point; - return gcode; } @@ -3307,42 +3125,26 @@ std::string GCodeGenerator::_extrude( gcode += m_label_objects.maybe_change_instance(m_writer); } - if (!m_current_layer_first_position) { - const Vec3crd point = to_3d(path.front().point, scaled(this->m_last_layer_z)); - gcode += this->travel_to_first_position(point, unscaled(point.z()), path_attr.role, [&](){ + if (!this->last_position) { + const double z = this->m_last_layer_z; + const std::string comment{"move to print after unknown position"}; + gcode += this->retract_and_wipe(); + gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); + 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, [this](){ return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); - }); - } else { - // go to first point of extrusion path - if (!this->last_position) { - const double z = this->m_last_layer_z; - const std::string comment{"move to print after unknown position"}; - gcode += this->retract_and_wipe(); - gcode += m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); - 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, [&](){ - return m_writer.multiple_extruders ? "" : m_label_objects.maybe_change_instance(m_writer); - })}; - gcode += travel_gcode; - } + })}; + gcode += travel_gcode; } // compensate retraction - if (this->m_already_unretracted) { - gcode += this->unretract(); - } else { - this->m_already_unretracted = true; - gcode += "FIRST_UNRETRACT" + this->unretract(); - - //First unretract may or may not be removed thus we must start from E0. - gcode += this->writer().reset_e(); - } + gcode += this->unretract(); if (m_writer.multiple_extruders && !has_active_instance) { gcode += m_label_objects.maybe_change_instance(m_writer); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 259203c6fe..b35bd567e0 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -15,6 +15,7 @@ #ifndef slic3r_GCode_hpp_ #define slic3r_GCode_hpp_ +#include "libslic3r/GCode/ExtrusionOrder.hpp" #include "libslic3r/GCode/ExtrusionProcessor.hpp" #include "JumpPointSearch.hpp" #include "libslic3r.h" @@ -92,18 +93,6 @@ struct LayerResult { }; 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; @@ -178,11 +167,16 @@ public: static void encode_full_config(const Print& print, std::vector>& config); using ObjectLayerToPrint = GCode::ObjectLayerToPrint; - using ObjectsLayerToPrint = std::vector; + using ObjectsLayerToPrint = GCode::ObjectsLayerToPrint; std::optional last_position; private: + using InstanceToPrint = GCode::InstanceToPrint; + using InfillRange = GCode::ExtrusionOrder::InfillRange; + using SliceExtrusions = GCode::ExtrusionOrder::SliceExtrusions; + using IslandExtrusions = GCode::ExtrusionOrder::IslandExtrusions; + class GCodeOutputStream { public: GCodeOutputStream(FILE *f, GCodeProcessor &processor) : f(f), m_processor(processor) {} @@ -197,7 +191,7 @@ private: bool is_open() const { return f; } bool is_error() const; - + void flush(); void close(); @@ -237,6 +231,15 @@ private: const GCode::Impl::Travels::ElevatedTravelParams &elevation_params ) const; + std::vector get_sorted_extrusions( + const Print &print, + const ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const GCode::SmoothPathCaches &smooth_path_caches, + const bool first_layer + ); + LayerResult process_layer( const Print &print, // Set of object & print layers of the same PrintObject and with the same print_z. @@ -249,6 +252,7 @@ private: // If set to size_t(-1), then print all copies of all objects. // Otherwise print a single copy of a single object. const size_t single_object_idx = size_t(-1)); + // Process all layers of all objects (non-sequential mode) with a parallel pipeline: // Generate G-code, run the filters (vase mode, cooling buffer), run the G-code analyser // and export G-code into file. @@ -275,27 +279,14 @@ private: std::string change_layer( coordf_t previous_layer_z, coordf_t print_z, - bool vase_mode + bool vase_mode, + const Point &first_point, + const bool first_layer ); - 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.); - std::string extrude_skirt(const ExtrusionLoop &loop_src, const ExtrusionFlow &extrusion_flow_override, - const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed); - - std::string extrude_multi_path(const ExtrusionMultiPath &multipath, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); - std::string extrude_path(const ExtrusionPath &path, bool reverse, const GCode::SmoothPathCache &smooth_path_cache, const std::string_view description, double speed = -1.); - - struct InstanceToPrint - { - InstanceToPrint(size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id) : - object_layer_to_print_id(object_layer_to_print_id), print_object(print_object), instance_id(instance_id) {} - - // Index into std::vector, which contains Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. - const size_t object_layer_to_print_id; - const PrintObject &print_object; - // Instance idx of the copy of a print object. - const size_t instance_id; - }; + std::string extrude_smooth_path( + const GCode::SmoothPath &smooth_path, const bool is_loop, const std::string_view description, const double speed + ); + std::string extrude_skirt(GCode::SmoothPath smooth_path, const ExtrusionFlow &extrusion_flow_override); std::vector sort_print_object_instances( // Object and Support layers for the current print_z, collected for a single object, or for possibly multiple objects with multiple instances. @@ -305,26 +296,32 @@ private: // For sequential print, the instance of the object to be printing has to be defined. const size_t single_object_instance_idx); - // This function will be called for each printing extruder, possibly twice: First for wiping extrusions, second for normal extrusions. - void process_layer_single_object( - // output - std::string &gcode, - // Index of the extruder currently active. - const unsigned int extruder_id, - // What object and instance is going to be printed. - const InstanceToPrint &print_instance, - // and the object & support layer of the above. - const ObjectLayerToPrint &layer_to_print, - // Container for extruder overrides (when wiping into object or infill). - const LayerTools &layer_tools, - // Optional smooth path interpolating extrusion polylines. - const GCode::SmoothPathCache &smooth_path_cache, - // Is any extrusion possibly marked as wiping extrusion? - const bool is_anything_overridden, - // Round 1 (wiping into object or infill) or round 2 (normal extrusions). - const bool print_wipe_extrusions); + std::string extrude_perimeters( + const PrintRegion ®ion, + const std::vector &perimeters, + const InstanceToPrint &print_instance + ); + + std::string extrude_infill_ranges( + const std::vector &infill_ranges, + const std::string &commment + ); + + void initialize_instance( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print + ); + + std::string extrude_slices( + const InstanceToPrint &print_instance, + const ObjectLayerToPrint &layer_to_print, + const std::vector &slices_extrusions + ); + + std::string extrude_support( + const std::vector &support_extrusions + ); - std::string extrude_support(const ExtrusionEntityReferences &support_fills, const GCode::SmoothPathCache &smooth_path_cache); std::string generate_travel_gcode( const Points3& travel, const std::string& comment, @@ -433,12 +430,6 @@ private: std::optional m_previous_layer_last_position; std::optional m_previous_layer_last_position_before_wipe; // This needs to be populated during the layer processing! - std::optional m_current_layer_first_position; - std::optional m_layer_change_extruder_id; - bool m_layer_change_used_external_mp{false}; - const Layer* m_layer_change_layer{nullptr}; - std::optional m_layer_change_origin; - bool m_already_unretracted{false}; std::unique_ptr m_cooling_buffer; std::unique_ptr m_spiral_vase; std::unique_ptr m_find_replace; diff --git a/src/libslic3r/GCode/ExtrusionOrder.cpp b/src/libslic3r/GCode/ExtrusionOrder.cpp new file mode 100644 index 0000000000..45724cced5 --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionOrder.cpp @@ -0,0 +1,739 @@ +#include "ExtrusionOrder.hpp" +#include "GCode/SmoothPath.hpp" +#include "libslic3r/ShortestPath.hpp" + +namespace Slic3r::GCode::ExtrusionOrder { + +bool is_overriden(const ExtrusionEntityCollection &eec, const LayerTools &layer_tools, const std::size_t instance_id) { + return layer_tools.wiping_extrusions().get_extruder_override(&eec, instance_id) > -1; +} + +int get_extruder_id( + const ExtrusionEntityCollection &eec, + const LayerTools &layer_tools, + const PrintRegion ®ion, + const std::size_t instance_id +) { + if (is_overriden(eec, layer_tools, instance_id)) { + return layer_tools.wiping_extrusions().get_extruder_override(&eec, instance_id); + } + + const int extruder_id = layer_tools.extruder(eec, region); + if (! layer_tools.has_extruder(extruder_id)) { + // Extruder is not in layer_tools - we'll print it by last extruder on this layer (could + // happen e.g. when a wiping object is taller than others - dontcare extruders are + // eradicated from layer_tools) + return layer_tools.extruders.back(); + } + return extruder_id; +} + +Point get_gcode_point(const InstancePoint &point, const Point &offset) { + return point.local_point + offset; +} + +InstancePoint get_instance_point(const Point &point, const Point &offset) { + return {point - offset}; +} + +std::optional get_gcode_point(const std::optional &point, const Point &offset) { + if (point) { + return get_gcode_point(*point, offset); + } + return std::nullopt; +} + +std::optional get_instance_point(const std::optional &point, const Point &offset) { + if (point) { + return get_instance_point(*point, offset); + } + return std::nullopt; +} + +using ExtractEntityPredicate = std::function; + +ExtrusionEntitiesPtr extract_infill_extrusions( + const PrintRegion ®ion, + const ExtrusionEntityCollection &fills, + const LayerExtrusionRanges::const_iterator& begin, + const LayerExtrusionRanges::const_iterator& end, + const ExtractEntityPredicate &should_pick_extrusion +) { + ExtrusionEntitiesPtr result; + for (auto it = begin; it != end; ++ it) { + assert(it->region() == begin->region()); + const LayerExtrusionRange &range{*it}; + for (uint32_t fill_id : range) { + assert(dynamic_cast(fills.entities[fill_id])); + + auto *eec{static_cast(fills.entities[fill_id])}; + if (eec == nullptr || eec->empty() || !should_pick_extrusion(*eec, region)) { + continue; + } + + if (eec->can_reverse()) { + // Flatten the infill collection for better path planning. + for (auto *ee : eec->entities) { + result.emplace_back(ee); + } + } else { + result.emplace_back(eec); + } + } + } + return result; +} + +std::vector extract_perimeter_extrusions( + const Print &print, + const Layer &layer, + const LayerIsland &island, + const ExtractEntityPredicate &should_pick_extrusion, + const unsigned extruder_id, + const Point &offset, + std::optional &previous_position, + const PathSmoothingFunction &smooth_path +) { + std::vector result; + + const LayerRegion &layerm = *layer.get_region(island.perimeters.region()); + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + + for (uint32_t perimeter_id : island.perimeters) { + // Extrusions inside islands are expected to be ordered already. + // Don't reorder them. + assert(dynamic_cast(layerm.perimeters().entities[perimeter_id])); + auto *eec = static_cast(layerm.perimeters().entities[perimeter_id]); + if (eec == nullptr || eec->empty() || !should_pick_extrusion(*eec, region)) { + continue; + } + + for (ExtrusionEntity *ee : *eec) { + if (ee != nullptr) { + std::optional last_position{get_instance_point(previous_position, offset)}; + bool reverse_loop{false}; + if (auto loop = dynamic_cast(ee)) { + const bool is_hole = loop->is_clockwise(); + reverse_loop = print.config().prefer_clockwise_movements ? !is_hole : is_hole; + } + SmoothPath path{smooth_path(&layer, ExtrusionEntityReference{*ee, reverse_loop}, extruder_id, last_position)}; + previous_position = get_gcode_point(last_position, offset); + if (!path.empty()) { + result.push_back(Perimeter{std::move(path), reverse_loop, ee}); + } + } + } + } + + return result; +} + +std::vector sort_fill_extrusions(const ExtrusionEntitiesPtr &fills, const Point* start_near) { + if (fills.empty()) { + return {}; + } + std::vector sorted_extrusions; + + for (const ExtrusionEntityReference &fill : chain_extrusion_references(fills, start_near)) { + if (auto *eec = dynamic_cast(&fill.extrusion_entity()); eec) { + for (const ExtrusionEntityReference &ee : chain_extrusion_references(*eec, start_near, fill.flipped())) { + sorted_extrusions.push_back(ee); + } + } else { + sorted_extrusions.push_back(fill); + } + } + return sorted_extrusions; +} + +std::vector extract_infill_ranges( + const Print &print, + const Layer &layer, + const LayerIsland &island, + const Point &offset, + std::optional &previous_position, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const unsigned extruder_id +) { + std::vector result; + for (auto it = island.fills.begin(); it != island.fills.end();) { + // Gather range of fill ranges with the same region. + auto it_end = it; + for (++ it_end; it_end != island.fills.end() && it->region() == it_end->region(); ++ it_end) ; + const LayerRegion &layerm = *layer.get_region(it->region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be unique to a PrintObject, they would not + // identify the content of PrintRegion accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + + ExtrusionEntitiesPtr extrusions{extract_infill_extrusions( + region, + layerm.fills(), + it, + it_end, + should_pick_extrusion + )}; + + const std::optional previous_instance_point{get_instance_point(previous_position, offset)}; + const Point* start_near{previous_instance_point ? &(previous_instance_point->local_point) : nullptr}; + const ExtrusionEntityReferences sorted_extrusions{sort_fill_extrusions(extrusions, start_near)}; + + std::vector paths; + for (const ExtrusionEntityReference &extrusion_reference : sorted_extrusions) { + std::optional last_position{get_instance_point(previous_position, offset)}; + SmoothPath path{smooth_path(&layer, extrusion_reference, extruder_id, last_position)}; + if (!path.empty()) { + paths.push_back(std::move(path)); + } + previous_position = get_gcode_point(last_position, offset); + } + if (!paths.empty()) { + result.push_back({std::move(paths), ®ion}); + } + it = it_end; + } + return result; +} + +std::vector extract_island_extrusions( + const LayerSlice &lslice, + const Print &print, + const Layer &layer, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const Point &offset, + const unsigned extruder_id, + std::optional &previous_position +) { + std::vector result; + for (const LayerIsland &island : lslice.islands) { + const LayerRegion &layerm = *layer.get_region(island.perimeters.region()); + // PrintObjects own the PrintRegions, thus the pointer to PrintRegion would be + // unique to a PrintObject, they would not identify the content of PrintRegion + // accross the whole print uniquely. Translate to a Print specific PrintRegion. + const PrintRegion ®ion = print.get_print_region(layerm.region().print_region_id()); + + const auto should_pick_infill = [&should_pick_extrusion](const ExtrusionEntityCollection &eec, const PrintRegion ®ion) { + return should_pick_extrusion(eec, region) && eec.role() != ExtrusionRole::Ironing; + }; + + result.push_back(IslandExtrusions{®ion}); + IslandExtrusions &island_extrusions{result.back()}; + island_extrusions.infill_first = print.config().infill_first; + + if (print.config().infill_first) { + island_extrusions.infill_ranges = extract_infill_ranges( + print, layer, island, offset, previous_position, should_pick_infill, smooth_path, extruder_id + ); + + island_extrusions.perimeters = extract_perimeter_extrusions(print, layer, island, should_pick_extrusion, extruder_id, offset, previous_position, smooth_path); + } else { + island_extrusions.perimeters = extract_perimeter_extrusions(print, layer, island, should_pick_extrusion, extruder_id, offset, previous_position, smooth_path); + + island_extrusions.infill_ranges = {extract_infill_ranges( + print, layer, island, offset, previous_position, should_pick_infill, smooth_path, extruder_id + )}; + } + } + return result; +} + +std::vector extract_ironing_extrusions( + const LayerSlice &lslice, + const Print &print, + const Layer &layer, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const Point &offset, + const unsigned extruder_id, + std::optional &previous_position +) { + std::vector result; + + for (const LayerIsland &island : lslice.islands) { + const auto should_pick_ironing = [&should_pick_extrusion](const auto &eec, const auto ®ion) { + return should_pick_extrusion(eec, region) && eec.role() == ExtrusionRole::Ironing; + }; + + const std::vector ironing_ranges{extract_infill_ranges( + print, layer, island, offset, previous_position, should_pick_ironing, smooth_path, extruder_id + )}; + result.insert( + result.end(), ironing_ranges.begin(), ironing_ranges.end() + ); + } + return result; +} + +std::vector get_slices_extrusions( + const Print &print, + const Layer &layer, + const ExtractEntityPredicate &should_pick_extrusion, + const PathSmoothingFunction &smooth_path, + const Point &offset, + const unsigned extruder_id, + std::optional &previous_position +) { + // Note: ironing. + // FIXME move ironing into the loop above over LayerIslands? + // First Ironing changes extrusion rate quickly, second single ironing may be done over + // multiple perimeter regions. Ironing in a second phase is safer, but it may be less + // efficient. + + std::vector result; + + for (size_t idx : layer.lslice_indices_sorted_by_print_order) { + const LayerSlice &lslice = layer.lslices_ex[idx]; + std::vector island_extrusions{extract_island_extrusions( + lslice, print, layer, should_pick_extrusion, smooth_path, offset, extruder_id, previous_position + )}; + std::vector ironing_extrusions{extract_ironing_extrusions( + lslice, print, layer, should_pick_extrusion, smooth_path, offset, extruder_id, previous_position + )}; + if (!island_extrusions.empty() || !ironing_extrusions.empty()) { + result.emplace_back( + SliceExtrusions{std::move(island_extrusions), std::move(ironing_extrusions)} + ); + } + } + return result; +} + +unsigned translate_support_extruder( + const int configured_extruder, + const LayerTools &layer_tools, + const ConfigOptionBools &is_soluable +) { + if (configured_extruder <= 0) { + // Some support will be printed with "don't care" material, preferably non-soluble. + // Is the current extruder assigned a soluble filament? + auto it_nonsoluble = std::find_if(layer_tools.extruders.begin(), layer_tools.extruders.end(), + [&is_soluable](unsigned int extruder_id) { return ! is_soluable.get_at(extruder_id); }); + // There should be a non-soluble extruder available. + assert(it_nonsoluble != layer_tools.extruders.end()); + return it_nonsoluble == layer_tools.extruders.end() ? layer_tools.extruders.front() : *it_nonsoluble; + } else { + return configured_extruder - 1; + } +} + +std::vector get_support_extrusions( + const unsigned int extruder_id, + const GCode::ObjectLayerToPrint &layer_to_print, + unsigned int support_extruder, + unsigned int interface_extruder, + const PathSmoothingFunction &smooth_path, + std::optional &previous_position +) { + if (const SupportLayer &support_layer = *layer_to_print.support_layer; + !support_layer.support_fills.entities.empty()) { + ExtrusionRole role = support_layer.support_fills.role(); + bool has_support = role.is_mixed() || role.is_support_base(); + bool has_interface = role.is_mixed() || role.is_support_interface(); + + bool extrude_support = has_support && support_extruder == extruder_id; + bool extrude_interface = has_interface && interface_extruder == extruder_id; + + if (extrude_support || extrude_interface) { + ExtrusionEntitiesPtr entities_cache; + const ExtrusionEntitiesPtr &entities = extrude_support && extrude_interface ? + support_layer.support_fills.entities : + entities_cache; + if (!extrude_support || !extrude_interface) { + auto role = extrude_support ? ExtrusionRole::SupportMaterial : + ExtrusionRole::SupportMaterialInterface; + entities_cache.reserve(support_layer.support_fills.entities.size()); + for (ExtrusionEntity *ee : support_layer.support_fills.entities) + if (ee->role() == role) + entities_cache.emplace_back(ee); + } + std::vector paths; + for (const ExtrusionEntityReference &entity_reference : chain_extrusion_references(entities)) { + auto collection{dynamic_cast(&entity_reference.extrusion_entity())}; + const bool is_interface{entity_reference.extrusion_entity().role() != ExtrusionRole::SupportMaterial}; + if (collection != nullptr) { + for (const ExtrusionEntity * sub_entity : *collection) { + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, {*sub_entity, entity_reference.flipped()}, extruder_id, last_position)}; + if (!path.empty()) { + paths.push_back({std::move(path), is_interface}); + } + previous_position = get_gcode_point(last_position, {0, 0}); + } + } else { + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + if (!path.empty()) { + paths.push_back({std::move(path), is_interface}); + } + previous_position = get_gcode_point(last_position, {0, 0}); + } + } + return paths; + } + } + return {}; +} + +std::vector get_overriden_extrusions( + const Print &print, + const GCode::ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const unsigned int extruder_id, + const PathSmoothingFunction &smooth_path, + std::optional &previous_position +) { + std::vector result; + + for (const InstanceToPrint &instance : instances_to_print) { + if (const Layer *layer = layers[instance.object_layer_to_print_id].object_layer; layer) { + const auto should_pick_extrusion = [&layer_tools, &instance, &extruder_id](const ExtrusionEntityCollection &entity_collection, + const PrintRegion ®ion) { + if (!is_overriden(entity_collection, layer_tools, instance.instance_id)) { + return false; + } + + if (get_extruder_id( + entity_collection, layer_tools, region, instance.instance_id + ) != static_cast(extruder_id)) { + return false; + } + return true; + }; + + const PrintObject &print_object{instance.print_object}; + const Point &offset{print_object.instances()[instance.instance_id].shift}; + + std::vector slices_extrusions{get_slices_extrusions( + print, *layer, should_pick_extrusion, smooth_path, offset, extruder_id, previous_position + )}; + if (!slices_extrusions.empty()) { + result.push_back({offset, std::move(slices_extrusions)}); + } + } + } + return result; +} + +std::vector get_normal_extrusions( + const Print &print, + const GCode::ObjectsLayerToPrint &layers, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const unsigned int extruder_id, + const PathSmoothingFunction &smooth_path, + std::optional &previous_position +) { + std::vector result; + + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + const PrintObject &print_object = instance.print_object; + const Point &offset = print_object.instances()[instance.instance_id].shift; + + result.emplace_back(); + result.back().instance_offset = offset; + + if (layers[instance.object_layer_to_print_id].support_layer != nullptr) { + result.back().support_extrusions = get_support_extrusions( + extruder_id, + layers[instance.object_layer_to_print_id], + translate_support_extruder(instance.print_object.config().support_material_extruder.value, layer_tools, print.config().filament_soluble), + translate_support_extruder(instance.print_object.config().support_material_interface_extruder.value, layer_tools, print.config().filament_soluble), + smooth_path, + previous_position + ); + } + + if (const Layer *layer = layers[instance.object_layer_to_print_id].object_layer; layer) { + const auto should_pick_extrusion{[&layer_tools, &instance, &extruder_id](const ExtrusionEntityCollection &entity_collection, const PrintRegion ®ion){ + if (is_overriden(entity_collection, layer_tools, instance.instance_id)) { + return false; + } + + if (get_extruder_id(entity_collection, layer_tools, region, instance.instance_id) != static_cast(extruder_id)) { + return false; + } + return true; + }}; + + result.back().slices_extrusions = get_slices_extrusions( + print, + *layer, + should_pick_extrusion, + smooth_path, + offset, + extruder_id, + previous_position + ); + } + } + return result; +} + +bool is_empty(const std::vector &extrusions) { + for (const SliceExtrusions &slice_extrusions : extrusions) { + for (const IslandExtrusions &island_extrusions : slice_extrusions.common_extrusions) { + if (!island_extrusions.perimeters.empty() || !island_extrusions.infill_ranges.empty()) { + return false; + } + } + if (!slice_extrusions.ironing_extrusions.empty()) { + return false; + } + } + return true; +} + +bool is_empty(const ExtruderExtrusions &extruder_extrusions) { + for (const OverridenExtrusions &overriden_extrusions : extruder_extrusions.overriden_extrusions) { + if (!is_empty(overriden_extrusions.slices_extrusions)) { + return false; + } + } + for (const NormalExtrusions &normal_extrusions : extruder_extrusions.normal_extrusions) { + if (!normal_extrusions.support_extrusions.empty()) { + return false; + } + if (!is_empty(normal_extrusions.slices_extrusions)) { + return false; + } + } + return true; +} + + +std::vector get_extrusions( + const Print &print, + const GCode::WipeTowerIntegration *wipe_tower, + const GCode::ObjectsLayerToPrint &layers, + const bool is_first_layer, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const std::map> &skirt_loops_per_extruder, + unsigned current_extruder_id, + const PathSmoothingFunction &smooth_path, + bool get_brim, + std::optional previous_position +) { + unsigned toolchange_number{0}; + + std::vector extrusions; + for (const unsigned int extruder_id : layer_tools.extruders) + { + ExtruderExtrusions extruder_extrusions{extruder_id}; + + if (layer_tools.has_wipe_tower && wipe_tower != nullptr) { + if (is_toolchange_required(is_first_layer, layer_tools.extruders.back(), extruder_id, current_extruder_id)) { + const WipeTower::ToolChangeResult tool_change{wipe_tower->get_toolchange(toolchange_number++)}; + previous_position = Point::new_scale(wipe_tower->transform_wt_pt(tool_change.end_pos)); + current_extruder_id = tool_change.new_tool; + extruder_extrusions.wipe_tower_start = Point::new_scale(wipe_tower->transform_wt_pt(tool_change.start_pos)); + } + } + + + if (auto loops_it = skirt_loops_per_extruder.find(extruder_id); loops_it != skirt_loops_per_extruder.end()) { + const std::pair loops = loops_it->second; + for (std::size_t i = loops.first; i < loops.second; ++i) { + bool reverse{false}; + if (auto loop = dynamic_cast(print.skirt().entities[i])) { + const bool is_hole = loop->is_clockwise(); + reverse = print.config().prefer_clockwise_movements ? !is_hole : is_hole; + } + const ExtrusionEntityReference entity{*print.skirt().entities[i], reverse}; + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, entity, extruder_id, last_position)}; + previous_position = get_gcode_point(last_position, {0, 0}); + extruder_extrusions.skirt.emplace_back(i, std::move(path)); + } + } + + // Extrude brim with the extruder of the 1st region. + if (get_brim) { + for (const ExtrusionEntity *entity : print.brim().entities) { + bool reverse{false}; + bool is_loop{false}; + if (auto loop = dynamic_cast(entity)) { + const bool is_hole = loop->is_clockwise(); + is_loop = true; + reverse = print.config().prefer_clockwise_movements ? !is_hole : is_hole; + } + const ExtrusionEntityReference entity_reference{*entity, reverse}; + + std::optional last_position{get_instance_point(previous_position, {0, 0})}; + SmoothPath path{smooth_path(nullptr, entity_reference, extruder_id, last_position)}; + previous_position = get_gcode_point(last_position, {0, 0}); + extruder_extrusions.brim.push_back({std::move(path), is_loop}); + } + get_brim = false; + } + + using GCode::ExtrusionOrder::get_overriden_extrusions; + bool is_anything_overridden = layer_tools.wiping_extrusions().is_anything_overridden(); + if (is_anything_overridden) { + extruder_extrusions.overriden_extrusions = get_overriden_extrusions( + print, layers, layer_tools, instances_to_print, extruder_id, smooth_path, + previous_position + ); + } + + using GCode::ExtrusionOrder::get_normal_extrusions; + extruder_extrusions.normal_extrusions = get_normal_extrusions( + print, layers, layer_tools, instances_to_print, extruder_id, smooth_path, + previous_position + ); + + if (is_empty(extruder_extrusions)) { + continue; + } + + extrusions.push_back(std::move(extruder_extrusions)); + } + return extrusions; +} + +std::optional get_first_point(const SmoothPath &path) { + for (const SmoothPathElement & element : path) { + if (!element.path.empty()) { + return InstancePoint{element.path.front().point}; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &smooth_paths) { + for (const SmoothPath &path : smooth_paths) { + if (auto result = get_first_point(path)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &infill_ranges) { + for (const InfillRange &infill_range : infill_ranges) { + if (auto result = get_first_point(infill_range.items)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &perimeters) { + for (const Perimeter &perimeter : perimeters) { + if (auto result = get_first_point(perimeter.smooth_path)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &extrusions) { + for (const IslandExtrusions &island : extrusions) { + if (island.infill_first) { + if (auto result = get_first_point(island.infill_ranges)) { + return result; + } + if (auto result = get_first_point(island.perimeters)) { + return result; + } + } else { + if (auto result = get_first_point(island.perimeters)) { + return result; + } + if (auto result = get_first_point(island.infill_ranges)) { + return result; + } + } + } + return std::nullopt; +} + +std::optional get_first_point(const std::vector &extrusions) { + for (const SliceExtrusions &slice : extrusions) { + if (auto result = get_first_point(slice.common_extrusions)) { + return result; + } + } + return std::nullopt; +} + +std::optional get_first_point(const ExtruderExtrusions &extrusions) { + for (const auto&[_, path] : extrusions.skirt) { + if (auto result = get_first_point(path)) { + return result->local_point; + }; + } + for (const BrimPath &brim_path : extrusions.brim) { + if (auto result = get_first_point(brim_path.path)) { + return result->local_point; + }; + } + for (const OverridenExtrusions &overriden_extrusions : extrusions.overriden_extrusions) { + if (auto result = get_first_point(overriden_extrusions.slices_extrusions)) { + return result->local_point - overriden_extrusions.instance_offset; + } + } + + for (const NormalExtrusions &normal_extrusions : extrusions.normal_extrusions) { + for (const SupportPath &support_path : normal_extrusions.support_extrusions) { + if (auto result = get_first_point(support_path.path)) { + return result->local_point + normal_extrusions.instance_offset; + } + } + if (auto result = get_first_point(normal_extrusions.slices_extrusions)) { + return result->local_point + normal_extrusions.instance_offset; + } + } + + return std::nullopt; +} + +std::optional get_first_point(const std::vector &extrusions) { + if (extrusions.empty()) { + return std::nullopt; + } + + if (extrusions.front().wipe_tower_start) { + return extrusions.front().wipe_tower_start; + } + + for (const ExtruderExtrusions &extruder_extrusions : extrusions) { + if (auto result = get_first_point(extruder_extrusions)) { + return result; + } + } + + return std::nullopt; +} + +const PrintInstance * get_first_instance( + const std::vector &extrusions, + const std::vector &instances_to_print +) { + for (const ExtruderExtrusions &extruder_extrusions : extrusions) { + if (!extruder_extrusions.overriden_extrusions.empty()) { + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const OverridenExtrusions &overriden_extrusions{extruder_extrusions.overriden_extrusions[i]}; + if (!is_empty(overriden_extrusions.slices_extrusions)) { + const InstanceToPrint &instance{instances_to_print[i]}; + return &instance.print_object.instances()[instance.instance_id]; + } + } + } + for (std::size_t i{0}; i < instances_to_print.size(); ++i) { + const InstanceToPrint &instance{instances_to_print[i]}; + const std::vector &support_extrusions{extruder_extrusions.normal_extrusions[i].support_extrusions}; + const std::vector &slices_extrusions{extruder_extrusions.normal_extrusions[i].slices_extrusions}; + + if (!support_extrusions.empty() || !is_empty(slices_extrusions)) { + return &instance.print_object.instances()[instance.instance_id]; + } + } + } + return nullptr; +} + +} diff --git a/src/libslic3r/GCode/ExtrusionOrder.hpp b/src/libslic3r/GCode/ExtrusionOrder.hpp new file mode 100644 index 0000000000..436be2fe7b --- /dev/null +++ b/src/libslic3r/GCode/ExtrusionOrder.hpp @@ -0,0 +1,147 @@ +#ifndef slic3r_GCode_ExtrusionOrder_hpp_ +#define slic3r_GCode_ExtrusionOrder_hpp_ + +#include + +#include "libslic3r/GCode/SmoothPath.hpp" +#include "libslic3r/GCode/WipeTowerIntegration.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/GCode/SeamPlacer.hpp" +#include "libslic3r/Print.hpp" +#include "libslic3r/ShortestPath.hpp" + +namespace Slic3r::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; + } +}; + +using ObjectsLayerToPrint = std::vector; + +struct InstanceToPrint +{ + InstanceToPrint( + size_t object_layer_to_print_id, const PrintObject &print_object, size_t instance_id + ) + : object_layer_to_print_id(object_layer_to_print_id) + , print_object(print_object) + , instance_id(instance_id) {} + + // Index into std::vector, which contains Object and Support layers for the + // current print_z, collected for a single object, or for possibly multiple objects with + // multiple instances. + const size_t object_layer_to_print_id; + const PrintObject &print_object; + // Instance idx of the copy of a print object. + const size_t instance_id; +}; +} // namespace Slic3r::GCode + +namespace Slic3r::GCode::ExtrusionOrder { + +struct InfillRange { + std::vector items; + const PrintRegion *region; +}; + +struct Perimeter { + GCode::SmoothPath smooth_path; + bool reversed; + const ExtrusionEntity *extrusion_entity; +}; + +struct IslandExtrusions { + const PrintRegion *region; + std::vector perimeters; + std::vector infill_ranges; + bool infill_first{false}; +}; + +struct SliceExtrusions { + std::vector common_extrusions; + std::vector ironing_extrusions; +}; + +struct SupportPath { + SmoothPath path; + bool is_interface; +}; + +struct NormalExtrusions { + Point instance_offset; + std::vector support_extrusions; + std::vector slices_extrusions; +}; + +struct OverridenExtrusions { + Point instance_offset; + std::vector slices_extrusions; +}; + +/** + * Intentionally strong type representing a point in a coordinate system + * of an instance. Introduced to avoid confusion between local and + * global coordinate systems. + */ +struct InstancePoint { + Point local_point; +}; + +using PathSmoothingFunction = std::function &previous_position +)>; + +struct BrimPath { + SmoothPath path; + bool is_loop; +}; + +struct ExtruderExtrusions { + unsigned extruder_id; + std::vector> skirt; + std::vector brim; + std::vector overriden_extrusions; + std::vector normal_extrusions; + std::optional wipe_tower_start; +}; + +static constexpr const double min_gcode_segment_length = 0.002; + +bool is_empty(const std::vector &extrusions); + +std::vector get_extrusions( + const Print &print, + const GCode::WipeTowerIntegration *wipe_tower, + const GCode::ObjectsLayerToPrint &layers, + const bool is_first_layer, + const LayerTools &layer_tools, + const std::vector &instances_to_print, + const std::map> &skirt_loops_per_extruder, + unsigned current_extruder_id, + const PathSmoothingFunction &smooth_path, + bool get_brim, + std::optional previous_position +); + +std::optional get_first_point(const std::vector &extrusions); + +const PrintInstance * get_first_instance( + const std::vector &extrusions, + const std::vector &instances_to_print +); +} + +#endif // slic3r_GCode_ExtrusionOrder_hpp_ diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 3c0047ead9..19aeb4a122 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -57,9 +57,6 @@ const std::vector GCodeProcessor::Reserved_Tags = { "HEIGHT:", "WIDTH:", "LAYER_CHANGE", - "LAYER_CHANGE_TRAVEL", - "LAYER_CHANGE_RETRACTION_START", - "LAYER_CHANGE_RETRACTION_END", "COLOR_CHANGE", "PAUSE_PRINT", "CUSTOM_GCODE", diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 6048144e96..068e7b34c5 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -181,9 +181,6 @@ namespace Slic3r { Height, Width, Layer_Change, - Layer_Change_Travel, - Layer_Change_Retraction_Start, - Layer_Change_Retraction_End, Color_Change, Pause_Print, Custom_Code, diff --git a/src/libslic3r/GCode/WipeTowerIntegration.cpp b/src/libslic3r/GCode/WipeTowerIntegration.cpp index ef69b98b43..b281060ff8 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.cpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.cpp @@ -34,15 +34,6 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip std::string gcode; - // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) - // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position - float alpha = m_wipe_tower_rotation / 180.f * float(M_PI); - - auto transform_wt_pt = [&alpha, this](const Vec2f& pt) -> Vec2f { - Vec2f out = Eigen::Rotation2Df(alpha) * pt; - out += m_wipe_tower_pos; - return out; - }; Vec2f start_pos = tcr.start_pos; Vec2f end_pos = tcr.end_pos; @@ -52,7 +43,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip } Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos; - float wipe_tower_rotation = tcr.priming ? 0.f : alpha; + float wipe_tower_rotation = tcr.priming ? 0.f : this->get_alpha(); std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation); @@ -77,18 +68,13 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip gcode += gcodegen.retract_and_wipe(); gcodegen.m_avoid_crossing_perimeters.use_external_mp_once = true; 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, [](){return "";} - ); - } else { - gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); - gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); - } + if (gcodegen.last_position) { + gcode += gcodegen.travel_to( + *gcodegen.last_position, xy_point, ExtrusionRole::Mixed, comment, [](){return "";} + ); } else { - const Vec3crd point = to_3d(xy_point, scaled(z)); - gcode += gcodegen.travel_to_first_position(point, current_z, ExtrusionRole::Mixed, [](){return "";}); + gcode += gcodegen.writer().travel_to_xy(gcodegen.point_to_gcode(xy_point), comment); + gcode += gcodegen.writer().get_travel_to_z_gcode(z, comment); } gcode += gcodegen.unretract(); } else { @@ -145,7 +131,7 @@ std::string WipeTowerIntegration::append_tcr(GCodeGenerator &gcodegen, const Wip Geometry::ArcWelder::Path path; path.reserve(tcr.wipe_path.size()); std::transform(tcr.wipe_path.begin(), tcr.wipe_path.end(), std::back_inserter(path), - [&gcodegen, &transform_wt_pt](const Vec2f &wipe_pt) { + [&gcodegen, this](const Vec2f &wipe_pt) { return Geometry::ArcWelder::Segment{ wipe_tower_point_to_object_point(gcodegen, transform_wt_pt(wipe_pt)) }; }); // Pass to the wipe cache. diff --git a/src/libslic3r/GCode/WipeTowerIntegration.hpp b/src/libslic3r/GCode/WipeTowerIntegration.hpp index 85553425de..05c60a2291 100644 --- a/src/libslic3r/GCode/WipeTowerIntegration.hpp +++ b/src/libslic3r/GCode/WipeTowerIntegration.hpp @@ -39,8 +39,23 @@ public: std::string tool_change(GCodeGenerator &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCodeGenerator &gcodegen); std::vector used_filament_length() const; + WipeTower::ToolChangeResult get_toolchange(std::size_t index) const { + return m_tool_changes.at(m_layer_idx).at(index); + } + + Vec2f transform_wt_pt(const Vec2f& pt) const { + Vec2f out = Eigen::Rotation2Df(this->get_alpha()) * pt; + out += m_wipe_tower_pos; + return out; + }; private: + // Toolchangeresult.gcode assumes the wipe tower corner is at the origin (except for priming lines) + // We want to rotate and shift all extrusions (gcode postprocessing) and starting and ending position + float get_alpha() const { + return m_wipe_tower_rotation / 180.f * float(M_PI); + } + WipeTowerIntegration& operator=(const WipeTowerIntegration&); std::string append_tcr(GCodeGenerator &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id, double z = -1.) const; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 09af1ba0ae..cd358951b7 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1484,6 +1484,21 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt) const return m_wipe_tower_data; } +bool is_toolchange_required( + const bool first_layer, + const unsigned last_extruder_id, + const unsigned extruder_id, + const unsigned current_extruder_id +) { + if (first_layer && extruder_id == last_extruder_id) { + return true; + } + if (extruder_id != current_extruder_id) { + return true; + } + return false; +} + void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); @@ -1552,10 +1567,11 @@ void Print::_make_wipe_tower() unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.all_extruders().back(); for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) continue; - bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); for (const auto extruder_id : layer_tools.extruders) { - if ((first_layer && extruder_id == m_wipe_tower_data.tool_ordering.all_extruders().back()) || extruder_id != current_extruder_id) { + const bool first_layer{&layer_tools == &m_wipe_tower_data.tool_ordering.front()}; + const unsigned last_extruder_id{m_wipe_tower_data.tool_ordering.all_extruders().back()}; + if (is_toolchange_required(first_layer, last_extruder_id, extruder_id, current_extruder_id)) { float volume_to_wipe = wipe_volumes[current_extruder_id][extruder_id]; // total volume to wipe after this toolchange // Not all of that can be used for infill purging: volume_to_wipe -= (float)m_config.filament_minimal_purge_on_wipe_tower.get_at(extruder_id); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index f5ad11f2f1..6c5947c423 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -493,6 +493,13 @@ private: WipeTowerData &operator=(const WipeTowerData & /* rhs */) = delete; }; +bool is_toolchange_required( + const bool first_layer, + const unsigned last_extruder_id, + const unsigned extruder_id, + const unsigned current_extruder_id +); + struct PrintStatistics { PrintStatistics() { clear(); }