From 2e223551e49ec4d8126b91cf6e5038d2ef191362 Mon Sep 17 00:00:00 2001 From: Scott Mudge <19617165+scottmudge@users.noreply.github.com> Date: Sat, 6 May 2023 01:41:47 -0400 Subject: [PATCH] Fan Speed Delay + Fan Kickstart Feature (#910) Initial commit for fan speed delay, required some changes when porting from SuperSlicer. Co-authored-by: SoftFever --- src/libslic3r/CMakeLists.txt | 2 + src/libslic3r/GCode.cpp | 51 +++- src/libslic3r/GCode.hpp | 4 + src/libslic3r/GCode/FanMover.cpp | 500 +++++++++++++++++++++++++++++++ src/libslic3r/GCode/FanMover.hpp | 95 ++++++ src/libslic3r/Preset.cpp | 1 + src/libslic3r/Print.cpp | 3 + src/libslic3r/PrintConfig.cpp | 29 ++ src/libslic3r/PrintConfig.hpp | 3 + src/slic3r/GUI/Tab.cpp | 7 + 10 files changed, 691 insertions(+), 4 deletions(-) create mode 100644 src/libslic3r/GCode/FanMover.cpp create mode 100644 src/libslic3r/GCode/FanMover.hpp diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index b75bfdd385..64801a68c0 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -119,6 +119,8 @@ set(lisbslic3r_sources GCode/ThumbnailData.hpp GCode/CoolingBuffer.cpp GCode/CoolingBuffer.hpp + GCode/FanMover.cpp + GCode/FanMover.hpp GCode/PostProcessor.cpp GCode/PostProcessor.hpp # GCode/PressureEqualizer.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 55ee0d2c83..a5eabe290b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1443,6 +1443,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato #if ENABLE_GCODE_VIEWER_DATA_CHECKING m_last_mm3_per_mm = 0.; #endif // ENABLE_GCODE_VIEWER_DATA_CHECKING + + m_fan_mover.release(); m_writer.set_is_bbl_machine(is_bbl_printers); @@ -1810,6 +1812,10 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_writer.set_current_position_clear(false); m_start_gcode_filament = GCodeProcessor::get_gcode_last_filament(machine_start_gcode); + //flush FanMover buffer to avoid modifying the start gcode if it's manual. + if (!machine_start_gcode.empty() && this->m_fan_mover.get() != nullptr) + file.write(this->m_fan_mover.get()->process_gcode("", true)); + // Process filament-specific gcode. /* if (has_wipe_tower) { // Wipe tower will control the extruder switching, it will call the filament_start_gcode. @@ -2186,11 +2192,30 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + const auto fan_mover = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { + CNumericLocalesSetter locales_setter; + + if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { + if (fan_mover.get() == nullptr) + fan_mover.reset(new Slic3r::FanMover( + writer, + std::abs((float)config.fan_speedup_time.value), + config.fan_speedup_time.value > 0, + config.use_relative_e_distances.value, + config.fan_speedup_overhangs.value, + (float)config.fan_kickstart.value)); + //flush as it's a whole layer + return fan_mover->process_gcode(in, true); + } + return in; + }); + // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, generator & cooling & fan_mover & output); } // Process all layers of a single object instance (sequential mode) with a parallel pipeline: @@ -2234,11 +2259,29 @@ void GCode::process_layers( [&output_stream](std::string s) { output_stream.write(s); } ); + const auto fan_mover = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, + [&fan_mover = this->m_fan_mover, &config = this->config(), &writer = this->m_writer](std::string in)->std::string { + + if (config.fan_speedup_time.value != 0 || config.fan_kickstart.value > 0) { + if (fan_mover.get() == nullptr) + fan_mover.reset(new Slic3r::FanMover( + writer, + std::abs((float)config.fan_speedup_time.value), + config.fan_speedup_time.value > 0, + config.use_relative_e_distances.value, + config.fan_speedup_overhangs.value, + (float)config.fan_kickstart.value)); + //flush as it's a whole layer + return fan_mover->process_gcode(in, true); + } + return in; + }); + // The pipeline elements are joined using const references, thus no copying is performed. if (m_spiral_vase) - tbb::parallel_pipeline(12, generator & spiral_mode & cooling & output); + tbb::parallel_pipeline(12, generator & spiral_mode & cooling & fan_mover & output); else - tbb::parallel_pipeline(12, generator & cooling & output); + tbb::parallel_pipeline(12, generator & cooling & fan_mover & output); } std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 315e69b511..532efee380 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -10,6 +10,7 @@ #include "PrintConfig.hpp" #include "GCode/AvoidCrossingPerimeters.hpp" #include "GCode/CoolingBuffer.hpp" +#include "GCode/FanMover.hpp" #include "GCode/RetractWhenCrossingPerimeters.hpp" #include "GCode/SpiralVase.hpp" #include "GCode/ToolOrdering.hpp" @@ -491,6 +492,9 @@ private: // Processor GCodeProcessor m_processor; + //some post-processing on the file, with their data class + std::unique_ptr m_fan_mover; + // BBS Print* m_curr_print = nullptr; unsigned int m_toolchange_count; diff --git a/src/libslic3r/GCode/FanMover.cpp b/src/libslic3r/GCode/FanMover.cpp new file mode 100644 index 0000000000..762d251b5b --- /dev/null +++ b/src/libslic3r/GCode/FanMover.cpp @@ -0,0 +1,500 @@ +#include "FanMover.hpp" + +#include "GCodeReader.hpp" + +#include +/* +#include +#include +#include + +#include "../libslic3r.h" +#include "../PrintConfig.hpp" +#include "../Utils.hpp" +#include "Print.hpp" + +#include +*/ + + +namespace Slic3r { + +const std::string& FanMover::process_gcode(const std::string& gcode, bool flush) +{ + m_process_output = ""; + + // recompute buffer time to recover from rounding + m_buffer_time_size = 0; + for (auto& data : m_buffer) m_buffer_time_size += data.time; + + if(!gcode.empty()) + m_parser.parse_buffer(gcode, + [this](GCodeReader& reader, const GCodeReader::GCodeLine& line) { /*m_process_output += line.raw() + "\n";*/ this->_process_gcode_line(reader, line); }); + + if (flush) { + while (!m_buffer.empty()) { + m_process_output += m_buffer.front().raw + "\n"; + remove_from_buffer(m_buffer.begin()); + } + } + + return m_process_output; +} + +bool is_end_of_word(char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0; +} + +float get_axis_value(const std::string& line, char axis) +{ + char match[3] = " X"; + match[1] = axis; + + size_t pos = line.find(match) + 2; + //size_t end = std::min(line.find(' ', pos + 1), line.find(';', pos + 1)); + // Try to parse the numeric value. + const char* c = line.c_str(); + char* pend = nullptr; + errno = 0; + double v = strtod(c + pos, &pend); + if (pend != nullptr && errno == 0 && pend != c) { + // The axis value has been parsed correctly. + return float(v); + } + return NAN; +} + +void change_axis_value(std::string& line, char axis, const float new_value, const int decimal_digits) +{ + + std::ostringstream ss; + ss << std::fixed << std::setprecision(decimal_digits) << new_value; + + char match[3] = " X"; + match[1] = axis; + + size_t pos = line.find(match) + 2; + size_t end = std::min(line.find(' ', pos + 1), line.find(';', pos + 1)); + line = line.replace(pos, end - pos, ss.str()); +} + +int16_t get_fan_speed(const std::string &line, GCodeFlavor flavor) { + if (line.compare(0, 4, "M106") == 0) { + if (flavor == (gcfMach3) || flavor == (gcfMachinekit)) { + return (int16_t)get_axis_value(line, 'P'); + } else { + return (int16_t)get_axis_value(line, 'S'); + } + } else if (line.compare(0, 4, "M127") == 0 || line.compare(0, 4, "M107") == 0) { + return 0; + } else if ((flavor == (gcfMakerWare) || flavor == (gcfSailfish)) && line.compare(0, 4, "M126") == 0) { + return (int16_t)get_axis_value(line, 'T'); + } else { + return -1; + } + +} + +void FanMover::_put_in_middle_G1(std::list::iterator item_to_split, float nb_sec_since_itemtosplit_start, BufferData &&line_to_write) { + assert(item_to_split != m_buffer.end()); + if (nb_sec_since_itemtosplit_start > item_to_split->time * 0.9) { + // doesn't really need to be split, print it after + m_buffer.insert(next(item_to_split), line_to_write); + } else if (nb_sec_since_itemtosplit_start < item_to_split->time * 0.1) { + // doesn't really need to be split, print it before + //will also print before if line_to_split.time == 0 + m_buffer.insert(item_to_split, line_to_write); + } else if (item_to_split->raw.size() > 2 + && item_to_split->raw[0] == 'G' && item_to_split->raw[1] == '1' && item_to_split->raw[2] == ' ') { + float percent = nb_sec_since_itemtosplit_start / item_to_split->time; + BufferData before = *item_to_split; + before.time *= percent; + item_to_split->time *= (1-percent); + if (item_to_split->dx != 0) { + before.dx = item_to_split->dx * percent; + item_to_split->x += before.dx; + item_to_split->dx = item_to_split->dx * (1-percent); + change_axis_value(before.raw, 'X', before.x + before.dx, 3); + } + if (item_to_split->dy != 0) { + before.dy = item_to_split->dy * percent; + item_to_split->y += before.dy; + item_to_split->dy = item_to_split->dy * (1 - percent); + change_axis_value(before.raw, 'Y', before.y + before.dy, 3); + } + if (item_to_split->dz != 0) { + before.dz = item_to_split->dz * percent; + item_to_split->z += before.dz; + item_to_split->dz = item_to_split->dz * (1 - percent); + change_axis_value(before.raw, 'Z', before.z + before.dz, 3); + } + if (item_to_split->de != 0) { + if (relative_e) { + before.de = item_to_split->de * percent; + change_axis_value(before.raw, 'E', before.de, 5); + item_to_split->de = item_to_split->de * (1 - percent); + change_axis_value(item_to_split->raw, 'E', item_to_split->de, 5); + } else { + before.de = item_to_split->de * percent; + item_to_split->e += before.de; + item_to_split->de = item_to_split->de * (1 - percent); + change_axis_value(before.raw, 'E', before.e + before.de, 5); + } + } + //add before then line_to_write, then there is the modified data. + m_buffer.insert(item_to_split, before); + m_buffer.insert(item_to_split, line_to_write); + + } else { + //not a G1, print it before + m_buffer.insert(item_to_split, line_to_write); + } +} + +void FanMover::_print_in_middle_G1(BufferData& line_to_split, float nb_sec, const std::string &line_to_write) { + if (nb_sec < line_to_split.time * 0.1) { + // doesn't really need to be split, print it after + m_process_output += line_to_split.raw + "\n"; + m_process_output += line_to_write + (line_to_write.back() == '\n'?"":"\n"); + } else if (nb_sec > line_to_split.time * 0.9) { + // doesn't really need to be split, print it before + //will also print before if line_to_split.time == 0 + m_process_output += line_to_write + (line_to_write.back() == '\n' ? "" : "\n"); + m_process_output += line_to_split.raw + "\n"; + }else if(line_to_split.raw.size() > 2 + && line_to_split.raw[0] == 'G' && line_to_split.raw[1] == '1' && line_to_split.raw[2] == ' ') { + float percent = nb_sec / line_to_split.time; + std::string before = line_to_split.raw; + std::string& after = line_to_split.raw; + if (line_to_split.dx != 0) { + change_axis_value(before, 'X', line_to_split.x + line_to_split.dx * percent, 3); + } + if (line_to_split.dy != 0) { + change_axis_value(before, 'Y', line_to_split.y + line_to_split.dy * percent, 3); + } + if (line_to_split.dz != 0) { + change_axis_value(before, 'Z', line_to_split.z + line_to_split.dz * percent, 3); + } + if (line_to_split.de != 0) { + if (relative_e) { + change_axis_value(before, 'E', line_to_split.de * percent, 5); + change_axis_value(after, 'E', line_to_split.de * (1 - percent), 5); + } else { + change_axis_value(before, 'E', line_to_split.e + line_to_split.de * percent, 5); + } + } + m_process_output += before + "\n"; + m_process_output += line_to_write + (line_to_write.back() == '\n' ? "" : "\n"); + m_process_output += line_to_split.raw + "\n"; + + } else { + //not a G1, print it before + m_process_output += line_to_write + (line_to_write.back() == '\n' ? "" : "\n"); + m_process_output += line_to_split.raw + "\n"; + } +} + +void FanMover::_remove_slow_fan(int16_t min_speed, float past_sec) { + //erase fan in the buffer -> don't slowdown if you are in the process of step-up. + //we began at the "recent" side , and remove as long as we don't push past_sec to 0 + auto it = m_buffer.begin(); + while (it != m_buffer.end() && past_sec > 0) { + past_sec -= it->time; + if (it->fan_speed >= 0 && it->fan_speed < min_speed){ + //found something that is lower than us + it = remove_from_buffer(it); + + } else { + ++it; + } + } + +} + +std::string FanMover::_set_fan(int16_t speed) { + //const Tool* tool = m_writer.get_tool(m_currrent_extruder < 20 ? m_currrent_extruder : 0); + return GCodeWriter::set_fan(m_writer.config.gcode_flavor.value, speed); +} + + +bool parse_number(const std::string_view sv, int& out) +{ + { + // Legacy conversion, which is costly due to having to make a copy of the string before conversion. + try { + assert(sv.size() < 1024); + assert(sv.data() != nullptr); + std::string str{ sv }; + size_t read = 0; + out = std::stoi(str, &read); + return str.size() == read; + } + catch (...) { + return false; + } + } +} + +//FIXME: add other firmware +// or just create that damn new gcode writer arch +void FanMover::_process_T(const std::string_view command) +{ + if (command.length() > 1) { + int eid = 0; + if (!parse_number(command.substr(1), eid) || eid < 0 || eid > 255) { + GCodeFlavor flavor = m_writer.config.gcode_flavor; + // Specific to the MMU2 V2 (see https://www.help.prusa3d.com/en/article/prusa-specific-g-codes_112173): + if ((flavor == gcfMarlinLegacy || flavor == gcfMarlinFirmware) && (command == "Tx" || command == "Tc" || command == "T?")) + return; + + // T-1 is a valid gcode line for RepRap Firmwares (used to deselects all tools) see https://github.com/prusa3d/PrusaSlicer/issues/5677 + if ((flavor != gcfRepRapFirmware && flavor != gcfRepRapSprinter) || eid != -1) + m_currrent_extruder = static_cast(0); + } else { + m_currrent_extruder = static_cast(eid); + } + } +} + +void FanMover::_process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line) +{ + // processes 'normal' gcode lines + bool need_flush = false; + std::string cmd(line.cmd()); + double time = 0; + int16_t fan_speed = -1; + if (cmd.length() > 1) { + if (line.has_f()) + m_current_speed = line.f() / 60.0f; + switch (::toupper(cmd[0])) { + case 'T': + case 't': + _process_T(cmd); + break; + case 'G': + { + if (::atoi(&cmd[1]) == 1 || ::atoi(&cmd[1]) == 0) { + double distx = line.dist_X(reader); + double disty = line.dist_Y(reader); + double distz = line.dist_Z(reader); + double dist = distx * distx + disty * disty + distz * distz; + if (dist > 0) { + dist = std::sqrt(dist); + time = dist / m_current_speed; + } + } + break; + } + case 'M': + { + fan_speed = get_fan_speed(line.raw(), m_writer.config.gcode_flavor); + if (fan_speed >= 0) { + const auto fan_baseline = 255.0; + fan_speed = 100 * fan_speed / fan_baseline; + //speed change: stop kickstart reverting if any + m_current_kickstart.time = -1; + if (!m_is_custom_gcode) { + // if slow down => put in the queue. if not => + if (m_back_buffer_fan_speed < fan_speed) { + if (nb_seconds_delay > 0 && (!only_overhangs || current_role == ExtrusionRole::erOverhangPerimeter)) { + //don't put this command in the queue + time = -1; + // this M106 need to go in the past + //check if we have ( kickstart and not in slowdown ) + if (kickstart > 0 && fan_speed > m_front_buffer_fan_speed) { + //stop current kickstart , it's not relevant anymore + if (m_current_kickstart.time > 0) { + m_current_kickstart.time = (-1); + } + + //if kickstart + // first erase everything lower that that value + _remove_slow_fan(fan_speed, m_buffer_time_size + 1); + // then erase everything lower that kickstart + _remove_slow_fan(fan_baseline, kickstart); + // print me + if (!m_buffer.empty() && (m_buffer_time_size - m_buffer.front().time * 0.1) > nb_seconds_delay) { + _print_in_middle_G1(m_buffer.front(), m_buffer_time_size - nb_seconds_delay, _set_fan(100));//m_writer.set_fan(100, true)); //FIXME extruder id (or use the gcode writer, but then you have to disable the multi-thread thing + remove_from_buffer(m_buffer.begin()); + } else { + m_process_output += _set_fan(100);//m_writer.set_fan(100, true)); //FIXME extruder id (or use the gcode writer, but then you have to disable the multi-thread thing + } + //write it in the queue if possible + const float kickstart_duration = kickstart * float(fan_speed - m_front_buffer_fan_speed) / 100.f; + float time_count = kickstart_duration; + auto it = m_buffer.begin(); + while (it != m_buffer.end() && time_count > 0) { + time_count -= it->time; + if (time_count< 0) { + //found something that is lower than us + _put_in_middle_G1(it, it->time + time_count, BufferData(std::string(line.raw()), 0, fan_speed, true)); + //found, stop + break; + } + ++it; + } + if (time_count > 0) { + //can't place it in the buffer, use m_current_kickstart + m_current_kickstart.fan_speed = fan_speed; + m_current_kickstart.time = time_count; + m_current_kickstart.raw = line.raw(); + } + m_front_buffer_fan_speed = fan_speed; + } else { + // first erase everything lower that that value + _remove_slow_fan(fan_speed, m_buffer_time_size + 1); + // then write the fan command + if (!m_buffer.empty() && (m_buffer_time_size - m_buffer.front().time * 0.1) > nb_seconds_delay) { + _print_in_middle_G1(m_buffer.front(), m_buffer_time_size - nb_seconds_delay, line.raw()); + remove_from_buffer(m_buffer.begin()); + } else { + m_process_output += line.raw() + "\n"; + } + m_front_buffer_fan_speed = fan_speed; + } + } else { + if (kickstart <= 0) { + //nothing to do + //we don't put time = -1; so it will printed in the buffer as other line are done + } else if (m_current_kickstart.time > 0) { + //cherry-pick this one + if (m_back_buffer_fan_speed >= fan_speed) { + //stop kickstart + m_current_kickstart.time = -1; + //this will print me just after as time >=0 + } else { + // add some duration to the kickstart and use it for me. + float kickstart_duration = kickstart * float(fan_speed - m_back_buffer_fan_speed) / 100.f; + m_current_kickstart.fan_speed = fan_speed; + m_current_kickstart.time += kickstart_duration; + m_current_kickstart.raw = line.raw(); + //i'm printed by the m_current_kickstart + time = -1; + } + } else if(m_back_buffer_fan_speed < fan_speed - 10){ //only kickstart if more than 10% change + //don't write this line, as it will need to be delayed + time = -1; + //get the duration of kickstart + float kickstart_duration = kickstart * float(fan_speed - m_back_buffer_fan_speed) / 100.f; + //if kickstart, write the M106 S[fan_baseline] first + //set the target speed and set the kickstart flag + put_in_buffer(BufferData(_set_fan(100)//m_writer.set_fan(100, true)); //FIXME extruder id (or use the gcode writer, but then you have to disable the multi-thread thing + , 0, fan_speed, true)); + //kickstart! + //m_process_output += m_writer.set_fan(100, true); + //add the normal speed line for the future + m_current_kickstart.fan_speed = fan_speed; + m_current_kickstart.time = kickstart_duration; + m_current_kickstart.raw = line.raw(); + } + } + } + //update back buffer fan speed + m_back_buffer_fan_speed = fan_speed; + } else { + // have to flush the buffer to avoid erasing a fan command. + need_flush = true; + } + } + break; + } + } + } else { + if(!line.raw().empty() && line.raw().front() == ';') + { + if (line.raw().size() > 10 && line.raw().rfind(";TYPE:", 0) == 0) { + // get the type of the next extrusions + std::string extrusion_string = line.raw().substr(6, line.raw().size() - 6); + current_role = ExtrusionEntity::string_to_role(extrusion_string); + } + if (line.raw().size() > 16) { + if (line.raw().rfind("; custom gcode", 0) != std::string::npos) + if (line.raw().rfind("; custom gcode end", 0) != std::string::npos) + m_is_custom_gcode = false; + else + m_is_custom_gcode = true; + } + } + } + + if (time >= 0) { + BufferData& new_data = put_in_buffer(BufferData(line.raw(), time, fan_speed)); + if (line.has(Axis::X)) { + new_data.x = reader.x(); + new_data.dx = line.dist_X(reader); + } + if (line.has(Axis::Y)) { + new_data.y = reader.y(); + new_data.dy = line.dist_Y(reader); + } + if (line.has(Axis::Z)) { + new_data.z = reader.z(); + new_data.dz = line.dist_Z(reader); + } + if (line.has(Axis::E)) { + new_data.e = reader.e(); + if (relative_e) + new_data.de = line.e(); + else + new_data.de = line.dist_E(reader); + } + + if (m_current_kickstart.time > 0 && time > 0) { + m_current_kickstart.time -= time; + if (m_current_kickstart.time < 0) { + //prev is possible because we just do a emplace_back. + _put_in_middle_G1(prev(m_buffer.end()), time + m_current_kickstart.time, BufferData{ m_current_kickstart.raw, 0, m_current_kickstart.fan_speed, true }); + } + } + }/* else { + BufferData& new_data = put_in_buffer(BufferData("; del? "+line.raw(), 0, fan_speed)); + if (line.has(Axis::X)) { + new_data.x = reader.x(); + new_data.dx = line.dist_X(reader); + } + if (line.has(Axis::Y)) { + new_data.y = reader.y(); + new_data.dy = line.dist_Y(reader); + } + if (line.has(Axis::Z)) { + new_data.z = reader.z(); + new_data.dz = line.dist_Z(reader); + } + if (line.has(Axis::E)) { + new_data.e = reader.e(); + if (relative_e) + new_data.de = line.e(); + else + new_data.de = line.dist_E(reader); + } + }*/ + // puts the line back into the gcode + //if buffer too big, flush it. + if (time >= 0) { + while (!m_buffer.empty() && (need_flush || m_buffer_time_size - m_buffer.front().time > nb_seconds_delay - EPSILON) ){ + BufferData& frontdata = m_buffer.front(); + if (frontdata.fan_speed < 0 || frontdata.fan_speed != m_front_buffer_fan_speed || frontdata.is_kickstart) { + if (frontdata.is_kickstart && frontdata.fan_speed < m_front_buffer_fan_speed) { + //you have to slow down! not kickstart! rewrite the fan speed. + m_process_output += _set_fan(frontdata.fan_speed);//m_writer.set_fan(frontdata.fan_speed,true); //FIXME extruder id (or use the gcode writer, but then you have to disable the multi-thread thing + + m_front_buffer_fan_speed = frontdata.fan_speed; + } else { + m_process_output += frontdata.raw + "\n"; + if (frontdata.fan_speed >= 0) { + //note that this is the only place where the fan_speed is set and we print from the buffer, as if the fan_speed >= 0 => time == 0 + //and as this flush all time == 0 lines from the back of the queue... + m_front_buffer_fan_speed = frontdata.fan_speed; + } + } + } + remove_from_buffer(m_buffer.begin()); + } + } + double sum = 0; + for (auto& data : m_buffer) sum += data.time; + assert( std::abs(m_buffer_time_size - sum) < 0.01); +} + +} // namespace Slic3r + diff --git a/src/libslic3r/GCode/FanMover.hpp b/src/libslic3r/GCode/FanMover.hpp new file mode 100644 index 0000000000..f785e4c658 --- /dev/null +++ b/src/libslic3r/GCode/FanMover.hpp @@ -0,0 +1,95 @@ +#ifndef slic3r_GCode_FanMover_hpp_ +#define slic3r_GCode_FanMover_hpp_ + + +#include "../libslic3r.h" +#include "../PrintConfig.hpp" +#include "../ExtrusionEntity.hpp" + +#include "../Point.hpp" +#include "../GCodeReader.hpp" +#include "../GCodeWriter.hpp" +#include + +namespace Slic3r { + +class BufferData { +public: + std::string raw; + float time; + int16_t fan_speed; + bool is_kickstart; + float x = 0, y = 0, z = 0, e = 0; + float dx = 0, dy = 0, dz = 0, de = 0; + BufferData(std::string line, float time = 0, int16_t fan_speed = 0, float is_kickstart = false) : raw(line), time(time), fan_speed(fan_speed), is_kickstart(is_kickstart){ + //avoid double \n + if(!line.empty() && line.back() == '\n') line.pop_back(); + } +}; + +class FanMover +{ +private: + const std::regex regex_fan_speed; + const float nb_seconds_delay; + const bool with_D_option; + const bool relative_e; + const bool only_overhangs; + const float kickstart; + + GCodeReader m_parser{}; + const GCodeWriter& m_writer; + + //current value (at the back of the buffer), when parsing a new line + ExtrusionRole current_role = ExtrusionRole::erCustom; + // in unit/second + double m_current_speed = 1000 / 60.0; + bool m_is_custom_gcode = false; + uint16_t m_currrent_extruder = 0; + + // variable for when you add a line (front of the buffer) + int m_front_buffer_fan_speed = 0; + int m_back_buffer_fan_speed = 0; + BufferData m_current_kickstart{"",-1,0}; + + //buffer + std::list m_buffer; + double m_buffer_time_size = 0; + + // The output of process_layer() + std::string m_process_output; + +public: + FanMover(const GCodeWriter& writer, const float nb_seconds_delay, const bool with_D_option, const bool relative_e, + const bool only_overhangs, const float kickstart) + : regex_fan_speed("S[0-9]+"), + nb_seconds_delay(nb_seconds_delay>0 ? std::max(0.01f,nb_seconds_delay) : 0), + with_D_option(with_D_option) + , relative_e(relative_e), only_overhangs(only_overhangs), kickstart(kickstart), m_writer(writer){} + + // Adds the gcode contained in the given string to the analysis and returns it after removing the workcodes + const std::string& process_gcode(const std::string& gcode, bool flush); + +private: + BufferData& put_in_buffer(BufferData&& data) { + m_buffer_time_size += data.time; + m_buffer.emplace_back(data); + return m_buffer.back(); + } + std::list::iterator remove_from_buffer(std::list::iterator data) { + m_buffer_time_size -= data->time; + return m_buffer.erase(data); + } + // Processes the given gcode line + void _process_gcode_line(GCodeReader& reader, const GCodeReader::GCodeLine& line); + void _process_T(const std::string_view command); + void _put_in_middle_G1(std::list::iterator item_to_split, float nb_sec, BufferData&& line_to_write); + void _print_in_middle_G1(BufferData& line_to_split, float nb_sec, const std::string& line_to_write); + void _remove_slow_fan(int16_t min_speed, float past_sec); + std::string _set_fan(int16_t speed); +}; + +} // namespace Slic3r + + +#endif /* slic3r_GCode_FanMover_hpp_ */ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f5b56227fc..ed14e856e9 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -795,6 +795,7 @@ static std::vector s_Preset_machine_limits_options { static std::vector s_Preset_printer_options { "printer_technology", "printable_area", "bed_exclude_area","bed_custom_texture", "bed_custom_model", "gcode_flavor", + "fan_kickstart", "fan_speedup_time", "fan_speedup_overhangs", "single_extruder_multi_material", "machine_start_gcode", "machine_end_gcode", "before_layer_change_gcode", "layer_change_gcode", "change_filament_gcode", "printer_model", "printer_variant", "printable_height", "extruder_clearance_radius", "extruder_clearance_height_to_lid", "extruder_clearance_height_to_rod", "default_print_profile", "inherits", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index ab57dd13d3..debee90894 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -98,6 +98,9 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n "reduce_fan_stop_start_freq", "fan_cooling_layer_time", "full_fan_speed_layer", + "fan_kickstart", + "fan_speedup_overhangs", + "fan_speedup_time", "filament_colour", "default_filament_colour", "filament_diameter", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 660140c9f0..22e43da4af 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1773,6 +1773,35 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("fan_speedup_time", coFloat); + def->label = L(""); + def->tooltip = L("Start the fan this number of seconds earlier than its target start time (you can use fractional seconds)." + " It assumes infinite acceleration for this time estimation, and will only take into account G1 and G0 moves (arc fitting" + " is unsupported)." + "\nIt won't move fan comands from custom gcodes (they act as a sort of 'barrier')." + "\nIt won't move fan comands into the start gcode if the 'only custom start gcode' is activated." + "\nUse 0 to deactivate."); + def->sidetext = L("s"); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("fan_speedup_overhangs", coBool); + def->label = L("Only overhangs"); + def->tooltip = L("Will only take into account the delay for the cooling of overhangs."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(true)); + + def = this->add("fan_kickstart", coFloat); + def->label = L("Fan kick-start time"); + def->tooltip = L("Emit a max fan speed command for this amount of seconds before reducing to target speed to kick-start the cooling fan." + "\nThis is useful for fans where a low PWM/power may be insufficient to get the fan started spinning from a stop, or to " + "get the fan up to speed faster." + "\nSet to 0 to deactivate."); + def->sidetext = L("s"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0)); + def = this->add("gcode_flavor", coEnum); def->label = L("G-code flavor"); def->tooltip = L("What kind of gcode the printer is compatible with"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index a9be497202..b4dd002413 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -829,6 +829,9 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloats, filament_flow_ratio)) ((ConfigOptionBools, enable_pressure_advance)) ((ConfigOptionFloats, pressure_advance)) + ((ConfigOptionFloat, fan_kickstart)) + ((ConfigOptionBool, fan_speedup_overhangs)) + ((ConfigOptionFloat, fan_speedup_time)) ((ConfigOptionFloats, filament_diameter)) ((ConfigOptionFloats, filament_density)) ((ConfigOptionStrings, filament_type)) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 20b3c6a830..4b4feb1480 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3093,6 +3093,13 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("machine_load_filament_time"); optgroup->append_single_option_line("machine_unload_filament_time"); + optgroup = page->new_optgroup(L("Cooling Fan")); + Line line = Line{ L("Fan speedup time"), optgroup->get_option("fan_speedup_time").opt.tooltip }; + line.append_option(optgroup->get_option("fan_speedup_time")); + line.append_option(optgroup->get_option("fan_speedup_overhangs")); + optgroup->append_line(line); + optgroup->append_single_option_line("fan_kickstart"); + optgroup = page->new_optgroup(L("Extruder Clearance")); optgroup->append_single_option_line("extruder_clearance_radius"); optgroup->append_single_option_line("extruder_clearance_height_to_rod");