diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 4227bf7817..f98cf62fc4 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -8507,25 +8507,20 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description, // the speed fade tracks perpendicular distance from the plane on // belt printers; otherwise this falls back to the slicing layer id. const int _layer = this->effective_layer_index_for_point(path_point_mm); - // Belt printers: tell the cooling buffer which band above the belt the next extrusion is - // in (CoolingBuffer::apply_belt_band_fan consumes the tag). A tilted layer runs from the - // belt to the top of the part, so this is decided per segment, not per path; a tag is - // written once per layer and at every change, capped where the fan stops depending on it. - const bool belt_band_tags = m_enable_cooling_markers && m_config.belt_printer.value; - const int belt_band_cap = belt_band_tags - ? std::max(m_config.close_fan_the_first_x_layers.get_at(m_writer->filament()->id()), - m_config.full_fan_speed_layer.get_at(m_writer->filament()->id())) + 1 : 0; - auto tag_belt_band = [this, &gcode, belt_band_tags, belt_band_cap, z = path_point_mm.z()](coord_t x, coord_t y) { + // Belt printers: a tilted layer runs from the belt to the top of the part, so the + // "first layers" the fan stays off for are a band along the belt. Mark where the + // extrusion enters and leaves it, per segment, for the cooling buffer. + const bool belt_band_tags = m_enable_cooling_markers && m_config.belt_printer.value; + const int belt_band_layers = belt_band_tags ? m_config.close_fan_the_first_x_layers.get_at(m_writer->filament()->id()) : 0; + auto tag_belt_band = [this, &gcode, belt_band_tags, belt_band_layers, z = path_point_mm.z()](coord_t x, coord_t y) { if (! belt_band_tags) return; - const int band = std::min(this->effective_layer_index_for_point(Vec3d(unscale(x), unscale(y), z)), belt_band_cap); - if (m_belt_band_tag_layer != m_layer_index || m_belt_band_tag != band) { - gcode += ";_BELT_BAND:" + std::to_string(band) + "\n"; - m_belt_band_tag = band; - m_belt_band_tag_layer = m_layer_index; + const bool in_band = this->effective_layer_index_for_point(Vec3d(unscale(x), unscale(y), z)) < belt_band_layers; + if (in_band != m_belt_in_band) { + gcode += in_band ? ";_BELT_BAND_START\n" : ";_BELT_BAND_END\n"; + m_belt_in_band = in_band; } }; - tag_belt_band(path.first_point().x(), path.first_point().y()); if (path_on_first_layer || object_layer_over_raft()) { //BBS: for solid infill of first layer, speed can be higher as long as //wall lines have be attached diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e390bc972f..17221101fd 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -856,9 +856,8 @@ protected: ~BeltFloorObjectGuard() { slot = nullptr; } }; - // Last ";_BELT_BAND" tag written and the layer it was written on (see _extrude()). - int m_belt_band_tag{std::numeric_limits::min()}; - int m_belt_band_tag_layer{std::numeric_limits::min()}; + // The last extrusion segment was inside the belt's first-layer fan band (see _extrude()). + bool m_belt_in_band{false}; std::set m_initial_layer_extruders; std::vector> m_sorted_layer_filaments; diff --git a/src/libslic3r/GCode/CoolingBuffer.cpp b/src/libslic3r/GCode/CoolingBuffer.cpp index 29ab528de5..a47dd2c148 100644 --- a/src/libslic3r/GCode/CoolingBuffer.cpp +++ b/src/libslic3r/GCode/CoolingBuffer.cpp @@ -1,13 +1,10 @@ #include "../GCode.hpp" #include "CoolingBuffer.hpp" -#include "FanMover.hpp" #include #include #include #include #include -#include -#include #include #include #include @@ -39,8 +36,7 @@ CoolingBuffer::CoolingBuffer(GCode &gcodegen) : m_config(gcodegen.config()), m_t void CoolingBuffer::reset(const Vec3d &position) { - m_belt_band_fan = -1; - m_belt_band_layer_fan = -1; + m_belt_band_active = false; // BBS: add I and J axis to store center of arc m_current_pos.assign(7, 0.f); m_current_pos[0] = float(position.x()); @@ -80,6 +76,9 @@ struct CoolingLine // ORCA: Add support for ironing fan speed control TYPE_IRONING_FAN_START = 1 << 19, TYPE_IRONING_FAN_END = 1 << 20, + // Belt printers: extrusions within the first-layer band above the belt. + TYPE_BELT_BAND_START = 1 << 21, + TYPE_BELT_BAND_END = 1 << 22, }; CoolingLine(unsigned int type, size_t line_start, size_t line_end) : @@ -334,13 +333,9 @@ std::string CoolingBuffer::process_layer(std::string &&gcode, size_t layer_id, b if (flush) { // This is either an object layer or the very last print layer. Calculate cool down over the collected support layers // and one object layer. - const unsigned int extruder_at_start = m_current_extruder; std::vector per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos); float layer_time_stretched = this->calculate_layer_slowdown(per_extruder_adjustments); out = this->apply_layer_cooldown(m_gcode, layer_id, layer_time_stretched, per_extruder_adjustments); - // Belt printers: the fan follows each path's height above the belt (see apply_belt_band_fan). - if (out.find(";_BELT_BAND:") != std::string::npos) - out = this->apply_belt_band_fan(std::move(out), layer_time_stretched, extruder_at_start); m_gcode.clear(); } return out; @@ -544,6 +539,10 @@ std::vector CoolingBuffer::parse_layer_gcode(const std:: line.type = CoolingLine::TYPE_IRONING_FAN_START; } else if (boost::starts_with(sline, ";_IRONING_FAN_END")) { // ORCA: Add support for ironing fan speed control line.type = CoolingLine::TYPE_IRONING_FAN_END; + } else if (boost::starts_with(sline, ";_BELT_BAND_START")) { + line.type = CoolingLine::TYPE_BELT_BAND_START; + } else if (boost::starts_with(sline, ";_BELT_BAND_END")) { + line.type = CoolingLine::TYPE_BELT_BAND_END; } else if (boost::starts_with(sline, "G4 ")) { // Parse the wait time. line.type = CoolingLine::TYPE_G4; @@ -885,7 +884,9 @@ std::string CoolingBuffer::apply_layer_cooldown( {CoolingLine::TYPE_SUPPORT_INTERFACE_FAN_START, false}, {CoolingLine::TYPE_IRONING_FAN_START, false}, // ORCA: Add support for ironing fan speed control {CoolingLine::TYPE_FORCE_RESUME_FAN, false}}; - bool need_set_fan = false; + // Belt printers: a band still open from the previous layer has to take the fan back from + // the layer-level speed issued just above. + bool need_set_fan = m_belt_band_active; for (const CoolingLine *line : lines) { const char *line_start = gcode.c_str() + line->line_start; @@ -899,6 +900,8 @@ std::string CoolingBuffer::apply_layer_cooldown( if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(true); + if (m_belt_band_active) + need_set_fan = true; } } new_gcode.append(line_start, line_end - line_start); @@ -951,6 +954,13 @@ std::string CoolingBuffer::apply_layer_cooldown( if (m_additional_fan_speed != -1 && m_config.auxiliary_fan.value) new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); } + else if (line->type & CoolingLine::TYPE_BELT_BAND_START) { + m_belt_band_active = true; + need_set_fan = true; + } else if (line->type & CoolingLine::TYPE_BELT_BAND_END) { + m_belt_band_active = false; + need_set_fan = true; + } else if (line->type & CoolingLine::TYPE_EXTRUDE_END) { // Just remove this comment. } else if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE | CoolingLine::TYPE_HAS_F)) { @@ -1043,7 +1053,15 @@ std::string CoolingBuffer::apply_layer_cooldown( m_current_fan_speed = speed; } }; - if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){ + if (m_belt_band_active) { + // Belt printers: a tilted layer runs from the belt to the top of the part, so + // "the first layers" are a band along the belt rather than the first slicing + // layers. Extrusions GCode::_extrude() marks as inside that band print with the + // fan off, whatever overhang, bridge or resume request is pending, as the first + // layers of a flat bed do. Leaving the band falls through to the branches below. + set_fan(0); + fan_speed_change_requests[CoolingLine::TYPE_FORCE_RESUME_FAN] = false; + } else if (fan_speed_change_requests[CoolingLine::TYPE_OVERHANG_FAN_START]){ set_fan(overhang_fan_speed); } else if (fan_speed_change_requests[CoolingLine::TYPE_INTERNAL_BRIDGE_FAN_START]){ // ORCA: Add support for separate internal bridge fan speed control set_fan(internal_bridge_fan_speed); @@ -1072,115 +1090,4 @@ std::string CoolingBuffer::apply_layer_cooldown( return new_gcode; } -// Pure helper: compute the main fan speed for a given effective layer index. -// Mirrors the inline logic in change_extruder_set_fan but is callable from -// per-tag code in apply_belt_band_fan. -int CoolingBuffer::compute_main_fan_speed(int effective_layer_id, float layer_time, - unsigned int extruder_id) const -{ -#define EXTRUDER_CFG(opt) m_config.opt.get_at(extruder_id) - float fan_min_speed = EXTRUDER_CFG(fan_min_speed); - float fan_max_speed = EXTRUDER_CFG(fan_max_speed); - bool reduce_fan_stop_start_freq = EXTRUDER_CFG(reduce_fan_stop_start_freq); - int close_fan_the_first_x_layers = EXTRUDER_CFG(close_fan_the_first_x_layers); - int full_fan_speed_layer = EXTRUDER_CFG(full_fan_speed_layer); - float slow_down_layer_time = float(EXTRUDER_CFG(slow_down_layer_time)); - float fan_cooling_layer_time = float(EXTRUDER_CFG(fan_cooling_layer_time)); -#undef EXTRUDER_CFG - - if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) - close_fan_the_first_x_layers = 1; - - float fan_speed_new = reduce_fan_stop_start_freq ? fan_min_speed : 0.f; - if (effective_layer_id >= close_fan_the_first_x_layers) { - if (layer_time < slow_down_layer_time) { - fan_speed_new = fan_max_speed; - } else if (layer_time < fan_cooling_layer_time) { - double t = (layer_time - slow_down_layer_time) / - (fan_cooling_layer_time - slow_down_layer_time); - fan_speed_new = float(int(floor(t * fan_min_speed + - (1. - t) * fan_max_speed) + 0.5)); - } - if (effective_layer_id + 1 < full_fan_speed_layer) { - float factor = float(effective_layer_id + 1 - close_fan_the_first_x_layers) - / float(full_fan_speed_layer - close_fan_the_first_x_layers); - fan_speed_new = float(std::clamp(int(fan_speed_new * factor + 0.5f), 0, 255)); - } - } else { - fan_speed_new = 0.f; - } - return int(fan_speed_new); -} - -// Belt printers: a layer is a tilted slab, so "the first layer" is not a slicing layer but -// whatever lies within a layer height of the belt. GCode::_extrude() knows each path's -// height above the belt in the slicing frame and tags every change of it with -// ";_BELT_BAND:". This pass turns the tags into part-fan changes -// and strips them. (The G-code itself is in machine coordinates, which is why the band -// is not worked out from the moves here.) -// -// Only the main part-cooling fan is touched; overhang and bridge fans keep the values -// apply_layer_cooldown() gave them. -std::string CoolingBuffer::apply_belt_band_fan(std::string &&gcode_in, float layer_time, unsigned int extruder_at_start) -{ - static constexpr std::string_view band_tag = ";_BELT_BAND:"; - const std::string &gcode = gcode_in; - std::string out; - out.reserve(gcode.size()); - - // Match the PWM floor applied at every other set_fan call in this file. - const unsigned int part_cooling_fan_min_pwm = static_cast(std::max(0, m_config.part_cooling_fan_min_pwm.value)); - unsigned int active_extruder = extruder_at_start; - - const char *p = gcode.data(); - const char *end = p + gcode.size(); - while (p < end) { - const char *line_end = static_cast(std::memchr(p, '\n', end - p)); - if (line_end == nullptr) - line_end = end; - const char *next_line = line_end < end ? line_end + 1 : end; - const std::string_view line(p, line_end - p); - - if (line.size() > band_tag.size() && line.compare(0, band_tag.size(), band_tag) == 0) { - const int eff_idx = std::atoi(std::string(line.substr(band_tag.size())).c_str()); - const bool in_band = eff_idx < std::max(m_config.close_fan_the_first_x_layers.get_at(active_extruder), 1); - int target = this->compute_main_fan_speed(eff_idx, layer_time, active_extruder); - // Clear of the belt and nothing to add: fall back to what the layer asked for. - if (! in_band && target == 0 && m_belt_band_layer_fan >= 0) - target = m_belt_band_layer_fan; - if (target != m_belt_band_fan) { - out += GCodeWriter::set_fan(m_config.gcode_flavor, target, part_cooling_fan_min_pwm); - m_belt_band_fan = target; - } - // The tag itself is dropped. - p = next_line; - continue; - } - - if (! line.empty() && line.front() == 'M') { - // Follow the fan commands of the layer-level cooling. FanMover's parser ignores - // auxiliary and chamber fans (M106 P2, P3...), which must not be taken for the part fan. - const int16_t raw = get_fan_speed(std::string(line), m_config.gcode_flavor); - if (raw >= 0) { - m_belt_band_fan = int(std::lround(double(std::min(raw, 255)) * 100. / 255.)); - m_belt_band_layer_fan = m_belt_band_fan; - } - } else if (line.size() > m_toolchange_prefix.size() && line.compare(0, m_toolchange_prefix.size(), m_toolchange_prefix) == 0) { - char *num_end = nullptr; - const std::string num(line.substr(m_toolchange_prefix.size())); - const long tool = std::strtol(num.c_str(), &num_end, 10); - if (num_end != num.c_str() && tool >= 0) - active_extruder = unsigned(tool); - } - - out.append(p, next_line - p); - p = next_line; - } - - // The fan's real state, so the next layer's cooling re-issues its own speed if it differs. - if (m_belt_band_fan >= 0) - m_current_fan_speed = m_belt_band_fan; - return out; -} - } // namespace Slic3r diff --git a/src/libslic3r/GCode/CoolingBuffer.hpp b/src/libslic3r/GCode/CoolingBuffer.hpp index 0d758b5a5b..e0b5044921 100644 --- a/src/libslic3r/GCode/CoolingBuffer.hpp +++ b/src/libslic3r/GCode/CoolingBuffer.hpp @@ -36,18 +36,6 @@ private: // Returns the adjusted G-code. std::string apply_layer_cooldown(const std::string &gcode, size_t layer_id, float layer_time, std::vector &per_extruder_adjustments); - // Belt printers: turn the ";_BELT_BAND:" tags GCode::_extrude() leaves in the - // layer's G-code into part-fan changes, so the fan follows a path's height above the - // belt rather than the slicing layer index, and strip the tags. - std::string apply_belt_band_fan(std::string &&gcode_in, float layer_time, unsigned int extruder_at_start); - - // Pure helper: compute the main fan speed for a given effective layer - // index (layer-id units, mapped through the plane evaluator) and the - // current extruder. Mirrors the inline logic in the change_extruder_set_fan - // lambda but is callable from per-line code. - int compute_main_fan_speed(int effective_layer_id, float layer_time, - unsigned int extruder_id) const; - // G-code snippet cached for the support layers preceding an object layer. std::string m_gcode; // Internal data. @@ -70,10 +58,9 @@ private: unsigned int m_current_nozzle; //BBS: current fan speed int m_current_fan_speed; - // Belt band pass state, kept across layers. The part fan as this pass last saw or set - // it (percent, -1 unknown), and the last value the layer-level cooling asked for. - int m_belt_band_fan = -1; - int m_belt_band_layer_fan = -1; + // Belt printers: the extrusion being processed lies in the first-layer band above the + // belt (between a ";_BELT_BAND_START" and a ";_BELT_BAND_END"). Kept across layers. + bool m_belt_band_active = false; }; } diff --git a/src/libslic3r/GCode/FanMover.hpp b/src/libslic3r/GCode/FanMover.hpp index 9567e8e2d5..17addd855c 100644 --- a/src/libslic3r/GCode/FanMover.hpp +++ b/src/libslic3r/GCode/FanMover.hpp @@ -27,11 +27,6 @@ public: } }; -// The part-cooling fan value a G-code line sets: the raw S (0..255) of an M106 that -// addresses the part fan, 0 for a fan-off command, -1 for any other line (auxiliary -// and chamber fans included). -int16_t get_fan_speed(const std::string &line, GCodeFlavor flavor); - class FanMover { private: diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 8cdd9e3c33..e9a98324d9 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -20,6 +20,9 @@ #include "test_utils.hpp" #include +#include +#include +#include #include #include #include @@ -628,13 +631,11 @@ TEST_CASE("Belt printers refuse an object taller than the gantry clearance", "[P } } -// On a belt every tilted layer starts on the belt, so "first layer" cooling is a band along -// the belt, not the first slicing layers: the part fan goes off for the paths that start -// within a layer height of the belt and back on above it, on every layer. The G-code is in -// machine coordinates, so the generator tags the band changes and the cooling buffer -// applies them; before that the buffer compared machine-frame moves with a slicing-frame -// plane and never switched the fan at all. -TEST_CASE("Belt printers switch the part fan by height above the belt", "[Print][belt][Cooling]") +// On a belt every tilted layer starts on the belt, so "the first layers" the fan stays off +// for are a band along the belt, not the first slicing layers. The generator marks where +// each extrusion segment enters and leaves that band and the cooling buffer keeps the fan +// off inside it, on every layer. +TEST_CASE("Belt printers keep the part fan off within the band above the belt", "[Print][belt][Cooling]") { DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); config.set_deserialize_strict({ @@ -649,8 +650,8 @@ TEST_CASE("Belt printers switch the part fan by height above the belt", "[Print] { "initial_layer_print_height", 0.2 }, { "skirt_loops", 0 }, { "z_hop", 0 }, - // Three layers: the lowest wall of each tilted layer is centred about 0.3 mm above - // the belt (half a line width in from the contact edge), outside a one-layer band. + // Three layers, 0.6 mm: the lowest wall of each tilted layer is centred about 0.3 mm + // above the belt (half a line width in from the contact edge). { "close_fan_the_first_x_layers", 3 }, { "full_fan_speed_layer", 0 }, { "fan_min_speed", 100 }, @@ -664,24 +665,50 @@ TEST_CASE("Belt printers switch the part fan by height above the belt", "[Print] const std::string gcode = slice({ cube(20) }, config); REQUIRE(! gcode.empty()); - // The tags are consumed by the cooling buffer and never reach the file. + // The markers are consumed by the cooling buffer and never reach the file. CHECK(gcode.find(";_BELT_BAND") == std::string::npos); - // The fan commands in order: '0' off, '1' on. - std::string fan_sequence; - GCodeReader parser; - parser.parse_buffer(gcode, [&](GCodeReader &, const GCodeReader::GCodeLine &line) { - if (line.cmd_is("M107")) - fan_sequence += '0'; - else if (line.cmd_is("M106")) { - float s = 0.f; - fan_sequence += (line.has_value('S', s) && s <= 0.f) ? '0' : '1'; + // With this axis mapping machine Y is the height above the belt along the gantry. Walk + // the moves with the fan state: extrusions that stay within 0.45 mm of the belt are well + // inside the band and must print with the fan off; extrusions that stay 5 mm clear of it + // must print with it on. The first three slicing layers have the fan off altogether. + size_t in_band = 0, in_band_fan_on = 0, clear = 0, clear_fan_off = 0; + int layer = -1; + bool fan_on = false; + double y = 0.; + std::istringstream lines(gcode); + for (std::string line; std::getline(lines, line); ) { + if (boost::starts_with(line, ";LAYER_CHANGE")) { + ++ layer; + } else if (boost::starts_with(line, "M107")) { + fan_on = false; + } else if (boost::starts_with(line, "M106")) { + const size_t s = line.find('S'); + fan_on = s != std::string::npos && std::atof(line.c_str() + s + 1) > 0.; + } else if (boost::starts_with(line, "G1 ")) { + const size_t comment = line.find(';'); + const std::string cmd = line.substr(0, comment); + const size_t ypos = cmd.find(" Y"), epos = cmd.find(" E"); + if (ypos == std::string::npos) + continue; + const double y_new = std::atof(cmd.c_str() + ypos + 2); + const bool extruding = epos != std::string::npos && std::atof(cmd.c_str() + epos + 2) > 0.; + if (extruding && layer >= 3) { + if (std::max(y, y_new) < 0.45) { + ++ in_band; + in_band_fan_on += fan_on; + } else if (std::min(y, y_new) > 5.) { + ++ clear; + clear_fan_off += ! fan_on; + } + } + y = y_new; } - }); - // A flat-bed print turns the fan on once and leaves it on. Here it goes off again for - // paths that start back down at the belt, and on again above it. - INFO("fan sequence: " << fan_sequence); - CHECK(fan_sequence.find("101") != std::string::npos); + } + CHECK(in_band > 20); + CHECK(in_band_fan_on == 0); + CHECK(clear > 20); + CHECK(clear_fan_off == 0); } // Organic supports under an overhang on a belt printer reach below the object's first layer,