diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 86279de57c..36fa60da34 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2874,10 +2874,37 @@ std::string GCodeGenerator::change_layer( 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) - gcode += this->_extrude(el.path_attributes, el.path, description, speed); + + // Extrude along the smooth path. + bool is_bridge_extruded = false; + EmitModifiers emit_modifiers = EmitModifiers::create_with_disabled_emits(); + for (auto el_it = smooth_path.begin(); el_it != smooth_path.end(); ++el_it) { + const auto next_el_it = next(el_it); + + // By default, GCodeGenerator::_extrude() emit markers _BRIDGE_FAN_START, _BRIDGE_FAN_END and _RESET_FAN_SPEED for every extrusion. + // Together with split extrusions because of different ExtrusionAttributes, this could flood g-code with those markers and then + // produce an unnecessary number of duplicity M106. + // To prevent this, we control when each marker should be emitted by EmitModifiers, which allows determining when a bridge starts and ends, + // even when it is split into several extrusions. + if (el_it->path_attributes.role.is_bridge()) { + emit_modifiers.emit_bridge_fan_start = !is_bridge_extruded; + emit_modifiers.emit_bridge_fan_end = next_el_it == smooth_path.end() || !next_el_it->path_attributes.role.is_bridge(); + is_bridge_extruded = true; + } else if (is_bridge_extruded) { + emit_modifiers.emit_bridge_fan_start = false; + emit_modifiers.emit_bridge_fan_end = false; + is_bridge_extruded = false; + } + + // Ensure that just for the last extrusion from the smooth path, the fan speed will be reset back + // to the value calculated by the CoolingBuffer. + if (next_el_it == smooth_path.end()) { + emit_modifiers.emit_fan_speed_reset = true; + } + + gcode += this->_extrude(el_it->path_attributes, el_it->path, description, speed, emit_modifiers); + } // reset acceleration gcode += m_writer.set_print_acceleration(fast_round_up(m_config.default_acceleration.value)); @@ -2889,6 +2916,7 @@ std::string GCodeGenerator::extrude_smooth_path( GCode::reverse(reversed_smooth_path); m_wipe.set_path(std::move(reversed_smooth_path)); } + return gcode; } @@ -3115,7 +3143,8 @@ std::string GCodeGenerator::_extrude( const ExtrusionAttributes &path_attr, const Geometry::ArcWelder::Path &path, const std::string_view description, - double speed) + double speed, + const EmitModifiers &emit_modifiers) { std::string gcode; const std::string_view description_bridge = path_attr.role.is_bridge() ? " (bridge)"sv : ""sv; @@ -3226,7 +3255,8 @@ std::string GCodeGenerator::_extrude( external_perimeter_reference_speed = cap_speed(external_perimeter_reference_speed, m_config, m_writer.extruder()->id(), path_attr); dynamic_print_and_fan_speeds = ExtrusionProcessor::calculate_overhang_speed(path_attr, this->m_config, m_writer.extruder()->id(), - float(external_perimeter_reference_speed), float(speed)); + float(external_perimeter_reference_speed), float(speed), + m_current_dynamic_fan_speed); } if (dynamic_print_and_fan_speeds.print_speed > -1) { @@ -3281,19 +3311,29 @@ std::string GCodeGenerator::_extrude( std::string cooling_marker_setspeed_comments; if (m_enable_cooling_markers) { - if (path_attr.role.is_bridge()) + if (path_attr.role.is_bridge() && emit_modifiers.emit_bridge_fan_start) { gcode += ";_BRIDGE_FAN_START\n"; - else + } else if (!path_attr.role.is_bridge()) { cooling_marker_setspeed_comments = ";_EXTRUDE_SET_SPEED"; - if (path_attr.role == ExtrusionRole::ExternalPerimeter) + } + + if (path_attr.role == ExtrusionRole::ExternalPerimeter) { cooling_marker_setspeed_comments += ";_EXTERNAL_PERIMETER"; + } } // F is mm per minute. gcode += m_writer.set_speed(F, "", cooling_marker_setspeed_comments); if (dynamic_print_and_fan_speeds.fan_speed >= 0) { - gcode += ";_SET_FAN_SPEED" + std::to_string(int(dynamic_print_and_fan_speeds.fan_speed)) + "\n"; + const int fan_speed = int(dynamic_print_and_fan_speeds.fan_speed); + if (!m_current_dynamic_fan_speed.has_value() || (m_current_dynamic_fan_speed.has_value() && m_current_dynamic_fan_speed != fan_speed)) { + m_current_dynamic_fan_speed = fan_speed; + gcode += ";_SET_FAN_SPEED" + std::to_string(fan_speed) + "\n"; + } + } else if (m_current_dynamic_fan_speed.has_value() && dynamic_print_and_fan_speeds.fan_speed < 0) { + m_current_dynamic_fan_speed.reset(); + gcode += ";_RESET_FAN_SPEED\n"; } std::string comment; @@ -3345,10 +3385,15 @@ std::string GCodeGenerator::_extrude( } if (m_enable_cooling_markers) { - gcode += path_attr.role.is_bridge() ? ";_BRIDGE_FAN_END\n" : ";_EXTRUDE_END\n"; + if (path_attr.role.is_bridge() && emit_modifiers.emit_bridge_fan_end) { + gcode += ";_BRIDGE_FAN_END\n"; + } else if (!path_attr.role.is_bridge()) { + gcode += ";_EXTRUDE_END\n"; + } } - if (dynamic_print_and_fan_speeds.fan_speed >= 0) { + if (m_current_dynamic_fan_speed.has_value() && emit_modifiers.emit_fan_speed_reset) { + m_current_dynamic_fan_speed.reset(); gcode += ";_RESET_FAN_SPEED\n"; } diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index b35bd567e0..82e2dc151a 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -436,6 +436,9 @@ private: std::unique_ptr m_pressure_equalizer; std::unique_ptr m_wipe_tower; + // Current fan speed set by dynamic fan speed control. + std::optional m_current_dynamic_fan_speed; + // Heights (print_z) at which the skirt has already been extruded. std::vector m_skirt_done; // Has the brim been extruded already? Brim is being extruded only for the first object of a multi-object print. @@ -455,8 +458,24 @@ private: // Back-pointer to Print (const). const Print* m_print; - std::string _extrude( - const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, const std::string_view description, double speed = -1); + struct EmitModifiers { + EmitModifiers(bool emit_fan_speed_reset, bool emit_bridge_fan_start, bool emit_bridge_fan_end) + : emit_fan_speed_reset(emit_fan_speed_reset), emit_bridge_fan_start(emit_bridge_fan_start), emit_bridge_fan_end(emit_bridge_fan_end) {} + + EmitModifiers() : EmitModifiers(true, true, true) {}; + + static EmitModifiers create_with_disabled_emits() { + return {false, false, false}; + } + + bool emit_fan_speed_reset = true; + + bool emit_bridge_fan_start = true; + bool emit_bridge_fan_end = true; + }; + + std::string _extrude(const ExtrusionAttributes &attribs, const Geometry::ArcWelder::Path &path, std::string_view description, double speed, const EmitModifiers &emit_modifiers = EmitModifiers()); + void print_machine_envelope(GCodeOutputStream &file, const Print &print); void _print_first_layer_chamber_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, int temp, bool wait, bool accurate); void _print_first_layer_bed_temperature(GCodeOutputStream &file, const Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index efee06644c..815f28c728 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -95,6 +95,7 @@ struct CoolingLine TYPE_ADJUSTABLE_EMPTY = 1 << 16, // Custom fan speed (introduced for overhang fan speed) TYPE_SET_FAN_SPEED = 1 << 17, + // Reset fan speed back to speed calculate by the CoolingBuffer. TYPE_RESET_FAN_SPEED = 1 << 18, }; @@ -571,19 +572,20 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: fast_float::from_chars(sline.data() + (has_S ? pos_S : pos_P) + 1, sline.data() + sline.size(), line.time); if (has_P) line.time *= 0.001f; - } else + } else { line.time = 0; - line.time_max = line.time; - } + } - if (boost::contains(sline, ";_SET_FAN_SPEED")) { + line.time_max = line.time; + } else if (boost::contains(sline, ";_SET_FAN_SPEED")) { auto speed_start = sline.find_last_of('D'); int speed = 0; for (char num : sline.substr(speed_start + 1)) { speed = speed * 10 + (num - '0'); } - line.type |= CoolingLine::TYPE_SET_FAN_SPEED; + line.fan_speed = speed; + line.type |= CoolingLine::TYPE_SET_FAN_SPEED; } else if (boost::contains(sline, ";_RESET_FAN_SPEED")) { line.type |= CoolingLine::TYPE_RESET_FAN_SPEED; } @@ -820,14 +822,22 @@ std::string CoolingBuffer::apply_layer_cooldown( new_gcode.reserve(gcode.size() * 2); bool bridge_fan_control = false; int bridge_fan_speed = 0; - auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed]() { + auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &bridge_fan_control, &bridge_fan_speed](const int requested_fan_speed = -1) { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) - int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); - int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; - std::pair custom_fan_speed_limits{fan_speed_new, 100 }; - int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); + const int min_fan_speed = EXTRUDER_CONFIG(min_fan_speed); // Is the fan speed ramp enabled? - int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); + const int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); + int disable_fan_first_layers = EXTRUDER_CONFIG(disable_fan_first_layers); + int fan_speed_new = EXTRUDER_CONFIG(fan_always_on) ? min_fan_speed : 0; + + struct FanSpeedRange + { + int min_speed; + int max_speed; + }; + + FanSpeedRange requested_fan_speed_limits{fan_speed_new, 100}; + if (disable_fan_first_layers <= 0 && full_fan_speed_layer > 0) { // When ramping up fan speed from disable_fan_first_layers to full_fan_speed_layer, force disable_fan_first_layers above zero, // so there will be a zero fan speed at least at the 1st layer. @@ -840,43 +850,52 @@ std::string CoolingBuffer::apply_layer_cooldown( if (EXTRUDER_CONFIG(cooling)) { if (layer_time < slowdown_below_layer_time) { // Layer time very short. Enable the fan to a full throttle. - fan_speed_new = max_fan_speed; - custom_fan_speed_limits.first = fan_speed_new; + fan_speed_new = max_fan_speed; + requested_fan_speed_limits.min_speed = max_fan_speed; } else if (layer_time < fan_below_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. assert(layer_time >= slowdown_below_layer_time); - double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); - fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); - custom_fan_speed_limits.first = fan_speed_new; + const double t = (layer_time - slowdown_below_layer_time) / (fan_below_layer_time - slowdown_below_layer_time); + + fan_speed_new = int(floor(t * min_fan_speed + (1. - t) * max_fan_speed) + 0.5); + requested_fan_speed_limits.min_speed = fan_speed_new; } } - bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); + + bridge_fan_speed = EXTRUDER_CONFIG(bridge_fan_speed); if (int(layer_id) >= disable_fan_first_layers && int(layer_id) + 1 < full_fan_speed_layer) { // Ramp up the fan speed from disable_fan_first_layers to full_fan_speed_layer. - float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers); - fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 100); - bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 100); - custom_fan_speed_limits.second = fan_speed_new; + const float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers); + + fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 100); + bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 100); + requested_fan_speed_limits.max_speed = fan_speed_new; } + #undef EXTRUDER_CONFIG bridge_fan_control = bridge_fan_speed > fan_speed_new; } else { // fan disabled - bridge_fan_control = false; - bridge_fan_speed = 0; - fan_speed_new = 0; - custom_fan_speed_limits.second = 0; + bridge_fan_control = false; + bridge_fan_speed = 0; + fan_speed_new = 0; + requested_fan_speed_limits.max_speed = 0; } + + requested_fan_speed_limits.min_speed = std::min(requested_fan_speed_limits.min_speed, requested_fan_speed_limits.max_speed); + if (requested_fan_speed >= 0) { + fan_speed_new = std::clamp(requested_fan_speed, requested_fan_speed_limits.min_speed, requested_fan_speed_limits.max_speed); + } + if (fan_speed_new != m_fan_speed) { m_fan_speed = fan_speed_new; new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, m_fan_speed); } - custom_fan_speed_limits.first = std::min(custom_fan_speed_limits.first, custom_fan_speed_limits.second); - return custom_fan_speed_limits; }; const char *pos = gcode.c_str(); int current_feedrate = 0; - std::pair fan_speed_limits = change_extruder_set_fan(); + + change_extruder_set_fan(); for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; const char *line_end = gcode.c_str() + line->line_end; @@ -887,17 +906,13 @@ std::string CoolingBuffer::apply_layer_cooldown( auto res = std::from_chars(line_start + m_toolchange_prefix.size(), line_end, new_extruder); if (res.ec != std::errc::invalid_argument && new_extruder != m_current_extruder) { m_current_extruder = new_extruder; - fan_speed_limits = change_extruder_set_fan(); + change_extruder_set_fan(); } new_gcode.append(line_start, line_end - line_start); } else if (line->type & CoolingLine::TYPE_SET_FAN_SPEED) { - int new_speed = std::clamp(line->fan_speed, fan_speed_limits.first, fan_speed_limits.second); - if (m_fan_speed != new_speed) { - new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, new_speed); - m_fan_speed = new_speed; - } + change_extruder_set_fan(line->fan_speed); } else if (line->type & CoolingLine::TYPE_RESET_FAN_SPEED){ - fan_speed_limits = change_extruder_set_fan(); + change_extruder_set_fan(); } else if (line->type & CoolingLine::TYPE_BRIDGE_FAN_START) { if (bridge_fan_control) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_config.gcode_comments, bridge_fan_speed); diff --git a/src/libslic3r/GCode/ExtrusionProcessor.cpp b/src/libslic3r/GCode/ExtrusionProcessor.cpp index d818581e8f..eb89b5ed57 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.cpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.cpp @@ -215,7 +215,8 @@ OverhangSpeeds calculate_overhang_speed(const ExtrusionAttributes &attributes, const FullPrintConfig &config, const size_t extruder_id, const float external_perimeter_reference_speed, - const float default_speed) + const float default_speed, + const std::optional ¤t_fan_speed) { assert(attributes.overhang_attributes.has_value()); @@ -249,6 +250,9 @@ OverhangSpeeds calculate_overhang_speed(const ExtrusionAttributes &attributes, if (!config.enable_dynamic_fan_speeds.get_at(extruder_id)) { overhang_speeds.fan_speed = -1; + } else if (current_fan_speed.has_value() && (fan_speed < *current_fan_speed) && (*current_fan_speed - fan_speed) <= MIN_FAN_SPEED_NEGATIVE_CHANGE_TO_EMIT) { + // Always allow the fan speed to be increased without any hysteresis, but the speed will be decreased only when it exceeds a limit for minimum change. + overhang_speeds.fan_speed = *current_fan_speed; } return overhang_speeds; diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp index 0b0610bc30..c197f91a76 100644 --- a/src/libslic3r/GCode/ExtrusionProcessor.hpp +++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp @@ -44,6 +44,10 @@ class Linef; namespace Slic3r::ExtrusionProcessor { +// Minimum decrease of the fan speed in percents that will be emitted into g-code. +// Decreases below this limit will be omitted to not overflow the g-code with fan speed changes. +const constexpr float MIN_FAN_SPEED_NEGATIVE_CHANGE_TO_EMIT = 3.f; + struct ExtendedPoint { Vec2d position; @@ -276,7 +280,8 @@ OverhangSpeeds calculate_overhang_speed(const ExtrusionAttributes &attributes, const FullPrintConfig &config, size_t extruder_id, float external_perimeter_reference_speed, - float default_speed); + float default_speed, + const std::optional ¤t_fan_speed); } // namespace Slic3r::ExtrusionProcessor