#include #include #include #include "../libslic3r.h" #include "../PrintConfig.hpp" #include "../LocalesUtils.hpp" #include "../GCode.hpp" #include "PressureEqualizer.hpp" #include "fast_float/fast_float.h" #include "GCodeWriter.hpp" namespace Slic3r { static const std::string EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:"; static const std::string EXTRUDE_END_TAG = ";_EXTRUDE_END"; static const std::string EXTRUDE_SET_SPEED_TAG = ";_EXTRUDE_SET_SPEED"; static const std::string EXTERNAL_PERIMETER_TAG = ";_EXTERNAL_PERIMETER"; // Maximum segment length to split a long segment if the initial and the final flow rate differ. // Smaller value means a smoother transition between two different flow rates. static constexpr float max_segment_length = 5.f; // For how many GCode lines back will adjust a flow rate from the latest line. // Bigger values affect the GCode export speed a lot, and smaller values could // affect how distant will be propagated a flow rate adjustment. static constexpr int max_look_back_limit = 128; PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value) { // Preallocate some data, so that output_buffer.data() will return an empty string. output_buffer.assign(32, 0); output_buffer_length = 0; output_buffer_prev_length = 0; m_current_extruder = 0; // Zero the position of the XYZE axes + the current feed memset(m_current_pos, 0, sizeof(float) * 5); m_current_extrusion_role = erNone; // Expect the first command to fill the nozzle (deretract). m_retracted = true; // Calculate filamet crossections for the multiple extruders. m_filament_crossections.clear(); for (double r : config.filament_diameter.values) { double a = 0.25f * M_PI * r * r; m_filament_crossections.push_back(float(a)); } // Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min // Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min // Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2 m_max_volumetric_extrusion_rate_slope_positive = float(config.max_volumetric_extrusion_rate_slope_positive.value) * 60.f * 60.f; m_max_volumetric_extrusion_rate_slope_negative = float(config.max_volumetric_extrusion_rate_slope_negative.value) * 60.f * 60.f; for (ExtrusionRateSlope &extrusion_rate_slope : m_max_volumetric_extrusion_rate_slopes) { extrusion_rate_slope.negative = m_max_volumetric_extrusion_rate_slope_negative; extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive; } // Don't regulate the pressure before and after gap-fill and ironing. for (const ExtrusionRole er : {erGapFill, erIroning}) { m_max_volumetric_extrusion_rate_slopes[er].negative = 0; m_max_volumetric_extrusion_rate_slopes[er].positive = 0; } opened_extrude_set_speed_block = false; #ifdef PRESSURE_EQUALIZER_STATISTIC m_stat.reset(); #endif #ifdef PRESSURE_EQUALIZER_DEBUG line_idx = 0; #endif } void PressureEqualizer::process_layer(const std::string &gcode) { if (!gcode.empty()) { const char *gcode_begin = gcode.c_str(); while (*gcode_begin != 0) { // Find end of the line. const char *gcode_end = gcode_begin; // Slic3r always generates end of lines in a Unix style. for (; *gcode_end != 0 && *gcode_end != '\n'; ++gcode_end); m_gcode_lines.emplace_back(); if (!this->process_line(gcode_begin, gcode_end, m_gcode_lines.back())) { // The line has to be forgotten. It contains comment marks, which shall be filtered out of the target g-code. m_gcode_lines.pop_back(); } gcode_begin = gcode_end; if (*gcode_begin == '\n') ++gcode_begin; } assert(!this->opened_extrude_set_speed_block); } } LayerResult PressureEqualizer::process_layer(LayerResult &&input) { const bool is_first_layer = m_layer_results.empty(); const size_t next_layer_first_idx = m_gcode_lines.size(); if (!input.nop_layer_result) { this->process_layer(input.gcode); input.gcode.clear(); // GCode is already processed, so it isn't needed to store it. m_layer_results.emplace(new LayerResult(input)); } if (is_first_layer) // Buffer previous input result and output NOP. return LayerResult::make_nop_layer_result(); // Export previous layer. LayerResult *prev_layer_result = m_layer_results.front(); m_layer_results.pop(); output_buffer_length = 0; output_buffer_prev_length = 0; for (size_t line_idx = 0; line_idx < next_layer_first_idx; ++line_idx) output_gcode_line(line_idx); m_gcode_lines.erase(m_gcode_lines.begin(), m_gcode_lines.begin() + int(next_layer_first_idx)); if (output_buffer_length > 0) prev_layer_result->gcode = std::string(output_buffer.data()); assert(!input.nop_layer_result || m_layer_results.empty()); LayerResult out = *prev_layer_result; delete prev_layer_result; return out; } // Is a white space? static inline bool is_ws(const char c) { return c == ' ' || c == '\t'; } // Is it an end of line? Consider a comment to be an end of line as well. static inline bool is_eol(const char c) { return c == 0 || c == '\r' || c == '\n' || c == ';'; } // Is it a white space or end of line? static inline bool is_ws_or_eol(const char c) { return is_ws(c) || is_eol(c); } // Eat whitespaces. static void eatws(const char *&line) { while (is_ws(*line)) ++ line; } // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. static inline int parse_int(const char *&line) { char *endptr = nullptr; long result = strtol(line, &endptr, 10); if (endptr == nullptr || !is_ws_or_eol(*endptr)) throw Slic3r::InvalidArgument("PressureEqualizer: Error parsing an int"); line = endptr; return int(result); } float string_to_float_decimal_point(const char *line, const size_t str_len, size_t* pos) { float out; size_t p = fast_float::from_chars(line, line + str_len, out).ptr - line; if (pos) *pos = p; return out; } // Parse an int starting at the current position of a line. // If succeeded, the line pointer is advanced. static inline float parse_float(const char *&line, const size_t line_length) { size_t endptr = 0; auto result = string_to_float_decimal_point(line, line_length, &endptr); if (endptr == 0 || !is_ws_or_eol(*(line + endptr))) throw Slic3r::RuntimeError("PressureEqualizer: Error parsing a float"); line = line + endptr; return result; } bool PressureEqualizer::process_line(const char *line, const char *line_end, GCodeLine &buf) { const size_t len = line_end - line; if (strncmp(line, EXTRUSION_ROLE_TAG.data(), EXTRUSION_ROLE_TAG.length()) == 0) { line += EXTRUSION_ROLE_TAG.length(); int role = atoi(line); m_current_extrusion_role = ExtrusionRole(role); #ifdef PRESSURE_EQUALIZER_DEBUG ++line_idx; #endif return false; } // Set the type, copy the line to the buffer. buf.type = GCODELINETYPE_OTHER; buf.modified = false; if (buf.raw.size() < len + 1) buf.raw.assign(line, line + len + 1); else memcpy(buf.raw.data(), line, len); buf.raw[len] = 0; buf.raw_length = len; memcpy(buf.pos_start, m_current_pos, sizeof(float)*5); memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); memset(buf.pos_provided, 0, 5); buf.volumetric_extrusion_rate = 0.f; buf.volumetric_extrusion_rate_start = 0.f; buf.volumetric_extrusion_rate_end = 0.f; buf.max_volumetric_extrusion_rate_slope_positive = 0.f; buf.max_volumetric_extrusion_rate_slope_negative = 0.f; buf.extrusion_role = m_current_extrusion_role; std::string str_line(line, line_end); const bool found_extrude_set_speed_tag = boost::contains(str_line, EXTRUDE_SET_SPEED_TAG); const bool found_extrude_end_tag = boost::contains(str_line, EXTRUDE_END_TAG); assert(!found_extrude_set_speed_tag || !found_extrude_end_tag); if (found_extrude_set_speed_tag) this->opened_extrude_set_speed_block = true; else if (found_extrude_end_tag) this->opened_extrude_set_speed_block = false; // Parse the G-code line, store the result into the buf. switch (toupper(*line ++)) { case 'G': { int gcode = -1; try { gcode = parse_int(line); } catch (Slic3r::InvalidArgument &) { // Ignore invalid GCodes. eatws(line); break; } assert(gcode != -1); eatws(line); switch (gcode) { case 0: case 1: { // G0, G1: A FFF 3D printer does not make a difference between the two. buf.adjustable_flow = this->opened_extrude_set_speed_block; buf.extrude_set_speed_tag = found_extrude_set_speed_tag; buf.extrude_end_tag = found_extrude_end_tag; float new_pos[5]; memcpy(new_pos, m_current_pos, sizeof(float)*5); bool changed[5] = { false, false, false, false, false }; while (!is_eol(*line)) { const char axis = toupper(*line++); int i = -1; switch (axis) { case 'X': case 'Y': case 'Z': i = axis - 'X'; break; case 'E': i = 3; break; case 'F': i = 4; break; default: break; } if (i != -1) { buf.pos_provided[i] = true; new_pos[i] = parse_float(line, line_end - line); if (i == 3 && m_use_relative_e_distances) new_pos[i] += m_current_pos[i]; changed[i] = new_pos[i] != m_current_pos[i]; eatws(line); } } if (changed[3]) { // Extrusion, retract or unretract. float diff = new_pos[3] - m_current_pos[3]; if (diff < 0) { buf.type = GCODELINETYPE_RETRACT; m_retracted = true; } else if (! changed[0] && ! changed[1] && ! changed[2]) { // assert(m_retracted); buf.type = GCODELINETYPE_UNRETRACT; m_retracted = false; } else { assert(changed[0] || changed[1]); // Moving in XY plane. buf.type = GCODELINETYPE_EXTRUDE; // Calculate the volumetric extrusion rate. float diff[4]; for (size_t i = 0; i < 4; ++ i) diff[i] = new_pos[i] - m_current_pos[i]; // volumetric extrusion rate = A_filament * F_xyz * L_e / L_xyz [mm^3/min] float len2 = diff[0]*diff[0]+diff[1]*diff[1]+diff[2]*diff[2]; float rate = m_filament_crossections[m_current_extruder] * new_pos[4] * sqrt((diff[3]*diff[3])/len2); buf.volumetric_extrusion_rate = rate; buf.volumetric_extrusion_rate_start = rate; buf.volumetric_extrusion_rate_end = rate; #ifdef PRESSURE_EQUALIZER_STATISTIC m_stat.update(rate, sqrt(len2)); #endif #ifdef PRESSURE_EQUALIZER_DEBUG if (rate < 40.f) { printf("Extremely low flow rate: %f. Line %d, Length: %f, extrusion: %f Old position: (%f, %f, %f), new position: (%f, %f, %f)\n", rate, int(line_idx), sqrt(len2), sqrt((diff[3] * diff[3]) / len2), m_current_pos[0], m_current_pos[1], m_current_pos[2], new_pos[0], new_pos[1], new_pos[2]); } #endif } } else if (changed[0] || changed[1] || changed[2]) { // Moving without extrusion. buf.type = GCODELINETYPE_MOVE; } memcpy(m_current_pos, new_pos, sizeof(float) * 5); break; } case 92: { // G92 : Set Position // Set a logical coordinate position to a new value without actually moving the machine motors. // Which axes to set? while (!is_eol(*line)) { const char axis = toupper(*line++); switch (axis) { case 'X': case 'Y': case 'Z': m_current_pos[axis - 'X'] = (!is_ws_or_eol(*line)) ? parse_float(line, line_end - line) : 0.f; break; case 'E': m_current_pos[3] = (!is_ws_or_eol(*line)) ? parse_float(line, line_end - line) : 0.f; break; default: break; } eatws(line); } break; } case 10: case 22: // Firmware retract. buf.type = GCODELINETYPE_RETRACT; m_retracted = true; break; case 11: case 23: // Firmware unretract. buf.type = GCODELINETYPE_UNRETRACT; m_retracted = false; break; default: // Ignore the rest. break; } break; } case 'M': { eatws(line); // Ignore the rest of the M-codes. break; } case 'T': { // Activate an extruder head. int new_extruder = -1; try { new_extruder = parse_int(line); } catch (Slic3r::InvalidArgument &) { // Ignore invalid GCodes starting with T. eatws(line); break; } assert(new_extruder != -1); if (new_extruder != int(m_current_extruder)) { m_current_extruder = new_extruder; m_retracted = true; buf.type = GCODELINETYPE_TOOL_CHANGE; } else { buf.type = GCODELINETYPE_NOOP; } break; } } buf.extruder_id = m_current_extruder; memcpy(buf.pos_end, m_current_pos, sizeof(float)*5); adjust_volumetric_rate(); #ifdef PRESSURE_EQUALIZER_DEBUG ++line_idx; #endif return true; } void PressureEqualizer::output_gcode_line(const size_t line_idx) { GCodeLine &line = m_gcode_lines[line_idx]; if (!line.modified) { push_to_output(line.raw.data(), line.raw_length, true); return; } // The line was modified. // Find the comment. const char *comment = line.raw.data(); while (*comment != ';' && *comment != 0) ++comment; if (*comment != ';') comment = nullptr; // Emit the line with lowered extrusion rates. float l = line.dist_xyz(); if (auto nSegments = size_t(ceil(l / max_segment_length)); nSegments == 1) { // Just update this segment. push_line_to_output(line_idx, line.feedrate() * line.volumetric_correction_avg(), comment); } else { bool accelerating = line.volumetric_extrusion_rate_start < line.volumetric_extrusion_rate_end; // Update the initial and final feed rate values. line.pos_start[4] = line.volumetric_extrusion_rate_start * line.pos_end[4] / line.volumetric_extrusion_rate; line.pos_end [4] = line.volumetric_extrusion_rate_end * line.pos_end[4] / line.volumetric_extrusion_rate; float feed_avg = 0.5f * (line.pos_start[4] + line.pos_end[4]); // Limiting volumetric extrusion rate slope for this segment. float max_volumetric_extrusion_rate_slope = accelerating ? line.max_volumetric_extrusion_rate_slope_positive : line.max_volumetric_extrusion_rate_slope_negative; // Total time for the segment, corrected for the possibly lowered volumetric feed rate, // if accelerating / decelerating over the complete segment. float t_total = line.dist_xyz() / feed_avg; // Time of the acceleration / deceleration part of the segment, if accelerating / decelerating // with the maximum volumetric extrusion rate slope. float t_acc = 0.5f * (line.volumetric_extrusion_rate_start + line.volumetric_extrusion_rate_end) / max_volumetric_extrusion_rate_slope; float l_acc = l; float l_steady = 0.f; if (t_acc < t_total) { // One may achieve higher print speeds if part of the segment is not speed limited. l_acc = t_acc * feed_avg; l_steady = l - l_acc; if (l_steady < 0.5f * max_segment_length) { l_acc = l; l_steady = 0.f; } else nSegments = size_t(ceil(l_acc / max_segment_length)); } float pos_start[5]; float pos_end[5]; float pos_end2[4]; memcpy(pos_start, line.pos_start, sizeof(float) * 5); memcpy(pos_end, line.pos_end, sizeof(float) * 5); if (l_steady > 0.f) { // There will be a steady feed segment emitted. if (accelerating) { // Prepare the final steady feed rate segment. memcpy(pos_end2, pos_end, sizeof(float)*4); float t = l_acc / l; for (int i = 0; i < 4; ++ i) { pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; line.pos_provided[i] = true; } } else { // Emit the steady feed rate segment. float t = l_steady / l; for (int i = 0; i < 4; ++ i) { line.pos_end[i] = pos_start[i] + (pos_end[i] - pos_start[i]) * t; line.pos_provided[i] = true; } push_line_to_output(line_idx, pos_start[4], comment); comment = nullptr; float new_pos_start_feedrate = pos_start[4]; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); memcpy(pos_start, line.pos_end, sizeof(float)*5); line.pos_start[4] = new_pos_start_feedrate; pos_start[4] = new_pos_start_feedrate; } } // Split the segment into pieces. for (size_t i = 1; i < nSegments; ++ i) { float t = float(i) / float(nSegments); for (size_t j = 0; j < 4; ++ j) { line.pos_end[j] = pos_start[j] + (pos_end[j] - pos_start[j]) * t; line.pos_provided[j] = true; } // Interpolate the feed rate at the center of the segment. push_line_to_output(line_idx, pos_start[4] + (pos_end[4] - pos_start[4]) * (float(i) - 0.5f) / float(nSegments), comment); comment = nullptr; memcpy(line.pos_start, line.pos_end, sizeof(float)*5); } if (l_steady > 0.f && accelerating) { for (int i = 0; i < 4; ++ i) { line.pos_end[i] = pos_end2[i]; line.pos_provided[i] = true; } push_line_to_output(line_idx, pos_end[4], comment); } else { for (int i = 0; i < 4; ++ i) { line.pos_end[i] = pos_end[i]; line.pos_provided[i] = true; } push_line_to_output(line_idx, pos_end[4], comment); } } } void PressureEqualizer::adjust_volumetric_rate() { if (m_gcode_lines.size() < 2) return; // Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes. size_t fist_line_idx = size_t(std::max(0, int(m_gcode_lines.size()) - max_look_back_limit)); const size_t last_line_idx = m_gcode_lines.size() - 1; size_t line_idx = last_line_idx; if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding()) // Nothing to do, the last move is not extruding. return; std::array feedrate_per_extrusion_role{}; feedrate_per_extrusion_role.fill(std::numeric_limits::max()); feedrate_per_extrusion_role[m_gcode_lines[line_idx].extrusion_role] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start; while (line_idx != fist_line_idx) { size_t idx_prev = line_idx - 1; for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev); if (!m_gcode_lines[idx_prev].extruding()) break; // Don't decelerate before ironing and gap-fill. if (m_gcode_lines[line_idx].extrusion_role == erIroning || m_gcode_lines[line_idx].extrusion_role == erGapFill) { line_idx = idx_prev; continue; } // Volumetric extrusion rate at the start of the succeding segment. float rate_succ = m_gcode_lines[line_idx].volumetric_extrusion_rate_start; // What is the gradient of the extrusion rate between idx_prev and idx? line_idx = idx_prev; GCodeLine &line = m_gcode_lines[line_idx]; for (size_t iRole = 1; iRole < erCount; ++ iRole) { const float &rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative; if (rate_slope == 0 || feedrate_per_extrusion_role[iRole] == std::numeric_limits::max()) continue; // The negative rate is unlimited or the rate for ExtrusionRole iRole is unlimited. float rate_end = feedrate_per_extrusion_role[iRole]; if (iRole == line.extrusion_role && rate_succ < rate_end) // Limit by the succeeding volumetric flow rate. rate_end = rate_succ; if (!line.adjustable_flow || line.extrusion_role == erExternalPerimeter || line.extrusion_role == erGapFill || line.extrusion_role == erBridgeInfill || line.extrusion_role == erIroning) { rate_end = line.volumetric_extrusion_rate_end; } else if (line.volumetric_extrusion_rate_end > rate_end) { line.volumetric_extrusion_rate_end = rate_end; line.max_volumetric_extrusion_rate_slope_negative = rate_slope; line.modified = true; } else if (iRole == line.extrusion_role) { rate_end = line.volumetric_extrusion_rate_end; } else { // Use the original, 'floating' extrusion rate as a starting point for the limiter. } if (line.adjustable_flow) { float rate_start = rate_end + rate_slope * line.time_corrected(); if (rate_start < line.volumetric_extrusion_rate_start) { // Limit the volumetric extrusion rate at the start of this segment due to a segment // of ExtrusionType iRole, which will be extruded in the future. line.volumetric_extrusion_rate_start = rate_start; line.max_volumetric_extrusion_rate_slope_negative = rate_slope; line.modified = true; } } // feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start; // Don't store feed rate for ironing and gap-fill. if (line.extrusion_role != erIroning && line.extrusion_role != erGapFill) feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start; } } feedrate_per_extrusion_role.fill(std::numeric_limits::max()); feedrate_per_extrusion_role[m_gcode_lines[line_idx].extrusion_role] = m_gcode_lines[line_idx].volumetric_extrusion_rate_end; assert(m_gcode_lines[line_idx].extruding()); while (line_idx != last_line_idx) { size_t idx_next = line_idx + 1; for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next); if (!m_gcode_lines[idx_next].extruding()) break; // Don't accelerate after ironing and gap-fill. if (m_gcode_lines[line_idx].extrusion_role == erIroning || m_gcode_lines[line_idx].extrusion_role == erGapFill) { line_idx = idx_next; continue; } float rate_prec = m_gcode_lines[line_idx].volumetric_extrusion_rate_end; // What is the gradient of the extrusion rate between idx_prev and idx? line_idx = idx_next; GCodeLine &line = m_gcode_lines[line_idx]; for (size_t iRole = 1; iRole < erCount; ++ iRole) { const float &rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive; if (rate_slope == 0 || feedrate_per_extrusion_role[iRole] == std::numeric_limits::max()) continue; // The positive rate is unlimited or the rate for ExtrusionRole iRole is unlimited. float rate_start = feedrate_per_extrusion_role[iRole]; if (!line.adjustable_flow || line.extrusion_role == erExternalPerimeter || line.extrusion_role == erGapFill || line.extrusion_role == erBridgeInfill || line.extrusion_role == erIroning) { rate_start = line.volumetric_extrusion_rate_start; } else if (iRole == line.extrusion_role && rate_prec < rate_start) rate_start = rate_prec; if (line.volumetric_extrusion_rate_start > rate_start) { line.volumetric_extrusion_rate_start = rate_start; line.max_volumetric_extrusion_rate_slope_positive = rate_slope; line.modified = true; } else if (iRole == line.extrusion_role) { rate_start = line.volumetric_extrusion_rate_start; } else { // Use the original, 'floating' extrusion rate as a starting point for the limiter. } if (line.adjustable_flow) { float rate_end = rate_start + rate_slope * line.time_corrected(); if (rate_end < line.volumetric_extrusion_rate_end) { // Limit the volumetric extrusion rate at the start of this segment due to a segment // of ExtrusionType iRole, which was extruded before. line.volumetric_extrusion_rate_end = rate_end; line.max_volumetric_extrusion_rate_slope_positive = rate_slope; line.modified = true; } } // feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end; // Don't store feed rate for ironing and gap-fill. if (line.extrusion_role != erIroning && line.extrusion_role != erGapFill) feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end; } } } inline void PressureEqualizer::push_to_output(GCodeG1Formatter &formatter) { return this->push_to_output(formatter.string(), false); } inline void PressureEqualizer::push_to_output(const std::string &text, bool add_eol) { return this->push_to_output(text.data(), text.size(), add_eol); } inline void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol) { // New length of the output buffer content. size_t len_new = output_buffer_length + len + 1; if (add_eol) ++len_new; // Resize the output buffer to a power of 2 higher than the required memory. if (output_buffer.size() < len_new) { size_t v = len_new; // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; output_buffer.resize(v); } // Copy the text to the output. if (len != 0) { memcpy(output_buffer.data() + output_buffer_length, text, len); this->output_buffer_prev_length = this->output_buffer_length; output_buffer_length += len; } if (add_eol) output_buffer[output_buffer_length++] = '\n'; output_buffer[output_buffer_length] = 0; } inline bool is_just_line_with_extrude_set_speed_tag(const std::string &line) { if (line.empty() && !boost::starts_with(line, "G1 ") && !boost::ends_with(line, EXTRUDE_SET_SPEED_TAG)) return false; const char *p_line = line.data() + 3; const char *const line_end = line.data() + line.length() - 1; while (!is_eol(*p_line)) { if (toupper(*p_line++) == 'F') break; else return false; } parse_float(p_line, line_end - p_line); eatws(p_line); p_line += EXTRUDE_SET_SPEED_TAG.length(); return p_line <= line_end && is_eol(*p_line); } void PressureEqualizer::push_line_to_output(const size_t line_idx, const float new_feedrate, const char *comment) { const GCodeLine &line = m_gcode_lines[line_idx]; if (line_idx > 0 && output_buffer_length > 0) { const std::string prev_line_str = std::string(output_buffer.begin() + int(this->output_buffer_prev_length), output_buffer.begin() + int(this->output_buffer_length) + 1); if (is_just_line_with_extrude_set_speed_tag(prev_line_str)) this->output_buffer_length = this->output_buffer_prev_length; // Remove the last line because it only sets the speed for an empty block of g-code lines, so it is useless. else push_to_output(EXTRUDE_END_TAG.data(), EXTRUDE_END_TAG.length(), true); } else push_to_output(EXTRUDE_END_TAG.data(), EXTRUDE_END_TAG.length(), true); GCodeG1Formatter feedrate_formatter; feedrate_formatter.emit_f(new_feedrate); feedrate_formatter.emit_string(std::string(EXTRUDE_SET_SPEED_TAG.data(), EXTRUDE_SET_SPEED_TAG.length())); if (line.extrusion_role == erExternalPerimeter) feedrate_formatter.emit_string(std::string(EXTERNAL_PERIMETER_TAG.data(), EXTERNAL_PERIMETER_TAG.length())); push_to_output(feedrate_formatter); GCodeG1Formatter extrusion_formatter; for (size_t axis_idx = 0; axis_idx < 3; ++axis_idx) if (line.pos_provided[axis_idx]) extrusion_formatter.emit_axis(char('X' + axis_idx), line.pos_end[axis_idx], GCodeFormatter::XYZF_EXPORT_DIGITS); extrusion_formatter.emit_axis('E', m_use_relative_e_distances ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3], GCodeFormatter::E_EXPORT_DIGITS); if (comment != nullptr) extrusion_formatter.emit_string(std::string(comment)); push_to_output(extrusion_formatter); } } // namespace Slic3r