From 8ce089c6ff1aa6b46ddd6cafc28f31f2118b61f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hejl?= Date: Thu, 25 Jan 2024 19:42:39 +0100 Subject: [PATCH] SPE-2098: Pick a required extruder before a color change for multi-extruder printers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When we need to perform a color change on an extruder that isn’t used within the layer, we perform a tool change to select the correct extruder. Then, we perform the color change, and finally, we perform another tool change to switch back to the original extruder. --- src/libslic3r/CustomGCode.cpp | 16 +++++++++ src/libslic3r/CustomGCode.hpp | 4 +++ src/libslic3r/GCode.cpp | 34 ++++++++++++------ src/libslic3r/GCode/ToolOrdering.cpp | 43 +++++++++++++++++----- src/libslic3r/GCode/ToolOrdering.hpp | 5 ++- src/libslic3r/PrintApply.cpp | 54 +++++++++++++++++----------- 6 files changed, 115 insertions(+), 41 deletions(-) diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp index 7b950b8d7c..92fc8c0d9d 100644 --- a/src/libslic3r/CustomGCode.cpp +++ b/src/libslic3r/CustomGCode.cpp @@ -72,6 +72,22 @@ std::vector> custom_tool_changes(const Info& cus return custom_tool_changes; } +// Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. +// Where print_z corresponds to the layer on which we perform a color change for the specified extruder. +std::vector> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders) +{ + std::vector> custom_color_changes; + for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes) + if (custom_gcode.type == ColorChange) { + // If extruder count in PrinterSettings was changed, ignore custom g-codes for extruder ids bigger than num_extruders. + assert(custom_gcode.extruder >= 0); + if (size_t(custom_gcode.extruder) <= num_extruders) { + custom_color_changes.emplace_back(custom_gcode.print_z, static_cast(custom_gcode.extruder)); + } + } + return custom_color_changes; +} + } // namespace CustomGCode } // namespace Slic3r diff --git a/src/libslic3r/CustomGCode.hpp b/src/libslic3r/CustomGCode.hpp index 3563084cb6..939f8bdd51 100644 --- a/src/libslic3r/CustomGCode.hpp +++ b/src/libslic3r/CustomGCode.hpp @@ -91,6 +91,10 @@ extern void check_mode_for_custom_gcode_per_print_z(Info& info); // print_z corresponds to the first layer printed with the new extruder. std::vector> custom_tool_changes(const Info& custom_gcode_per_print_z, size_t num_extruders); +// Return pairs of sorted by increasing print_z from custom_gcode_per_print_z. +// Where print_z corresponds to the layer on which we perform a color change for the specified extruder. +std::vector> custom_color_changes(const Info& custom_gcode_per_print_z, size_t num_extruders); + } // namespace CustomGCode } // namespace Slic3r diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 994fb3ac17..04770d1083 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2255,16 +2255,6 @@ LayerResult GCodeGenerator::process_layer( // Map from extruder ID to index of skirt loops to be extruded with that extruder. std::map> skirt_loops_per_extruder; - if (single_object_instance_idx == size_t(-1) && layer_tools.custom_gcode != nullptr) { - // Normal (non-sequential) print. - std::string custom_gcode = ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); - if (layer_tools.custom_gcode->type == CustomGCode::ColorChange) { - // We have a color change to do on this layer, but we want to do it immediately before the first extrusion instead of now, in order to fix GH #2672 - m_pending_pre_extrusion_gcode = custom_gcode; - } else { - gcode += custom_gcode; - } - } // 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 ? @@ -2283,6 +2273,23 @@ LayerResult GCodeGenerator::process_layer( } } + const bool has_custom_gcode_to_emit = single_object_instance_idx == size_t(-1) && layer_tools.custom_gcode != nullptr; + const int extruder_id_for_custom_gcode = int(layer_tools.extruder_needed_for_color_changer) - 1; + + if (has_custom_gcode_to_emit && extruder_id_for_custom_gcode == -1) { + // Normal (non-sequential) print with some custom code without picking a specific extruder before it. + // If we don't need to pick a specific extruder before the color change, we can just emit a custom g-code. + // Otherwise, we will emit the g-code after picking the specific extruder. + + std::string custom_gcode = ProcessLayer::emit_custom_gcode_per_print_z(*this, *layer_tools.custom_gcode, m_writer.extruder()->id(), first_extruder_id, print.config()); + if (layer_tools.custom_gcode->type == CustomGCode::ColorChange) { + // We have a color change to do on this layer, but we want to do it immediately before the first extrusion instead of now, in order to fix GH #2672. + m_pending_pre_extrusion_gcode = custom_gcode; + } else { + gcode += custom_gcode; + } + } + // Extrude the skirt, brim, support, perimeters, infill ordered by the extruders. for (unsigned int extruder_id : layer_tools.extruders) { @@ -2294,6 +2301,13 @@ LayerResult GCodeGenerator::process_layer( 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)) { + 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()) { const std::pair loops = loops_it->second; this->set_origin(0., 0.); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 2eba21de8c..1bcb076dd3 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -110,7 +110,7 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); // Collect extruders required to print the layers. - this->collect_extruders(object, std::vector>()); + this->collect_extruders(object, std::vector>(), std::vector>()); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); @@ -156,17 +156,24 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool // Use the extruder switches from Model::custom_gcode_per_print_z to override the extruder to print the object. // Do it only if all the objects were configured to be printed with a single extruder. std::vector> per_layer_extruder_switches; - if (auto num_extruders = unsigned(print.config().nozzle_diameter.size()); - num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle + auto num_extruders = unsigned(print.config().nozzle_diameter.size()); + if (num_extruders > 1 && print.object_extruders().size() == 1 && // the current Print's configuration is CustomGCode::MultiAsSingle print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiAsSingle) { // Printing a single extruder platter on a printer with more than 1 extruder (or single-extruder multi-material). // There may be custom per-layer tool changes available at the model. per_layer_extruder_switches = custom_tool_changes(print.model().custom_gcode_per_print_z, num_extruders); } - // Collect extruders reuqired to print the layers. + // Color changes for each layer to determine which extruder needs to be picked before color change. + // This is done just for multi-extruder printers without enabled Single Extruder Multi Material (tool changer printers). + std::vector> per_layer_color_changes; + if (num_extruders > 1 && print.model().custom_gcode_per_print_z.mode == CustomGCode::MultiExtruder && !print.config().single_extruder_multi_material) { + per_layer_color_changes = custom_color_changes(print.model().custom_gcode_per_print_z, num_extruders); + } + + // Collect extruders required to print the layers. for (auto object : print.objects()) - this->collect_extruders(*object, per_layer_extruder_switches); + this->collect_extruders(*object, per_layer_extruder_switches, per_layer_color_changes); // Reorder the extruders to minimize tool switches. this->reorder_extruders(first_extruder); @@ -219,8 +226,11 @@ void ToolOrdering::initialize_layers(std::vector &zs) } // Collect extruders reuqired to print layers. -void ToolOrdering::collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches) -{ +void ToolOrdering::collect_extruders( + const PrintObject &object, + const std::vector> &per_layer_extruder_switches, + const std::vector> &per_layer_color_changes +) { // Collect the support extruders. for (auto support_layer : object.support_layers()) { LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); @@ -238,10 +248,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } // Extruder overrides are ordered by print_z. - std::vector>::const_iterator it_per_layer_extruder_override; - it_per_layer_extruder_override = per_layer_extruder_switches.begin(); + std::vector>::const_iterator it_per_layer_extruder_override = per_layer_extruder_switches.begin(); unsigned int extruder_override = 0; + std::vector>::const_iterator it_per_layer_color_changes = per_layer_color_changes.begin(); + // Collect the object extruders. for (auto layer : object.layers()) { LayerTools &layer_tools = this->tools_for_layer(layer->print_z); @@ -253,6 +264,15 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto // Store the current extruder override (set to zero if no overriden), so that layer_tools.wiping_extrusions().is_overridable_and_mark() will use it. layer_tools.extruder_override = extruder_override; + // Append the extruder needed to be picked before performing the color change. + for (; it_per_layer_color_changes != per_layer_color_changes.end() && it_per_layer_color_changes->first < layer->print_z + EPSILON; ++it_per_layer_color_changes) { + if (std::abs(it_per_layer_color_changes->first - layer->print_z) < EPSILON) { + assert(layer_tools.extruder_needed_for_color_changer == 0); // Just on color change per layer is allowed. + layer_tools.extruder_needed_for_color_changer = it_per_layer_color_changes->second; + layer_tools.extruders.emplace_back(it_per_layer_color_changes->second); + } + } + // What extruders are required to print this object layer? for (const LayerRegion *layerm : layer->regions()) { const PrintRegion ®ion = layerm->region(); @@ -370,6 +390,11 @@ void ToolOrdering::reorder_extruders(unsigned int last_extruder_id) std::swap(lt.extruders[i], lt.extruders.front()); break; } + } else if (lt.extruder_needed_for_color_changer != 0) { + // Put the extruder needed for performing the color change at the beginning. + auto it = std::find(lt.extruders.begin(), lt.extruders.end(), lt.extruder_needed_for_color_changer); + assert(it != lt.extruders.end()); + std::rotate(lt.extruders.begin(), it, it + 1); } } last_extruder_id = lt.extruders.back(); diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 028663660b..aa8f228b24 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -98,6 +98,9 @@ public: // If per layer extruder switches are inserted by the G-code preview slider, this value contains the new (1 based) extruder, with which the whole object layer is being printed with. // If not overriden, it is set to 0. unsigned int extruder_override = 0; + // For multi-extruder printers, when there is a color change, this contains an extruder (1 based) on which the color change will be performed. + // Otherwise, it is set to 0. + unsigned int extruder_needed_for_color_changer = 0; // Should a skirt be printed at this layer? // Layers are marked for infinite skirt aka draft shield. Not all the layers have to be printed. bool has_skirt = false; @@ -169,7 +172,7 @@ public: private: void initialize_layers(std::vector &zs); - void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); + void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches, const std::vector> &per_layer_color_changes); void reorder_extruders(unsigned int last_extruder_id); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height); bool insert_wipe_tower_extruder(); diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 7baaa75a8d..1d9fdbcc8c 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1,7 +1,7 @@ -///|/ Copyright (c) Prusa Research 2021 - 2023 Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Roman Beránek @zavorka -///|/ -///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher -///|/ +///|/ Copyright (c) Prusa Research 2021 - 2023 Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv, Pavel Mikuš @Godrak, Oleksandra Iushchenko @YuSanka, Lukáš Matěna @lukasmatena, Lukáš Hejl @hejllukas, Roman Beránek @zavorka +///|/ +///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher +///|/ #include "Model.hpp" #include "Print.hpp" @@ -169,34 +169,37 @@ static bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_ return true; } -// Returns true if va == vb when all CustomGCode items that are not ToolChangeCode are ignored. -static bool custom_per_printz_gcodes_tool_changes_differ(const std::vector &va, const std::vector &vb) -{ +// Returns true if va == vb when all CustomGCode items that are not the specified type (not_ignore_type) are ignored. +static bool custom_per_printz_gcodes_tool_changes_differ( + const std::vector &va, + const std::vector &vb, + CustomGCode::Type not_ignore_type +) { auto it_a = va.begin(); auto it_b = vb.begin(); while (it_a != va.end() || it_b != vb.end()) { - if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. + if (it_a != va.end() && it_a->type != not_ignore_type) { + // Skip any CustomGCode items, which are not equal to not_ignore_type. ++ it_a; continue; } - if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { - // Skip any CustomGCode items, which are not tool changes. + if (it_b != vb.end() && it_b->type != not_ignore_type) { + // Skip any CustomGCode items, which are not equal to not_ignore_type. ++ it_b; continue; } if (it_a == va.end() || it_b == vb.end()) - // va or vb contains more Tool Changes than the other. + // va or vb contains more items of not_ignore_type than the other. return true; - assert(it_a->type == CustomGCode::ToolChange); - assert(it_b->type == CustomGCode::ToolChange); + assert(it_a->type == not_ignore_type); + assert(it_b->type == not_ignore_type); if (*it_a != *it_b) - // The two Tool Changes differ. + // The two items of not_ignore_type differ. return true; ++ it_a; ++ it_b; } - // There is no change in custom Tool Changes. + // There is no change in specified not_ignore_type items. return false; } @@ -1059,11 +1062,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ model_object_status_db.add(*model_object, ModelObjectStatus::New); } else { if (m_model.custom_gcode_per_print_z != model.custom_gcode_per_print_z) { - update_apply_status(num_extruders_changed || - // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. - //FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable - // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. - (num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes)) ? + const CustomGCode::Mode current_mode = m_model.custom_gcode_per_print_z.mode; + const CustomGCode::Mode next_mode = model.custom_gcode_per_print_z.mode; + + const bool multi_extruder_differ = (current_mode == next_mode) && (current_mode == CustomGCode::MultiExtruder || next_mode == CustomGCode::MultiExtruder); + // Tool change G-codes are applied as color changes for a single extruder printer, no need to invalidate tool ordering. + // FIXME The tool ordering may be invalidated unnecessarily if the custom_gcode_per_print_z.mode is not applicable + // to the active print / model state, and then it is reset, so it is being applicable, but empty, thus the effect is the same. + const bool tool_change_differ = num_extruders > 1 && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ToolChange); + // For multi-extruder printers, we perform a tool change before a color change. + // So, in that case, we must invalidate tool ordering and wipe tower even if custom color change g-codes differ. + const bool color_change_differ = num_extruders > 1 && (next_mode == CustomGCode::MultiExtruder) && custom_per_printz_gcodes_tool_changes_differ(m_model.custom_gcode_per_print_z.gcodes, model.custom_gcode_per_print_z.gcodes, CustomGCode::ColorChange); + + update_apply_status( + (num_extruders_changed || tool_change_differ || multi_extruder_differ || color_change_differ) ? // The Tool Ordering and the Wipe Tower are no more valid. this->invalidate_steps({ psWipeTower, psGCodeExport }) : // There is no change in Tool Changes stored in custom_gcode_per_print_z, therefore there is no need to update Tool Ordering.