#include "../GCode.hpp" #include "GCodeEditor.hpp" #include #include #include #include #include #if 0 #define DEBUG #define _DEBUG #undef NDEBUG #endif #include namespace Slic3r { GCodeEditor::GCodeEditor(GCode &gcodegen) : m_config(gcodegen.config()), m_toolchange_prefix(gcodegen.writer().toolchange_prefix()), m_current_extruder(0) { this->reset(gcodegen.writer().get_position()); const std::vector &extruders = gcodegen.writer().extruders(); m_extruder_ids.reserve(extruders.size()); for (const Extruder &ex : extruders) { m_num_extruders = std::max(ex.id() + 1, m_num_extruders); m_extruder_ids.emplace_back(ex.id()); } } void GCodeEditor::reset(const Vec3d &position) { // 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()); m_current_pos[1] = float(position.y()); m_current_pos[2] = float(position.z()); m_current_pos[4] = float(m_config.travel_speed.get_at(get_extruder_index(m_config, m_current_extruder))); m_fan_speed = -1; m_additional_fan_speed = -1; m_current_fan_speed = -1; } static void record_wall_lines(bool &flag, int &line_idx, PerExtruderAdjustments *adjustment, const std::pair &node_pos) { if (flag && line_idx < adjustment->lines.size()) { CoolingLine &ptr = adjustment->lines[line_idx]; ptr.outwall_smooth_mark = true; ptr.object_id = node_pos.first; ptr.cooling_node_id = node_pos.second; flag = false; } } static void mark_node_pos( bool &flag, int &line_idx, std::pair &node_pos, const std::vector &object_label, int cooling_node_id, int object_id, PerExtruderAdjustments *adjustment) { for (size_t object_idx = 0; object_idx < object_label.size(); ++object_idx) { if (object_label[object_idx] == object_id) { if (cooling_node_id == -1) break; line_idx = adjustment->lines.size(); flag = true; node_pos.first = object_idx; node_pos.second = cooling_node_id; break; } } } std::string GCodeEditor::process_layer(std::string && gcode, const size_t layer_id, std::vector &per_extruder_adjustments, const std::vector & object_label, const bool flush, const bool spiral_vase) { // Cache the input G-code. if (m_gcode.empty()) m_gcode = std::move(gcode); else m_gcode += gcode; std::string out; 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. // record parse gcode info to per_extruder_adjustments per_extruder_adjustments = this->parse_layer_gcode(m_gcode, m_current_pos, object_label, spiral_vase, layer_id > 0); out = m_gcode; m_gcode.clear(); } return out; } //native-resource://sandbox_fs/webcontent/resource/assets/img/41ecc25c56.png // Parse the layer G-code for the moves, which could be adjusted. // Return the list of parsed lines, bucketed by an extruder. std::vector GCodeEditor::parse_layer_gcode( const std::string &gcode, std::vector & current_pos, const std::vector & object_label, bool spiral_vase, bool join_z_smooth) { std::vector per_extruder_adjustments(m_extruder_ids.size()); std::vector map_extruder_to_per_extruder_adjustment(m_num_extruders, 0); for (size_t i = 0; i < m_extruder_ids.size(); ++ i) { PerExtruderAdjustments &adj = per_extruder_adjustments[i]; unsigned int extruder_id = m_extruder_ids[i]; adj.extruder_id = extruder_id; adj.cooling_slow_down_enabled = m_config.slow_down_for_layer_cooling.get_at(extruder_id); adj.slow_down_layer_time = float(m_config.slow_down_layer_time.get_at(extruder_id)); adj.slow_down_min_speed = float(m_config.slow_down_min_speed.get_at(extruder_id)); map_extruder_to_per_extruder_adjustment[extruder_id] = i; } unsigned int current_extruder = m_parse_gcode_extruder; PerExtruderAdjustments *adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; const char *line_start = gcode.c_str(); const char *line_end = line_start; // Index of an existing CoolingLine of the current adjustment, which holds the feedrate setting command // for a sequence of extrusion moves. size_t active_speed_modifier = size_t(-1); int object_id = -1; int cooling_node_id = -1; std::string object_id_string = "; OBJECT_ID: "; std::string cooling_node_label = "; COOLING_NODE: "; bool append_wall_ptr = false; bool append_inner_wall_ptr = false; bool not_join_cooling = false; std::pair node_pos; int line_idx = -1; for (; *line_start != 0; line_start = line_end) { while (*line_end != '\n' && *line_end != 0) ++ line_end; // sline will not contain the trailing '\n'. std::string sline(line_start, line_end); // CoolingLine will contain the trailing '\n'. if (*line_end == '\n') ++ line_end; CoolingLine line(0, line_start - gcode.c_str(), line_end - gcode.c_str()); if (boost::starts_with(sline, "G0 ")) line.type = CoolingLine::TYPE_G0; else if (boost::starts_with(sline, "G1 ")) line.type = CoolingLine::TYPE_G1; else if (boost::starts_with(sline, "G92 ")) line.type = CoolingLine::TYPE_G92; else if (boost::starts_with(sline, "G2 ")) line.type = CoolingLine::TYPE_G2; else if (boost::starts_with(sline, "G3 ")) line.type = CoolingLine::TYPE_G3; //BBS: parse object id & node id else if (boost::starts_with(sline, object_id_string)) { std::string sub = sline.substr(object_id_string.size()); object_id = std::stoi(sub); } else if (boost::starts_with(sline, cooling_node_label)) { std::string sub = sline.substr(cooling_node_label.size()); cooling_node_id = std::stoi(sub); } if (line.type) { // G0, G1 or G92 // Parse the G-code line. std::vector new_pos(current_pos); const char *c = sline.data() + 3; for (;;) { // Skip whitespaces. for (; *c == ' ' || *c == '\t'; ++ c); if (*c == 0 || *c == ';') break; assert(is_decimal_separator_point()); // for atof //BBS: Parse the axis. size_t axis = (*c >= 'X' && *c <= 'Z') ? (*c - 'X') : (*c == 'E') ? 3 : (*c == 'F') ? 4 : (*c == 'I') ? 5 : (*c == 'J') ? 6 : size_t(-1); if (axis != size_t(-1)) { new_pos[axis] = float(atof(++c)); if (axis == 4) { // Convert mm/min to mm/sec. new_pos[4] /= 60.f; if ((line.type & CoolingLine::TYPE_G92) == 0) // This is G0 or G1 line and it sets the feedrate. This mark is used for reducing the duplicate F calls. line.type |= CoolingLine::TYPE_HAS_F; } else if (axis == 5 || axis == 6) { // BBS: get position of arc center new_pos[axis] += current_pos[axis - 5]; } } // Skip this word. for (; *c != ' ' && *c != '\t' && *c != 0; ++ c); } bool external_perimeter = boost::contains(sline, ";_EXTERNAL_PERIMETER"); bool wipe = boost::contains(sline, ";_WIPE"); record_wall_lines(append_inner_wall_ptr, line_idx, adjustment, node_pos); if (wipe) line.type |= CoolingLine::TYPE_WIPE; if (boost::contains(sline, ";_EXTRUDE_SET_SPEED") && !wipe && !not_join_cooling) { line.type |= CoolingLine::TYPE_ADJUSTABLE; active_speed_modifier = adjustment->lines.size(); } record_wall_lines(append_wall_ptr, line_idx, adjustment, node_pos); if (external_perimeter) { line.type |= CoolingLine::TYPE_EXTERNAL_PERIMETER; if (line.type & CoolingLine::TYPE_ADJUSTABLE && join_z_smooth && !spiral_vase) { // BBS: collect outwall info mark_node_pos(append_wall_ptr, line_idx, node_pos, object_label, cooling_node_id, object_id, adjustment); } } if ((line.type & CoolingLine::TYPE_G92) == 0) { //BBS: G0, G1, G2, G3. Calculate the duration. if (m_config.use_relative_e_distances.value) // Reset extruder accumulator. current_pos[3] = 0.f; float dif[4]; for (size_t i = 0; i < 4; ++ i) dif[i] = new_pos[i] - current_pos[i]; float dxy2 = 0; //BBS: support to calculate length of arc if (line.type & CoolingLine::TYPE_G2 || line.type & CoolingLine::TYPE_G3) { Vec3f start(current_pos[0], current_pos[1], 0); Vec3f end(new_pos[0], new_pos[1], 0); Vec3f center(new_pos[5], new_pos[6], 0); bool is_ccw = line.type & CoolingLine::TYPE_G3; float dxy = ArcSegment::calc_arc_length(start, end, center, is_ccw); dxy2 = dxy * dxy; } else { dxy2 = dif[0] * dif[0] + dif[1] * dif[1]; } float dxyz2 = dxy2 + dif[2] * dif[2]; if (dxyz2 > 0.f) { // Movement in xyz, calculate time from the xyz Euclidian distance. line.length = sqrt(dxyz2); } else if (std::abs(dif[3]) > 0.f) { // Movement in the extruder axis. line.length = std::abs(dif[3]); } line.feedrate = new_pos[4]; line.origin_feedrate = new_pos[4]; assert((line.type & CoolingLine::TYPE_ADJUSTABLE) == 0 || line.feedrate > 0.f); if (line.length > 0) line.time = line.length / line.feedrate; if (line.feedrate == 0) line.time = 0; line.time_max = line.time; if ((line.type & CoolingLine::TYPE_ADJUSTABLE) || active_speed_modifier != size_t(-1)) line.time_max = (adjustment->slow_down_min_speed == 0.f) ? FLT_MAX : std::max(line.time, line.length / adjustment->slow_down_min_speed); line.origin_time_max = line.time_max; // BBS: add G2 and G3 support if (active_speed_modifier < adjustment->lines.size() && ((line.type & CoolingLine::TYPE_G1) || (line.type & CoolingLine::TYPE_G2) || (line.type & CoolingLine::TYPE_G3))) { // Inside the ";_EXTRUDE_SET_SPEED" blocks, there must not be a G1 Fxx entry. assert((line.type & CoolingLine::TYPE_HAS_F) == 0); CoolingLine &sm = adjustment->lines[active_speed_modifier]; assert(sm.feedrate > 0.f); sm.length += line.length; sm.time += line.time; if (sm.time_max != FLT_MAX) { if (line.time_max == FLT_MAX) sm.time_max = FLT_MAX; else sm.time_max += line.time_max; sm.origin_time_max = sm.time_max; } // Don't store this line. line.type = 0; } } current_pos = std::move(new_pos); } else if (boost::starts_with(sline, "; Slow Down Start")) { not_join_cooling = true; } else if (boost::starts_with(sline, "; Slow Down End")) { not_join_cooling = false; } else if (boost::starts_with(sline, ";_EXTRUDE_END")) { line.type = CoolingLine::TYPE_EXTRUDE_END; active_speed_modifier = size_t(-1); } else if (boost::starts_with(sline, m_toolchange_prefix)) { unsigned int new_extruder = (unsigned int)atoi(sline.c_str() + m_toolchange_prefix.size()); // Only change extruder in case the number is meaningful. User could provide an out-of-range index through custom gcodes - those shall be ignored. if (new_extruder < map_extruder_to_per_extruder_adjustment.size()) { if (new_extruder != current_extruder) { // Switch the tool. line.type = CoolingLine::TYPE_SET_TOOL; current_extruder = new_extruder; adjustment = &per_extruder_adjustments[map_extruder_to_per_extruder_adjustment[current_extruder]]; } } else { // Only log the error in case of MM printer. Single extruder printers likely ignore any T anyway. if (map_extruder_to_per_extruder_adjustment.size() > 1) BOOST_LOG_TRIVIAL(error) << "CoolingBuffer encountered an invalid toolchange, maybe from a custom gcode: " << sline; } } else if (boost::starts_with(sline, ";_OVERHANG_FAN_START")) { line.type = CoolingLine::TYPE_OVERHANG_FAN_START; } else if (boost::starts_with(sline, ";_OVERHANG_FAN_END")) { line.type = CoolingLine::TYPE_OVERHANG_FAN_END; } else if (boost::starts_with(sline, "G4 ")) { // Parse the wait time. line.type = CoolingLine::TYPE_G4; size_t pos_S = sline.find('S', 3); size_t pos_P = sline.find('P', 3); assert(is_decimal_separator_point()); // for atof line.time = line.time_max = float( (pos_S > 0) ? atof(sline.c_str() + pos_S + 1) : (pos_P > 0) ? atof(sline.c_str() + pos_P + 1) * 0.001 : 0.); line.origin_time_max = line.time_max; } else if (boost::starts_with(sline, ";_FORCE_RESUME_FAN_SPEED")) { line.type = CoolingLine::TYPE_FORCE_RESUME_FAN; } else if (boost::starts_with(sline, ";_SET_FAN_SPEED_CHANGING_LAYER")) { line.type = CoolingLine::TYPE_SET_FAN_CHANGING_LAYER; } else if (boost::starts_with(sline, "M624")) { line.type = CoolingLine::TYPE_OBJECT_START; } else if (boost::starts_with(sline, "M625")) { line.type = CoolingLine::TYPE_OBJECT_END; } if (line.type != 0) adjustment->lines.emplace_back(std::move(line)); } m_parse_gcode_extruder = current_extruder; return per_extruder_adjustments; } // Apply slow down over G-code lines stored in per_extruder_adjustments, enable fan if needed. // Returns the adjusted G-code. std::string GCodeEditor::write_layer_gcode( // Source G-code for the current layer. const std::string &gcode, // ID of the current layer, used to disable fan for the first n layers. size_t layer_id, // Total time of this layer after slow down, used to control the fan. float layer_time, // Per extruder list of G-code lines and their cool down attributes. std::vector &per_extruder_adjustments) { if (gcode.empty()) return gcode; // First sort the adjustment lines by of multiple extruders by their position in the source G-code. std::vector lines; { size_t n_lines = 0; for (const PerExtruderAdjustments &adj : per_extruder_adjustments) n_lines += adj.lines.size(); lines.reserve(n_lines); for (const PerExtruderAdjustments &adj : per_extruder_adjustments) for (const CoolingLine &line : adj.lines) lines.emplace_back(&line); std::sort(lines.begin(), lines.end(), [](const CoolingLine *ln1, const CoolingLine *ln2) { return ln1->line_start < ln2->line_start; } ); } // Second generate the adjusted G-code. std::string new_gcode; new_gcode.reserve(gcode.size() * 2); bool overhang_fan_control= false; int overhang_fan_speed = 0; float pre_start_overhang_fan_time = 0.f; enum class SetFanType { sfChangingLayer = 0, sfChangingFilament, sfImmediatelyApply }; auto change_extruder_set_fan = [this, layer_id, layer_time, &new_gcode, &overhang_fan_control, &overhang_fan_speed, &pre_start_overhang_fan_time](SetFanType type) { #define EXTRUDER_CONFIG(OPT) m_config.OPT.get_at(m_current_extruder) int fan_min_speed = EXTRUDER_CONFIG(fan_min_speed); int fan_speed_new = EXTRUDER_CONFIG(reduce_fan_stop_start_freq) ? fan_min_speed : 0; //BBS int additional_fan_speed_new = EXTRUDER_CONFIG(additional_cooling_fan_speed); int close_fan_the_first_x_layers = EXTRUDER_CONFIG(close_fan_the_first_x_layers); // Is the fan speed ramp enabled? int full_fan_speed_layer = EXTRUDER_CONFIG(full_fan_speed_layer); if (close_fan_the_first_x_layers <= 0 && full_fan_speed_layer > 0) { // When ramping up fan speed from close_fan_the_first_x_layers to full_fan_speed_layer, force close_fan_the_first_x_layers above zero, // so there will be a zero fan speed at least at the 1st layer. close_fan_the_first_x_layers = 1; } if (int(layer_id) >= close_fan_the_first_x_layers) { int fan_max_speed = EXTRUDER_CONFIG(fan_max_speed); float slow_down_layer_time = float(EXTRUDER_CONFIG(slow_down_layer_time)); float fan_cooling_layer_time = float(EXTRUDER_CONFIG(fan_cooling_layer_time)); //BBS: always enable the fan speed interpolation according to layer time //if (EXTRUDER_CONFIG(cooling)) { if (layer_time < slow_down_layer_time) { // Layer time very short. Enable the fan to a full throttle. fan_speed_new = fan_max_speed; } else if (layer_time < fan_cooling_layer_time) { // Layer time quite short. Enable the fan proportionally according to the current layer time. assert(layer_time >= slow_down_layer_time); double t = (layer_time - slow_down_layer_time) / (fan_cooling_layer_time - slow_down_layer_time); fan_speed_new = int(floor(t * fan_min_speed + (1. - t) * fan_max_speed) + 0.5); } //} overhang_fan_speed = EXTRUDER_CONFIG(overhang_fan_speed); if (int(layer_id) >= close_fan_the_first_x_layers && int(layer_id) + 1 < full_fan_speed_layer) { // Ramp up the fan speed from close_fan_the_first_x_layers to full_fan_speed_layer. float factor = float(int(layer_id + 1) - close_fan_the_first_x_layers) / float(full_fan_speed_layer - close_fan_the_first_x_layers); fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255); overhang_fan_speed = std::clamp(int(float(overhang_fan_speed) * factor + 0.5f), 0, 255); } #undef EXTRUDER_CONFIG overhang_fan_control= overhang_fan_speed > fan_speed_new; } else { overhang_fan_control= false; overhang_fan_speed = 0; fan_speed_new = 0; additional_fan_speed_new = 0; } if (fan_speed_new != m_fan_speed) { m_fan_speed = fan_speed_new; //BBS m_current_fan_speed = fan_speed_new; if (type == SetFanType::sfImmediatelyApply) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); else if (type == SetFanType::sfChangingLayer) this->m_set_fan_changing_layer = true; //BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished } //BBS if (additional_fan_speed_new != m_additional_fan_speed) { m_additional_fan_speed = additional_fan_speed_new; if (type == SetFanType::sfImmediatelyApply) new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); else if (type == SetFanType::sfChangingLayer) this->m_set_addition_fan_changing_layer = true; //BBS: don't need to handle change filament, because we are always force to resume fan speed when filament change is finished } //BBS: set fan pre start time value pre_start_overhang_fan_time = overhang_fan_control ? m_config.pre_start_fan_time.get_at(m_current_extruder) : 0.f; }; const char *pos = gcode.c_str(); int current_feedrate = 0; //BBS m_set_fan_changing_layer = false; m_set_addition_fan_changing_layer = false; change_extruder_set_fan(SetFanType::sfChangingLayer); //BBS: start the fan earlier for overhangs float cumulative_time = 0.f; float search_time = 0.f; for (int i = 0,j = 0; i < lines.size(); i++) { const CoolingLine *line = lines[i]; if (pre_start_overhang_fan_time > 0.f && overhang_fan_speed > m_fan_speed) { cumulative_time += line->time; j = jtype & CoolingLine::TYPE_FORCE_RESUME_FAN) { //stop search when find a force resume fan command break; } search_time += line_iter->time; if (line_iter->type & CoolingLine::TYPE_OVERHANG_FAN_START) { m_current_fan_speed = overhang_fan_speed; new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); break; } } } const char *line_start = gcode.c_str() + line->line_start; const char *line_end = gcode.c_str() + line->line_end; if (line_start > pos) new_gcode.append(pos, line_start - pos); if (line->type & CoolingLine::TYPE_SET_TOOL) { unsigned int new_extruder = (unsigned int)atoi(line_start + m_toolchange_prefix.size()); if (new_extruder != m_current_extruder) { m_current_extruder = new_extruder; change_extruder_set_fan(SetFanType::sfChangingFilament); //BBS: will force to resume fan speed when filament change is finished cumulative_time = 0.f; search_time = 0.f; } new_gcode.append(line_start, line_end - line_start); } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_START) { if (overhang_fan_control && m_current_fan_speed < overhang_fan_speed) { //BBS m_current_fan_speed = overhang_fan_speed; new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, overhang_fan_speed); } } else if (line->type & CoolingLine::TYPE_OVERHANG_FAN_END) { if (overhang_fan_control) { //BBS m_current_fan_speed = m_fan_speed; new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); } } else if (line->type & CoolingLine::TYPE_FORCE_RESUME_FAN) { //BBS: force to write a fan speed command again if (m_current_fan_speed != -1) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); if (m_additional_fan_speed != -1) new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); } else if (line->type & CoolingLine::TYPE_SET_FAN_CHANGING_LAYER) { //BBS: check whether fan speed need to changed when change layer if (m_current_fan_speed != -1 && m_set_fan_changing_layer) { new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); m_set_fan_changing_layer = false; } if (m_additional_fan_speed != -1 && m_set_addition_fan_changing_layer) { new_gcode += GCodeWriter::set_additional_fan(m_additional_fan_speed); m_set_addition_fan_changing_layer = false; } } 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)) { // Find the start of a comment, or roll to the end of line. const char *end = line_start; for (; end < line_end && *end != ';'; ++ end); // Find the 'F' word. const char *fpos = strstr(line_start + 2, " F") + 2; int new_feedrate = current_feedrate; // Modify the F word of the current G-code line. bool modify = false; // Remove the F word from the current G-code line. bool remove = false; assert(fpos != nullptr); new_feedrate = line->slowdown ? int(floor(60. * line->feedrate + 0.5)) : atoi(fpos); if (new_feedrate == current_feedrate) { // No need to change the F value. if ((line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) || line->length == 0.) // Feedrate does not change and this line does not move the print head. Skip the complete G-code line including the G-code comment. end = line_end; else // Remove the feedrate from the G0/G1 line. The G-code line may become empty! remove = true; } else if (line->slowdown) { // The F value will be overwritten. modify = true; } else { // The F value is different from current_feedrate, but not slowed down, thus the G-code line will not be modified. // Emit the line without the comment. new_gcode.append(line_start, end - line_start); current_feedrate = new_feedrate; } if (modify || remove) { if (modify) { // Replace the feedrate. new_gcode.append(line_start, fpos - line_start); current_feedrate = new_feedrate; char buf[64]; sprintf(buf, "%d", int(current_feedrate)); new_gcode += buf; } else { // Remove the feedrate word. const char *f = fpos; // Roll the pointer before the 'F' word. for (f -= 2; f > line_start && (*f == ' ' || *f == '\t'); -- f); if ((f - line_start == 1) && *line_start == 'G' && (*f == '1' || *f == '0')) { // BBS: only remain "G1" or "G0" of this line after remove 'F' part, don't save } else { // Append up to the F word, without the trailing whitespace. new_gcode.append(line_start, f - line_start + 1); } } // Skip the non-whitespaces of the F parameter up the comment or end of line. for (; fpos != end && *fpos != ' ' && *fpos != ';' && *fpos != '\n'; ++ fpos); // Append the rest of the line without the comment. if (fpos < end) // The G-code line is not empty yet. Emit the rest of it. new_gcode.append(fpos, end - fpos); else if (remove && new_gcode == "G1") { // The G-code line only contained the F word, now it is empty. Remove it completely including the comments. new_gcode.resize(new_gcode.size() - 2); end = line_end; } } // Process the rest of the line. if (end < line_end) { if (line->type & (CoolingLine::TYPE_ADJUSTABLE | CoolingLine::TYPE_EXTERNAL_PERIMETER | CoolingLine::TYPE_WIPE)) { // Process comments, remove ";_EXTRUDE_SET_SPEED", ";_EXTERNAL_PERIMETER", ";_WIPE" std::string comment(end, line_end); boost::replace_all(comment, ";_EXTRUDE_SET_SPEED", ""); if (line->type & CoolingLine::TYPE_EXTERNAL_PERIMETER) boost::replace_all(comment, ";_EXTERNAL_PERIMETER", ""); if (line->type & CoolingLine::TYPE_WIPE) boost::replace_all(comment, ";_WIPE", ""); new_gcode += comment; } else { // Just attach the rest of the source line. new_gcode.append(end, line_end - end); } } } else if (line->type & CoolingLine::TYPE_OBJECT_START) { new_gcode.append(line_start, line_end - line_start); if (pre_start_overhang_fan_time > 0.f && m_current_fan_speed > m_fan_speed) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_current_fan_speed); } else if (line->type & CoolingLine::TYPE_OBJECT_END) { if (pre_start_overhang_fan_time > 0.f && m_current_fan_speed > m_fan_speed) new_gcode += GCodeWriter::set_fan(m_config.gcode_flavor, m_fan_speed); new_gcode.append(line_start, line_end - line_start); }else { new_gcode.append(line_start, line_end - line_start); } pos = line_end; } const char *gcode_end = gcode.c_str() + gcode.size(); if (pos < gcode_end) new_gcode.append(pos, gcode_end - pos); return new_gcode; } } // namespace Slic3r