diff --git a/resources/profiles/BBL/filament/fdm_filament_common.json b/resources/profiles/BBL/filament/fdm_filament_common.json index 9facb1b736..fcb1d848f9 100644 --- a/resources/profiles/BBL/filament/fdm_filament_common.json +++ b/resources/profiles/BBL/filament/fdm_filament_common.json @@ -102,6 +102,21 @@ "filament_minimal_purge_on_wipe_tower": [ "15" ], + "filament_tower_interface_pre_extrusion_dist": [ + "10" + ], + "filament_tower_interface_pre_extrusion_length": [ + "0" + ], + "filament_tower_ironing_area": [ + "4" + ], + "filament_tower_interface_purge_volume": [ + "20" + ], + "filament_tower_interface_print_temp": [ + "-1" + ], "filament_printable": [ "3" ], @@ -277,4 +292,4 @@ "volumetric_speed_coefficients":[ "0 0 0 0 0 0" ] -} \ No newline at end of file +} diff --git a/resources/profiles/BBL/process/fdm_process_common.json b/resources/profiles/BBL/process/fdm_process_common.json index 90a1c759c4..fc8379e442 100644 --- a/resources/profiles/BBL/process/fdm_process_common.json +++ b/resources/profiles/BBL/process/fdm_process_common.json @@ -93,6 +93,8 @@ "prime_tower_lift_height": "-1", "prime_tower_max_speed": "90", "prime_tower_flat_ironing": "0", + "enable_tower_interface_features": "0", + "enable_tower_interface_cooldown_during_tower": "0", "raft_layers": "0", "reduce_crossing_wall": "0", "reduce_infill_retraction": "1", @@ -170,4 +172,4 @@ "xy_contour_compensation": "0", "xy_hole_compensation": "0", "z_direction_outwall_speed_continuous": "0" -} \ No newline at end of file +} diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 114875609b..f4ac0c498a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -880,7 +880,23 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("old_retract_length_toolchange", new ConfigOptionFloat(old_retract_length_toolchange)); config.set_key_value("new_retract_length_toolchange", new ConfigOptionFloat(new_retract_length_toolchange)); config.set_key_value("old_filament_temp", new ConfigOptionInt(old_filament_temp)); + int interface_temp = full_config.filament_tower_interface_print_temp.get_at(new_filament_id); + if (interface_temp == -1) + interface_temp = full_config.nozzle_temperature_range_high.get_at(new_filament_id); + if (full_config.enable_tower_interface_features && tcr.is_contact) + new_filament_temp = interface_temp; config.set_key_value("new_filament_temp", new ConfigOptionInt(new_filament_temp)); + if (full_config.enable_tower_interface_features && tcr.is_contact) { + auto temps = full_config.nozzle_temperature.values; + if (new_filament_id >= 0 && new_filament_id < (int)temps.size()) + temps[new_filament_id] = interface_temp; + config.set_key_value("temperature", new ConfigOptionInts(temps)); + + auto first_layer_temps = full_config.nozzle_temperature_initial_layer.values; + if (new_filament_id >= 0 && new_filament_id < (int)first_layer_temps.size()) + first_layer_temps[new_filament_id] = interface_temp; + config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps)); + } config.set_key_value("x_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(0))); config.set_key_value("y_after_toolchange", new ConfigOptionFloat(tool_change_start_pos(1))); config.set_key_value("z_after_toolchange", new ConfigOptionFloat(nozzle_pos(2))); @@ -910,6 +926,9 @@ static std::vector get_path_of_change_filament(const Print& print) config.set_key_value("flush_length", new ConfigOptionFloat(purge_length)); config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(is_used_travel_avoid_perimeter)); config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); + config.set_key_value("is_prime_tower_interface", new ConfigOptionBool(tcr.is_contact)); + config.set_key_value("filament_tower_interface_purge_volume", new ConfigOptionFloat(full_config.filament_tower_interface_purge_volume.get_at(new_filament_id))); + config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp)); int flush_count = std::min(g_max_flush_count, (int) std::round(purge_volume / g_purge_volume_one_time)); float flush_unit = purge_length / flush_count; @@ -1144,10 +1163,18 @@ static std::vector get_path_of_change_filament(const Print& print) std::string toolchange_gcode_str; std::string deretraction_str; + int toolchange_temp_override = -1; + int interface_temp = -1; if (tcr.priming || (new_extruder_id >= 0 && needs_toolchange)) { if (is_ramming) gcodegen.m_wipe.reset_path(); // We don't want wiping on the ramming lines. - toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z); // TODO: toolchange_z vs print_z + if (gcodegen.config().enable_tower_interface_features && tcr.is_contact) { + interface_temp = gcodegen.config().filament_tower_interface_print_temp.get_at(new_extruder_id); + if (interface_temp == -1) + interface_temp = gcodegen.config().nozzle_temperature_range_high.get_at(new_extruder_id); + toolchange_temp_override = interface_temp; + } + toolchange_gcode_str = gcodegen.set_extruder(new_extruder_id, tcr.print_z, false, toolchange_temp_override); // TODO: toolchange_z vs print_z if (gcodegen.config().enable_prime_tower) { deretraction_str += gcodegen.writer().travel_to_z(z, "Force restore layer Z", true); Vec3d position{gcodegen.writer().get_position()}; @@ -1157,6 +1184,144 @@ static std::vector get_path_of_change_filament(const Print& print) } } + if (toolchange_temp_override > 0) { + int base_temp = gcodegen.on_first_layer() ? gcodegen.config().nozzle_temperature_initial_layer.get_at(new_extruder_id) + : gcodegen.config().nozzle_temperature.get_at(new_extruder_id); + if (std::abs(tcr.print_z) < EPSILON) + base_temp = gcodegen.config().nozzle_temperature_initial_layer.get_at(new_extruder_id); + const std::string t_token = " T" + std::to_string(new_extruder_id); + std::string out; + out.reserve(toolchange_gcode_str.size()); + size_t pos = 0; + while (pos < toolchange_gcode_str.size()) { + size_t line_end = toolchange_gcode_str.find('\n', pos); + if (line_end == std::string::npos) + line_end = toolchange_gcode_str.size(); + std::string line = toolchange_gcode_str.substr(pos, line_end - pos); + std::string trimmed = line; + trimmed.erase(0, trimmed.find_first_not_of(" \t")); + bool skip_line = false; + if (boost::starts_with(trimmed, "M109")) { + bool matches_extruder = trimmed.find(t_token) != std::string::npos; + if (!matches_extruder) { + size_t t_pos = trimmed.find('T'); + if (t_pos != std::string::npos) { + size_t t_end = trimmed.find_first_not_of("0123456789", t_pos + 1); + const std::string t_val = trimmed.substr(t_pos + 1, t_end == std::string::npos ? std::string::npos : t_end - (t_pos + 1)); + if (!t_val.empty()) { + try { + matches_extruder = std::stoi(t_val) == new_extruder_id; + } catch (...) { + matches_extruder = false; + } + } + } + } + if (matches_extruder) { + size_t s_pos = trimmed.find('S'); + if (s_pos != std::string::npos) { + size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1); + const std::string s_val = trimmed.substr(s_pos + 1, s_end == std::string::npos ? std::string::npos : s_end - (s_pos + 1)); + if (!s_val.empty()) { + try { + skip_line = std::stoi(s_val) == base_temp; + } catch (...) { + skip_line = false; + } + } + } + } + } + if (!skip_line) { + out.append(line); + if (line_end < toolchange_gcode_str.size()) + out.push_back('\n'); + } + pos = line_end + 1; + } + toolchange_gcode_str.swap(out); + } + + if (toolchange_temp_override > 0) { + const std::string preheat_token = "preheat T" + std::to_string(new_extruder_id); + const int preheat_temp = interface_temp > 0 ? interface_temp : toolchange_temp_override; + std::string out; + out.reserve(tcr_rotated_gcode.size()); + size_t pos = 0; + while (pos < tcr_rotated_gcode.size()) { + size_t line_end = tcr_rotated_gcode.find('\n', pos); + if (line_end == std::string::npos) + line_end = tcr_rotated_gcode.size(); + std::string line = tcr_rotated_gcode.substr(pos, line_end - pos); + std::string trimmed = line; + trimmed.erase(0, trimmed.find_first_not_of(" \t")); + const bool is_preheat_line = (trimmed.find(preheat_token) != std::string::npos); + if (is_preheat_line) { + // Preserve early-preheat timing while forcing interface temp for contact toolchanges. + size_t s_pos = trimmed.find('S'); + if (s_pos != std::string::npos) { + size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1); + trimmed.replace(s_pos + 1, + (s_end == std::string::npos ? trimmed.size() : s_end) - (s_pos + 1), + std::to_string(preheat_temp)); + // Reapply left indentation from the original line. + size_t line_prefix = line.find_first_not_of(" \t"); + if (line_prefix != std::string::npos) + line = line.substr(0, line_prefix) + trimmed; + else + line = trimmed; + } + } + out.append(line); + if (line_end < tcr_rotated_gcode.size()) + out.push_back('\n'); + pos = line_end + 1; + } + tcr_rotated_gcode.swap(out); + } + + if (toolchange_temp_override > 0 && interface_temp > 0) { + const std::string t_token = " T" + std::to_string(new_extruder_id); + std::string out; + out.reserve(tcr_rotated_gcode.size()); + size_t pos = 0; + while (pos < tcr_rotated_gcode.size()) { + size_t line_end = tcr_rotated_gcode.find('\n', pos); + if (line_end == std::string::npos) + line_end = tcr_rotated_gcode.size(); + std::string line = tcr_rotated_gcode.substr(pos, line_end - pos); + std::string trimmed = line; + trimmed.erase(0, trimmed.find_first_not_of(" \t")); + bool skip_line = false; + if (boost::starts_with(trimmed, "M109")) { + bool matches_extruder = true; + if (trimmed.find('T') != std::string::npos) + matches_extruder = trimmed.find(t_token) != std::string::npos; + if (matches_extruder) { + size_t s_pos = trimmed.find('S'); + if (s_pos != std::string::npos) { + size_t s_end = trimmed.find_first_not_of("0123456789", s_pos + 1); + const std::string s_val = trimmed.substr(s_pos + 1, s_end == std::string::npos ? std::string::npos : s_end - (s_pos + 1)); + if (!s_val.empty()) { + try { + skip_line = std::stoi(s_val) == interface_temp; + } catch (...) { + skip_line = false; + } + } + } + } + } + if (!skip_line) { + out.append(line); + if (line_end < tcr_rotated_gcode.size()) + out.push_back('\n'); + } + pos = line_end + 1; + } + tcr_rotated_gcode.swap(out); + } + // Insert the toolchange and deretraction gcode into the generated gcode. DynamicConfig config; @@ -7192,7 +7357,7 @@ std::string GCode::retract(bool toolchange, bool is_last_retraction, LiftType li return gcode; } -std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object) +std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bool by_object, int toolchange_temp_override) { int new_extruder_id = get_extruder_id(new_filament_id); if (!m_writer.need_toolchange(new_filament_id)) @@ -7280,6 +7445,8 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo // BBS: if print_z == 0 use first layer temperature if (abs(print_z) < EPSILON) new_filament_temp = m_config.nozzle_temperature_initial_layer.get_at(new_filament_id); + if (toolchange_temp_override > 0) + new_filament_temp = toolchange_temp_override; Vec3d nozzle_pos = m_writer.get_position(); float old_retract_length, old_retract_length_toolchange, wipe_volume; @@ -7372,6 +7539,27 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo dyn_config.set_key_value("travel_point_3_y", new ConfigOptionFloat(float(travel_point_3.y()))); dyn_config.set_key_value("wipe_avoid_perimeter", new ConfigOptionBool(false)); dyn_config.set_key_value("wipe_avoid_pos_x", new ConfigOptionFloat(wipe_avoid_pos_x)); + dyn_config.set_key_value("is_prime_tower_interface", new ConfigOptionBool(false)); + dyn_config.set_key_value("filament_tower_interface_purge_volume", new ConfigOptionFloat(m_config.filament_tower_interface_purge_volume.get_at(new_filament_id))); + { + int interface_temp = m_config.filament_tower_interface_print_temp.get_at(new_filament_id); + if (interface_temp == -1) + interface_temp = m_config.nozzle_temperature_range_high.get_at(new_filament_id); + dyn_config.set_key_value("filament_tower_interface_print_temp", new ConfigOptionInt(interface_temp)); + } + if (toolchange_temp_override > 0) { + auto temps = m_config.nozzle_temperature.values; + if (new_filament_id < temps.size()) + temps[new_filament_id] = toolchange_temp_override; + dyn_config.set_key_value("temperature", new ConfigOptionInts(temps)); + dyn_config.set_key_value("nozzle_temperature", new ConfigOptionInts(temps)); + + auto first_layer_temps = m_config.nozzle_temperature_initial_layer.values; + if (new_filament_id < first_layer_temps.size()) + first_layer_temps[new_filament_id] = toolchange_temp_override; + dyn_config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps)); + dyn_config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(first_layer_temps)); + } auto flush_v_speed = m_print->config().filament_flush_volumetric_speed.values; auto flush_temps =m_print->config().filament_flush_temp.values; @@ -7472,6 +7660,19 @@ std::string GCode::set_extruder(unsigned int new_filament_id, double print_z, bo config.set_key_value("layer_z", new ConfigOptionFloat(this->writer().get_position().z() - m_config.z_offset.value)); config.set_key_value("max_layer_z", new ConfigOptionFloat(m_max_layer_z)); config.set_key_value("filament_extruder_id", new ConfigOptionInt(int(new_filament_id))); + if (toolchange_temp_override > 0) { + auto temps = m_config.nozzle_temperature.values; + if (new_filament_id < temps.size()) + temps[new_filament_id] = toolchange_temp_override; + config.set_key_value("temperature", new ConfigOptionInts(temps)); + config.set_key_value("nozzle_temperature", new ConfigOptionInts(temps)); + + auto first_layer_temps = m_config.nozzle_temperature_initial_layer.values; + if (new_filament_id < first_layer_temps.size()) + first_layer_temps[new_filament_id] = toolchange_temp_override; + config.set_key_value("first_layer_temperature", new ConfigOptionInts(first_layer_temps)); + config.set_key_value("nozzle_temperature_initial_layer", new ConfigOptionInts(first_layer_temps)); + } gcode += this->placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); if (add_change_filament_624) { gcode += "M625\n"; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index b2047bd3aa..30ad964038 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -249,7 +249,7 @@ public: bool needs_retraction(const Polyline& travel, ExtrusionRole role, LiftType& lift_type); std::string retract(bool toolchange = false, bool is_last_retraction = false, LiftType lift_type = LiftType::NormalLift, bool apply_instantly = false, ExtrusionRole role = erNone); std::string unretract() { return m_writer.unlift() + m_writer.unretract(); } - std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false); + std::string set_extruder(unsigned int extruder_id, double print_z, bool by_object=false, int toolchange_temp_override = -1); bool is_BBL_Printer(); bool is_QIDI_Printer(); diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 01e26b890d..8bf39eb715 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -760,11 +760,23 @@ public: } } + const std::string& line_at(size_t idx) const + { + return m_lines[idx].line; + } + + size_t lines_size() const + { + return m_lines.size(); + } + // Insert the gcode lines required by the command cmd by backtracing into the cache - void insert_lines(const Backtrace& backtrace, + bool insert_lines(const Backtrace& backtrace, const std::string& cmd, std::function&)> line_inserter, - std::function line_replacer) + std::function line_replacer, + std::function allow_insert = nullptr, + bool force_insert_last = false) { // Orca: find start pos by seaching G28/G29/PRINT_START/START_PRINT commands auto is_start_pos = [](const std::string& curr_cmd) { @@ -775,6 +787,21 @@ public: const float time_step = backtrace.time_step(); size_t rev_it_dist = 0; // distance from the end of the cache of the starting point of the backtrace float last_time_insertion = 0.0f; // used to avoid inserting two lines at the same time + auto inside_toolchange_block = [this](size_t idx) -> bool { + if (m_lines.empty() || idx >= m_lines.size()) + return false; + // Find last START/END marker before or at idx; inside if last marker is START. + for (size_t i = idx + 1; i-- > 0;) { + const std::string &line = m_lines[i].line; + if (line.find("CP TOOLCHANGE START") != std::string::npos) + return true; + if (line.find("CP TOOLCHANGE END") != std::string::npos) + return false; + } + return false; + }; + + bool inserted = false; for (int i = 0; i < backtrace.steps; ++i) { const float backtrace_time_i = (i + 1) * time_step; const float time_threshold_i = m_times[Normal] - backtrace_time_i; @@ -796,6 +823,23 @@ public: // insert the line for the current step if (rev_it != m_lines.rend() && rev_it != start_rev_it && rev_it->times[Normal] != last_time_insertion) { + // Avoid inserting inside wipe-tower toolchange blocks. + // If the selected point is inside a block, move to the nearest + // earlier line outside the block instead of dropping preheat. + size_t idx = m_lines.size() - 1 - size_t(std::distance(m_lines.rbegin(), rev_it)); + if (inside_toolchange_block(idx)) { + size_t adjusted_idx = idx; + while (adjusted_idx > 0 && inside_toolchange_block(adjusted_idx)) + --adjusted_idx; + if (inside_toolchange_block(adjusted_idx)) + continue; + const size_t adjusted_rev_dist = m_lines.size() - 1 - adjusted_idx; + rev_it = m_lines.rbegin() + adjusted_rev_dist; + idx = adjusted_idx; + } + if (allow_insert && !allow_insert(idx)) { + continue; + } last_time_insertion = rev_it->times[Normal]; std::vector time_diffs; time_diffs.push_back(m_times[Normal] - last_time_insertion); @@ -814,8 +858,39 @@ public: } ++m_added_lines_counter; + inserted = true; } } + + if (!inserted && force_insert_last) { + for (size_t idx = 0; idx < m_lines.size(); ++idx) { + if (inside_toolchange_block(idx)) + continue; + if (allow_insert && !allow_insert(idx)) + continue; + + const LineData &data = m_lines[idx]; + std::vector time_diffs; + time_diffs.push_back(m_times[Normal] - data.times[Normal]); + if (!m_machines[Stealth].g1_times_cache.empty()) + time_diffs.push_back(m_times[Stealth] - data.times[Stealth]); + const std::string out_line = line_inserter(1, time_diffs); + const size_t rev_it_dist = m_lines.size() - idx; + m_lines.insert(m_lines.begin() + idx, {out_line, data.times}); +#ifndef NDEBUG + m_statistics.add_line(out_line.length()); +#endif // NDEBUG + m_size += out_line.length(); + for (auto map_it = m_gcode_lines_map.rbegin(); map_it != m_gcode_lines_map.rbegin() + rev_it_dist - 1; ++map_it) { + ++map_it->second; + } + ++m_added_lines_counter; + inserted = true; + break; + } + } + + return inserted; } // write to file: @@ -1254,12 +1329,169 @@ void GCodeProcessor::run_post_process() if (m_print != nullptr) m_print->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, warning); } + int override_temp = -1; + { + const size_t line_count = export_line.lines_size(); + if (line_count > 0) { + size_t start_idx = 0; + bool in_block = false; + for (size_t i = line_count; i-- > 0;) { + const std::string &line = export_line.line_at(i); + if (line.find("CP TOOLCHANGE END") != std::string::npos) + break; + if (line.find("CP TOOLCHANGE START") != std::string::npos) { + start_idx = i; + in_block = true; + break; + } + } + if (in_block) { + int base_temp = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] : + m_filament_nozzle_temp_first_layer[tool_number]); + for (size_t i = start_idx; i < line_count; ++i) { + const std::string &line = export_line.line_at(i); + if (GCodeReader::GCodeLine::cmd_is(line, "M109")) { + GCodeReader::GCodeLine gline; + GCodeReader reader; + reader.parse_line(line, [&gline](GCodeReader& reader, const GCodeReader::GCodeLine& l) { gline = l; }); + float s_val = 0.f; + if (gline.has_value('S', s_val)) { + float t_val = -1.f; + if (gline.has_value('T', t_val)) { + if (int(t_val) != tool_number) + continue; + } + int temp = int(std::round(s_val)); + if (temp != base_temp) + override_temp = temp; + } + } + } + } + } + } + // Disallow inserting preheat before the last switch away from this tool. + size_t min_insert_idx = 0; + { + const size_t line_count = export_line.lines_size(); + if (line_count > 0) { + for (size_t i = line_count; i-- > 0;) { + const std::string &line = export_line.line_at(i); + std::string cmd = GCodeReader::GCodeLine::extract_cmd(line); + if (cmd.empty()) + continue; + if (cmd[0] == 'T') { + unsigned int id = 0; + auto ret = std::from_chars(cmd.data() + 1, cmd.data() + cmd.size(), id); + if (ret.ec == std::errc()) { + if (static_cast(id) != tool_number) { + min_insert_idx = i; + break; + } + } + } else if (cmd == "M1020") { + size_t pos = line.find("S"); + if (pos != std::string::npos) { + size_t start = pos + 1; + size_t end = line.find_first_not_of("0123456789", start); + const std::string val = line.substr(start, end == std::string::npos ? std::string::npos : end - start); + if (!val.empty()) { + try { + if (std::stoi(val) != tool_number) { + min_insert_idx = i; + break; + } + } catch (...) { + } + } + } + } + } + } + } + + auto allow_insert = [&export_line, tool_number, min_insert_idx](size_t idx) -> bool { + if (idx <= min_insert_idx) + return false; + // Do not insert preheat for a tool that is already active at the insertion point. + const size_t line_count = export_line.lines_size(); + if (idx >= line_count) + return true; + for (size_t i = idx + 1; i-- > 0;) { + const std::string &line = export_line.line_at(i); + std::string cmd = GCodeReader::GCodeLine::extract_cmd(line); + if (cmd.empty()) + continue; + if (cmd[0] == 'T') { + unsigned int id = 0; + auto ret = std::from_chars(cmd.data() + 1, cmd.data() + cmd.size(), id); + if (ret.ec == std::errc()) { + return static_cast(id) != tool_number; + } + } else if (cmd == "M1020") { + size_t pos = line.find("S"); + if (pos != std::string::npos) { + size_t start = pos + 1; + size_t end = line.find_first_not_of("0123456789", start); + const std::string val = line.substr(start, end == std::string::npos ? std::string::npos : end - start); + if (!val.empty()) { + try { + return std::stoi(val) != tool_number; + } catch (...) { + return true; + } + } + } + } + } + return true; + }; + + // Skip preheat insertion if this T command doesn't change the active tool. + { + const size_t line_count = export_line.lines_size(); + if (line_count > 0) { + int last_tool = -1; + for (size_t i = line_count; i-- > 0;) { + const std::string &line = export_line.line_at(i); + std::string cmd = GCodeReader::GCodeLine::extract_cmd(line); + if (cmd.empty()) + continue; + if (cmd[0] == 'T') { + unsigned int id = 0; + auto ret = std::from_chars(cmd.data() + 1, cmd.data() + cmd.size(), id); + if (ret.ec == std::errc()) { + last_tool = static_cast(id); + break; + } + } else if (cmd == "M1020") { + size_t pos = line.find("S"); + if (pos != std::string::npos) { + size_t start = pos + 1; + size_t end = line.find_first_not_of("0123456789", start); + const std::string val = line.substr(start, end == std::string::npos ? std::string::npos : end - start); + if (!val.empty()) { + try { + last_tool = std::stoi(val); + break; + } catch (...) { + } + } + } + } + } + if (last_tool != -1 && last_tool == tool_number) + return; + } + } + export_line.insert_lines( backtrace, cmd, // line inserter - [tool_number, this](unsigned int id, const std::vector& time_diffs) { - const int temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] : - m_filament_nozzle_temp_first_layer[tool_number]); + [tool_number, override_temp, this](unsigned int id, const std::vector& time_diffs) { + const int base_temperature = int(m_layer_id != 1 ? m_filament_nozzle_temp[tool_number] : + m_filament_nozzle_temp_first_layer[tool_number]); + const int temperature = override_temp > 0 ? override_temp : base_temperature; // Orca: M104.1 for XL printers, I can't find the documentation for this so I copied the C++ comments from // Prusa-Firmware-Buddy here /** @@ -1302,7 +1534,11 @@ void GCodeProcessor::run_post_process() } } return line; - } + }, + allow_insert, + // If backtracing can't find a valid insertion point (e.g. dense recurring toolchanges), + // still force one at the earliest allowed line after the last switch-away point. + true ); } } @@ -5897,4 +6133,3 @@ int GCodeProcessor::get_extruder_id(bool force_initialize)const } } /* namespace Slic3r */ - diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 4c6ed404b5..43ee7f92e4 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -16,7 +16,6 @@ namespace Slic3r { -static constexpr float flat_iron_area = 4.f; constexpr float flat_iron_speed = 10.f * 60.f; static const double wipe_tower_wall_infill_overlap = 0.0; static constexpr double WIPE_TOWER_RESOLUTION = 0.1; @@ -1243,7 +1242,8 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, size_t old_tool, bool is_finish, bool is_tool_change, - float purge_volume) const + float purge_volume, + bool is_contact) const { ToolChangeResult result; result.priming = priming; @@ -1260,6 +1260,7 @@ WipeTower::ToolChangeResult WipeTower::construct_tcr(WipeTowerWriter& writer, result.is_finish_first = is_finish; result.nozzle_change_result = m_nozzle_change_result; result.is_tool_change = is_tool_change; + result.is_contact = is_contact; result.tool_change_start_pos = is_tool_change ? result.start_pos : Vec2f(0, 0); // BBS @@ -1283,6 +1284,7 @@ WipeTower::ToolChangeResult WipeTower::construct_block_tcr(WipeTowerWriter &writ result.wipe_path = std::move(writer.wipe_path()); result.is_finish_first = is_finish; result.is_tool_change = false; + result.is_contact = false; result.tool_change_start_pos = Vec2f(0, 0); // BBS result.purge_volume = purge_volume; @@ -1486,7 +1488,9 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi m_used_fillet(config.wipe_tower_fillet_wall.value), m_extra_spacing((float)config.prime_tower_infill_gap.value/100.f), m_tower_framework(config.prime_tower_enable_framework.value), - m_flat_ironing(config.prime_tower_flat_ironing.value) + m_flat_ironing(config.prime_tower_flat_ironing.value), + m_enable_tower_interface_features(config.enable_tower_interface_features.value), + m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value) { m_flat_ironing = (m_flat_ironing && m_use_gap_wall); // Read absolute value of first layer speed, if given as percentage, @@ -1546,6 +1550,16 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].nozzle_temperature = config.nozzle_temperature.get_at(idx); m_filpar[idx].nozzle_temperature_initial_layer = config.nozzle_temperature_initial_layer.get_at(idx); m_filpar[idx].category = config.filament_adhesiveness_category.get_at(idx); + { + int interface_temp = config.filament_tower_interface_print_temp.get_at(idx); + if (interface_temp == -1) + interface_temp = config.nozzle_temperature_range_high.get_at(idx); + m_filpar[idx].interface_print_temperature = interface_temp; + } + m_filpar[idx].tower_interface_pre_extrusion_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx); + m_filpar[idx].tower_interface_pre_extrusion_length = config.filament_tower_interface_pre_extrusion_length.get_at(idx); + m_filpar[idx].tower_ironing_area = config.filament_tower_ironing_area.get_at(idx); + m_filpar[idx].tower_interface_purge_length = config.filament_tower_interface_purge_volume.get_at(idx); // If this is a single extruder MM printer, we will use all the SE-specific config values. // Otherwise, the defaults will be used to turn off the SE stuff. @@ -1608,7 +1622,7 @@ std::vector WipeTower::prime( return std::vector(); } -Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length) +Vec2f WipeTower::get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool) { const float &xl = cleaning_box.ld.x(); const float &xr = cleaning_box.rd.x(); @@ -1740,7 +1754,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per } } - Vec2f initial_position = get_next_pos(cleaning_box, wipe_length); + Vec2f initial_position = get_next_pos(cleaning_box, wipe_length, false, tool); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); if (extrude_perimeter) { @@ -1796,7 +1810,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(size_t tool, bool extrude_per if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, false, true, purge_volume); + return construct_tcr(writer, false, old_tool, false, true, purge_volume, false); } WipeTower::NozzleChangeResult WipeTower::nozzle_change(int old_filament_id, int new_filament_id) @@ -2284,6 +2298,8 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool bool first_layer = is_first_layer(); // BBS: speed up perimeter speed to 90mm/s for non-first layer float feedrate = first_layer ? std::min(m_first_layer_speed * 60.f, 5400.f) : std::min(60.0f * m_filpar[m_current_tool].max_e_speed / m_extrusion_flow, 5400.f); + if (m_enable_tower_interface_features && m_prev_layer_had_interface) + feedrate = std::min(feedrate, 20.f * 60.f); float fill_box_y = m_layer_info->toolchanges_depth() + m_perimeter_width; box_coordinates fill_box(Vec2f(m_perimeter_width, fill_box_y), m_wipe_tower_width - 2 * m_perimeter_width, m_layer_info->depth - fill_box_y); @@ -2426,7 +2442,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, true, false, 0.f); + return construct_tcr(writer, false, old_tool, true, false, 0.f, false); } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box @@ -2646,6 +2662,7 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, { assert(first.new_tool == second.initial_tool); WipeTower::ToolChangeResult out = first; + out.is_contact = first.is_contact || second.is_contact; if ((first.end_pos - second.start_pos).norm() > (float)EPSILON) { std::string travel_gcode = "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) + " F5400" + "\n"; @@ -2769,6 +2786,15 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol } } + bool interface_layer = solid_toolchange && m_enable_tower_interface_features; + if (interface_layer && new_tool < m_filpar.size()) { + float extra_purge_length = m_filpar[new_tool].tower_interface_purge_length; + if (extra_purge_length > 0.f) { + purge_volume += extra_purge_length * m_filpar[new_tool].filament_area; + wipe_length += extra_purge_length; + } + } + WipeTowerBlock* block = get_block_by_category(m_filpar[new_tool].category, false); if (!block) { assert(block != nullptr); @@ -2795,7 +2821,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. if (new_tool != (unsigned int) -1) { // This is not the last change. - Vec2f initial_position = get_next_pos(cleaning_box, wipe_length); + Vec2f initial_position = get_next_pos(cleaning_box, wipe_length, interface_layer, new_tool); writer.set_initial_position(initial_position, m_wipe_tower_width, m_wipe_tower_depth, m_internal_rotation); writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_Start) + "\n"); @@ -2804,6 +2830,23 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol toolchange_Change(writer, new_tool, m_filpar[new_tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); + int base_temp = is_first_layer() ? m_filpar[new_tool].nozzle_temperature_initial_layer : m_filpar[new_tool].nozzle_temperature; + if (interface_layer) { + int interface_temp = m_filpar[new_tool].interface_print_temperature; + if (interface_temp > 0 && interface_temp != base_temp) + writer.set_extruder_temp(interface_temp, true); + if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp) + writer.set_extruder_temp(base_temp, false); + float pre_dist = m_filpar[new_tool].tower_interface_pre_extrusion_dist; + float pre_len = m_filpar[new_tool].tower_interface_pre_extrusion_length; + if (pre_dist > 0.f && pre_len > 0.f) { + bool start_left = (m_cur_layer_id % 4 == 0 || m_cur_layer_id % 4 == 3); + float target_x = writer.x() + (start_left ? pre_dist : -pre_dist); + target_x = std::max(cleaning_box.ld.x(), std::min(cleaning_box.rd.x(), target_x)); + writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f); + } + } + if (m_is_multi_extruder && is_tpu_filament(new_tool)) { float dy = m_layer_info->extra_spacing * m_nozzle_change_perimeter_width; if (m_layer_info->extra_spacing < m_tpu_fixed_spacing) { @@ -2838,6 +2881,13 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol toolchange_wipe_new(writer, cleaning_box, wipe_length, solid_toolchange); + if (interface_layer) { + int base_temp = is_first_layer() ? m_filpar[new_tool].nozzle_temperature_initial_layer : m_filpar[new_tool].nozzle_temperature; + int interface_temp = m_filpar[new_tool].interface_print_temperature; + if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp) + writer.set_extruder_temp(base_temp, false); + } + writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); ++m_num_tool_changes; } else @@ -2859,7 +2909,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change_new(size_t new_tool, bool sol if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, false, true, purge_volume); + return construct_tcr(writer, false, old_tool, false, true, purge_volume, interface_layer); } WipeTower::NozzleChangeResult WipeTower::nozzle_change_new(int old_filament_id, int new_filament_id, bool solid_infill) @@ -3157,7 +3207,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter, m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); m_nozzle_change_result.gcode.clear(); - return construct_tcr(writer, false, m_current_tool, true, false, 0.f); + return construct_tcr(writer, false, m_current_tool, true, false, 0.f, false); } WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block, int filament_id, bool extrude_fill) @@ -3350,6 +3400,8 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat } float retract_length = m_filpar[m_current_tool].retract_length; float retract_speed = m_filpar[m_current_tool].retract_speed * 60; + const float ironing_area = m_filpar[m_current_tool].tower_ironing_area; + const bool do_ironing = m_flat_ironing && (!solid_tool_toolchange || !m_enable_tower_interface_features); const float &xl = cleaning_box.ld.x(); const float &xr = cleaning_box.rd.x(); @@ -3393,10 +3445,10 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat writer.extrude(writer.x() + ironing_length, writer.y(), wipe_speed); writer.retract(retract_length, retract_speed); writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 600.); - if (m_flat_ironing) { + if (do_ironing && ironing_area > 0.f) { writer.travel(writer.x() + 0.5f * ironing_length, writer.y(), 240.); Vec2f pos{writer.x() + 1.f * ironing_length, writer.y()}; - writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); + writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, flat_iron_speed); writer.travel(pos, wipe_speed); } else writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 240.); @@ -3408,10 +3460,10 @@ void WipeTower::toolchange_wipe_new(WipeTowerWriter &writer, const box_coordinat writer.extrude(writer.x() - ironing_length, writer.y(), wipe_speed); writer.retract(retract_length, retract_speed); writer.travel(writer.x() + 1.5 * ironing_length, writer.y(), 600.); - if (m_flat_ironing) { + if (do_ironing && ironing_area > 0.f) { writer.travel(writer.x() - 0.5f * ironing_length, writer.y(), 240.); Vec2f pos{writer.x() - 1.0f * ironing_length, writer.y()}; - writer.spiral_flat_ironing(writer.pos(), flat_iron_area, m_perimeter_width, flat_iron_speed); + writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, flat_iron_speed); writer.travel(pos, wipe_speed); }else writer.travel(writer.x() - 1.5 * ironing_length, writer.y(), 240.); @@ -3837,6 +3889,8 @@ void WipeTower::generate_new(std::vectordepth < m_perimeter_width) continue; @@ -4188,7 +4242,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode) if (!m_no_sparse_layers || toolchanges_on_layer) if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, true, false, 0.f); + return construct_tcr(writer, false, old_tool, true, false, 0.f, false); } Polygon WipeTower::generate_rib_polygon(const box_coordinates &wt_box) diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index d46a38b5ae..e03c543903 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -83,6 +83,7 @@ public: bool priming; bool is_tool_change{false}; + bool is_contact{false}; Vec2f tool_change_start_pos; // Pass a polyline so that normal G-code generator can do a wipe for us. @@ -160,8 +161,9 @@ public: bool priming, size_t old_tool, bool is_finish, - bool is_tool_change, - float purge_volume) const; + bool is_tool_change, + float purge_volume, + bool is_contact = false) const; ToolChangeResult construct_block_tcr(WipeTowerWriter& writer, bool priming, @@ -319,6 +321,7 @@ public: bool is_support = false; int nozzle_temperature = 0; int nozzle_temperature_initial_layer = 0; + int interface_print_temperature = 0; float loading_speed = 0.f; float loading_speed_start = 0.f; float unloading_speed = 0.f; @@ -336,6 +339,10 @@ public: float retract_length; float retract_speed; float wipe_dist; + float tower_interface_pre_extrusion_dist = 0.f; + float tower_interface_pre_extrusion_length = 0.f; + float tower_ironing_area = 4.f; + float tower_interface_purge_length = 0.f; }; @@ -492,6 +499,10 @@ private: std::map m_outer_wall; bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } bool m_flat_ironing=false; + bool m_enable_tower_interface_features=false; + bool m_enable_tower_interface_cooldown_during_tower=false; + bool m_prev_layer_had_interface=false; + bool m_current_layer_has_interface=false; // Calculates length of extrusion line to extrude given volume float volume_to_length(float volume, float line_width, float layer_height) const { return std::max(0.f, volume / (layer_height * (line_width - layer_height * (1.f - float(M_PI) / 4.f)))); @@ -503,7 +514,7 @@ private: // Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental void make_wipe_tower_square(); - Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length); + Vec2f get_next_pos(const WipeTower::box_coordinates &cleaning_box, float wipe_length, bool interface_layer, size_t interface_tool); // Goes through m_plan, calculates border and finish_layer extrusions and subtracts them from last wipe void save_on_last_wipe(); diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index ba22338eba..35131fd474 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -972,6 +972,25 @@ public: return add_wipe_point(Vec2f(x, y)); } + void spiral_flat_ironing(const Vec2f ¢er, float area, float step_length, float feedrate) + { + float edge_length = std::sqrt(area); + Vec2f box_max = center + Vec2f{step_length, step_length}; + Vec2f box_min = center - Vec2f{step_length, step_length}; + int n = std::ceil(edge_length / step_length / 2.f); + if (n <= 0) + return; + while (n--) { + travel(box_max.x(), m_current_pos.y(), feedrate); + travel(m_current_pos.x(), box_max.y(), feedrate); + travel(box_min.x(), m_current_pos.y(), feedrate); + travel(m_current_pos.x(), box_min.y(), feedrate); + + box_max += Vec2f{step_length, step_length}; + box_min -= Vec2f{step_length, step_length}; + } + } + // Extrude with an explicitely provided amount of extrusion. WipeTowerWriter2& extrude_arc_explicit(ArcSegment& arc, float f = 0.f, @@ -1200,7 +1219,8 @@ private: WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer, bool priming, size_t old_tool, - bool is_finish) const + bool is_finish, + bool is_contact) const { WipeTower::ToolChangeResult result; result.priming = priming; @@ -1215,6 +1235,7 @@ WipeTower::ToolChangeResult WipeTower2::construct_tcr(WipeTowerWriter2& writer, result.extrusions = std::move(writer.extrusions()); result.wipe_path = std::move(writer.wipe_path()); result.is_finish_first = is_finish; + result.is_contact = is_contact; // ORCA: Always initialize the tool_change_start_pos with a valid position // to avoid undefined variable travel on X in Gcode.cpp function std::string WipeTowerIntegration::post_process_wipe_tower_moves result.tool_change_start_pos = result.start_pos; // always valid fallback @@ -1250,7 +1271,10 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau m_used_fillet(config.wipe_tower_fillet_wall), m_rib_width(config.wipe_tower_rib_width), m_extra_rib_length(config.wipe_tower_extra_rib_length), - m_wall_type((int)config.wipe_tower_wall_type) + m_wall_type((int)config.wipe_tower_wall_type), + m_flat_ironing(config.prime_tower_flat_ironing.value), + m_enable_tower_interface_features(config.enable_tower_interface_features.value), + m_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value) { // Read absolute value of first layer speed, if given as percentage, // it is taken over following default. Speeds from config are not @@ -1317,6 +1341,16 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config) m_filpar[idx].temperature = config.nozzle_temperature.get_at(idx); m_filpar[idx].first_layer_temperature = config.nozzle_temperature_initial_layer.get_at(idx); m_filpar[idx].filament_minimal_purge_on_wipe_tower = config.filament_minimal_purge_on_wipe_tower.get_at(idx); + { + int interface_temp = config.filament_tower_interface_print_temp.get_at(idx); + if (interface_temp == -1) + interface_temp = config.nozzle_temperature_range_high.get_at(idx); + m_filpar[idx].interface_print_temperature = interface_temp; + } + m_filpar[idx].tower_interface_pre_extrusion_dist = config.filament_tower_interface_pre_extrusion_dist.get_at(idx); + m_filpar[idx].tower_interface_pre_extrusion_length = config.filament_tower_interface_pre_extrusion_length.get_at(idx); + m_filpar[idx].tower_ironing_area = config.filament_tower_ironing_area.get_at(idx); + m_filpar[idx].tower_interface_purge_length = config.filament_tower_interface_purge_volume.get_at(idx); // If this is a single extruder MM printer, we will use all the SE-specific config values. // Otherwise, the defaults will be used to turn off the SE stuff. @@ -1440,11 +1474,11 @@ std::vector WipeTower2::prime( toolchange_Load(writer, cleaning_box); // Prime the tool. if (idx_tool + 1 == tools.size()) { // Last tool should not be unloaded, but it should be wiped enough to become of a pure color. - toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool]); + toolchange_Wipe(writer, cleaning_box, wipe_volumes[tools[idx_tool-1]][tool], false); } else { // Ram the hot material out of the melt zone, retract the filament into the cooling tubes and let it cool. //writer.travel(writer.x(), writer.y() + m_perimeter_width, 7200); - toolchange_Wipe(writer, cleaning_box , 20.f); + toolchange_Wipe(writer, cleaning_box , 20.f, false); WipeTower::box_coordinates box = cleaning_box; box.translate(0.f, writer.y() - cleaning_box.ld.y() + m_perimeter_width); toolchange_Unload(writer, box , m_filpar[m_current_tool].material, m_filpar[m_current_tool].first_layer_temperature, m_filpar[tools[idx_tool + 1]].first_layer_temperature); @@ -1472,7 +1506,7 @@ std::vector WipeTower2::prime( "\n\n"); } - results.emplace_back(construct_tcr(writer, true, old_tool, true)); + results.emplace_back(construct_tcr(writer, true, old_tool, true, false)); } m_old_temperature = -1; // If the priming is turned off in config, the temperature changing commands will not actually appear @@ -1487,6 +1521,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) float wipe_area = 0.f; float wipe_volume = 0.f; + bool interface_layer = m_enable_tower_interface_features && m_current_layer_has_interface; // Finds this toolchange info if (tool != (unsigned int)(-1)) @@ -1501,6 +1536,12 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) else { // Otherwise we are going to Unload only. And m_layer_info would be invalid. } + if (interface_layer && tool != (unsigned int)(-1) && tool < m_filpar.size()) { + float extra_purge_length = m_filpar[tool].tower_interface_purge_length; + if (extra_purge_length > 0.f) { + wipe_volume += extra_purge_length * m_filpar[tool].filament_area; + } + } WipeTower::box_coordinates cleaning_box( Vec2f(m_perimeter_width / 2.f, m_perimeter_width / 2.f), @@ -1542,7 +1583,27 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials. toolchange_Load(writer, cleaning_box); writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road - toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area. + int base_temp = is_first_layer() ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature; + if (interface_layer) { + int interface_temp = m_filpar[tool].interface_print_temperature; + if (interface_temp > 0 && interface_temp != base_temp) + writer.set_extruder_temp(interface_temp, true); + if (m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp) + writer.set_extruder_temp(base_temp, false); + float pre_dist = m_filpar[tool].tower_interface_pre_extrusion_dist; + float pre_len = m_filpar[tool].tower_interface_pre_extrusion_length; + if (pre_dist > 0.f && pre_len > 0.f) { + float target_x = writer.x() + pre_dist; + target_x = std::max(cleaning_box.ld.x(), std::min(cleaning_box.rd.x(), target_x)); + writer.extrude_explicit(target_x, writer.y(), pre_len, 600.f); + } + } + toolchange_Wipe(writer, cleaning_box, wipe_volume, interface_layer); // Wipe the newly loaded filament until the end of the assigned wipe area. + if (interface_layer) { + int interface_temp = m_filpar[tool].interface_print_temperature; + if (!m_enable_tower_interface_cooldown_during_tower && interface_temp > 0 && interface_temp != base_temp) + writer.set_extruder_temp(base_temp, false); + } writer.append(";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Tower_End) + "\n"); ++ m_num_tool_changes; } else @@ -1564,7 +1625,7 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool) if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); - return construct_tcr(writer, false, old_tool, false); + return construct_tcr(writer, false, old_tool, false, interface_layer); } @@ -1854,7 +1915,8 @@ void WipeTower2::toolchange_Load( void WipeTower2::toolchange_Wipe( WipeTowerWriter2 &writer, const WipeTower::box_coordinates &cleaning_box, - float wipe_volume) + float wipe_volume, + bool interface_layer) { // Increase flow on first layer, slow down print. writer.set_extrusion_flow(m_extrusion_flow * (is_first_layer() ? 1.18f : 1.f)) @@ -1884,6 +1946,9 @@ void WipeTower2::toolchange_Wipe( m_left_to_right = !m_left_to_right; } + const bool do_ironing = m_flat_ironing && (!interface_layer || !m_enable_tower_interface_features); + const float ironing_area = m_filpar[m_current_tool].tower_ironing_area; + // now the wiping itself: for (int i = 0; true; ++i) { if (i!=0) { @@ -1899,6 +1964,11 @@ void WipeTower2::toolchange_Wipe( else writer.extrude(xl + (i % 4 == 1 ? 0 : 1.5f*line_width), writer.y(), wipe_speed); + if (i == 0 && do_ironing && ironing_area > 0.f) { + writer.travel(writer.x(), writer.y(), 600.f); + writer.spiral_flat_ironing(writer.pos(), ironing_area, m_perimeter_width, 10.f * 60.f); + } + if (writer.y()+float(EPSILON) > cleaning_box.lu.y()-0.5f*line_width) break; // in case next line would not fit @@ -1943,10 +2013,12 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() .set_y_shift(m_y_shift - (m_current_shape == SHAPE_REVERSED ? m_layer_info->toolchanges_depth() : 0.f)); - // Slow down on the 1st layer. + // Slow down on the 1st layer. // If spare layers are excluded -> if 1 or less toolchange has been done, it must be still the first layer, too. So slow down. bool first_layer = is_first_layer() || (m_num_tool_changes <= 1 && m_no_sparse_layers); float feedrate = first_layer ? m_first_layer_speed * 60.f : std::min(m_wipe_tower_max_purge_speed * 60.f, m_infill_speed * 60.f); + if (m_enable_tower_interface_features && m_prev_layer_had_interface) + feedrate = std::min(feedrate, 20.f * 60.f); float current_depth = m_layer_info->depth - m_layer_info->toolchanges_depth(); WipeTower::box_coordinates fill_box(Vec2f(m_perimeter_width, m_layer_info->depth-(current_depth-m_perimeter_width)), m_wipe_tower_width - 2 * m_perimeter_width, current_depth-m_perimeter_width); @@ -2077,7 +2149,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() m_current_height += m_layer_info->height; } - return construct_tcr(writer, false, old_tool, true); + return construct_tcr(writer, false, old_tool, true, false); } // Static method to get the radius and x-scaling of the stabilizing cone base. @@ -2245,6 +2317,7 @@ static WipeTower::ToolChangeResult merge_tcr(WipeTower::ToolChangeResult& first, { assert(first.new_tool == second.initial_tool); WipeTower::ToolChangeResult out = first; + out.is_contact = first.is_contact || second.is_contact; if (first.end_pos != second.start_pos) out.gcode += "G1 X" + Slic3r::float_to_string_decimal_point(second.start_pos.x(), 3) + " Y" + Slic3r::float_to_string_decimal_point(second.start_pos.y(), 3) diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp index f6b01c8d9d..7c40e0eb4a 100644 --- a/src/libslic3r/GCode/WipeTower2.hpp +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -31,7 +31,8 @@ public: WipeTower::ToolChangeResult construct_tcr(WipeTowerWriter2& writer, bool priming, size_t old_tool, - bool is_finish) const; + bool is_finish, + bool is_contact = false) const; // x -- x coordinates of wipe tower in mm ( left bottom corner ) // y -- y coordinates of wipe tower in mm ( left bottom corner ) @@ -88,11 +89,13 @@ public: m_layer_height = layer_height; m_depth_traversed = 0.f; m_current_layer_finished = false; + m_prev_layer_had_interface = m_current_layer_has_interface; // Advance m_layer_info iterator, making sure we got it right while (!m_plan.empty() && m_layer_info->z < print_z - WT_EPSILON && m_layer_info+1 != m_plan.end()) ++m_layer_info; + m_current_layer_has_interface = (m_layer_info != m_plan.end()) && (m_layer_info->toolchanges_depth() > WT_EPSILON); //m_current_shape = (! this->is_first_layer() && m_current_shape == SHAPE_NORMAL) ? SHAPE_REVERSED : SHAPE_NORMAL; m_current_shape = SHAPE_NORMAL; @@ -145,6 +148,7 @@ public: bool is_soluble = false; int temperature = 0; int first_layer_temperature = 0; + int interface_print_temperature = 0; float loading_speed = 0.f; float loading_speed_start = 0.f; float unloading_speed = 0.f; @@ -168,6 +172,10 @@ public: float filament_minimal_purge_on_wipe_tower = 0.f; float retract_length; float retract_speed; + float tower_interface_pre_extrusion_dist = 0.f; + float tower_interface_pre_extrusion_length = 0.f; + float tower_ironing_area = 4.f; + float tower_interface_purge_length = 0.f; }; private: @@ -208,6 +216,11 @@ private: float m_perimeter_speed = 0.f; float m_first_layer_speed = 0.f; size_t m_first_layer_idx = size_t(-1); + bool m_flat_ironing = false; + bool m_enable_tower_interface_features = false; + bool m_enable_tower_interface_cooldown_during_tower = false; + bool m_prev_layer_had_interface = false; + bool m_current_layer_has_interface = false; int m_wall_type; bool m_used_fillet = true; @@ -335,7 +348,8 @@ private: void toolchange_Wipe( WipeTowerWriter2 &writer, const WipeTower::box_coordinates &cleaning_box, - float wipe_volume); + float wipe_volume, + bool interface_layer); Polygon generate_support_rib_wall(WipeTowerWriter2& writer, diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 4656665a18..7cb0569cff 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -923,6 +923,8 @@ static std::vector s_Preset_print_options { "prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "prime_volume", "prime_tower_infill_gap", "prime_tower_flat_ironing", + "enable_tower_interface_features", + "enable_tower_interface_cooldown_during_tower", "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", "flush_into_infill", "flush_into_objects", "flush_into_support", "tree_support_branch_angle", "tree_support_angle_slow", "tree_support_wall_count", "tree_support_top_rate", "tree_support_branch_distance", "tree_support_tip_diameter", @@ -959,6 +961,8 @@ static std::vector s_Preset_filament_options {/*"filament_colour", "filament_soluble", "filament_is_support", "filament_printable", "filament_max_volumetric_speed", "filament_adaptive_volumetric_speed", "filament_flow_ratio", "filament_density", "filament_adhesiveness_category", "filament_cost", "filament_minimal_purge_on_wipe_tower", + "filament_tower_interface_pre_extrusion_dist", "filament_tower_interface_pre_extrusion_length", "filament_tower_ironing_area", "filament_tower_interface_purge_volume", + "filament_tower_interface_print_temp", "nozzle_temperature", "nozzle_temperature_initial_layer", // BBS "cool_plate_temp", "textured_cool_plate_temp", "eng_plate_temp", "hot_plate_temp", "textured_plate_temp", "cool_plate_temp_initial_layer", "textured_cool_plate_temp_initial_layer", "eng_plate_temp_initial_layer", "hot_plate_temp_initial_layer", "textured_plate_temp_initial_layer", "supertack_plate_temp_initial_layer", "supertack_plate_temp", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 0e35f7fc8a..b942eed4d1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -317,6 +317,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "prime_tower_brim_width" || opt_key == "prime_tower_skip_points" || opt_key == "prime_tower_flat_ironing" + || opt_key == "enable_tower_interface_features" || opt_key == "first_layer_print_sequence" || opt_key == "other_layers_print_sequence" || opt_key == "other_layers_print_sequence_nums" @@ -324,6 +325,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_map_mode" || opt_key == "filament_map" || opt_key == "filament_adhesiveness_category" + || opt_key == "filament_tower_interface_pre_extrusion_dist" + || opt_key == "filament_tower_interface_pre_extrusion_length" + || opt_key == "filament_tower_ironing_area" + || opt_key == "filament_tower_interface_purge_volume" + || opt_key == "filament_tower_interface_print_temp" || opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_extra_flow" || opt_key == "wipe_tower_no_sparse_layers" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ee96ce605f..4db7db98be 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2587,6 +2587,46 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 15. }); + def = this->add("filament_tower_interface_pre_extrusion_dist", coFloats); + def->label = L("Interface layer pre-extrusion distance"); + def->tooltip = L("Pre-extrusion distance for prime tower interface layer (where different materials meet)."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 10. }); + + def = this->add("filament_tower_interface_pre_extrusion_length", coFloats); + def->label = L("Interface layer pre-extrusion length"); + def->tooltip = L("Pre-extrusion length for prime tower interface layer (where different materials meet)."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("filament_tower_ironing_area", coFloats); + def->label = L("Tower ironing area"); + def->tooltip = L("Ironing area for prime tower interface layer (where different materials meet)."); + def->sidetext = L("mm²"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 4. }); + + def = this->add("filament_tower_interface_purge_volume", coFloats); + def->label = L("Interface layer purge length"); + def->tooltip = L("Purge length for prime tower interface layer (where different materials meet)."); + def->sidetext = L("mm"); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 20. }); + + def = this->add("filament_tower_interface_print_temp", coInts); + def->label = L("Interface layer print temperature"); + def->tooltip = L("Print temperature for prime tower interface layer (where different materials meet). If set to -1, use max recommended nozzle temperature."); + def->sidetext = L("°C"); + def->min = -1; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInts { -1 }); + def = this->add("filament_cooling_final_speed", coFloats); def->label = L("Speed of the last cooling move"); def->tooltip = L("Cooling moves are gradually accelerating towards this speed."); @@ -6408,6 +6448,18 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("enable_tower_interface_features", coBool); + def->label = L("Enable tower interface features"); + def->tooltip = L("Enable optimized prime tower interface behavior when different materials meet."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("enable_tower_interface_cooldown_during_tower", coBool); + def->label = L("Cool down from interface boost during prime tower"); + def->tooltip = L("When interface-layer temperature boost is active, set the nozzle back to print temperature at the start of the prime tower so it cools down during the tower."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("prime_tower_infill_gap", coPercent); def->label = L("Infill gap"); def->tooltip = L("Infill gap."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index fc7de59cf8..45d72b313d 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1376,6 +1376,11 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInts, filament_cooling_moves)) ((ConfigOptionFloats, filament_cooling_initial_speed)) ((ConfigOptionFloats, filament_minimal_purge_on_wipe_tower)) + ((ConfigOptionFloats, filament_tower_interface_pre_extrusion_dist)) + ((ConfigOptionFloats, filament_tower_interface_pre_extrusion_length)) + ((ConfigOptionFloats, filament_tower_ironing_area)) + ((ConfigOptionFloats, filament_tower_interface_purge_volume)) + ((ConfigOptionInts, filament_tower_interface_print_temp)) ((ConfigOptionFloats, filament_cooling_final_speed)) ((ConfigOptionStrings, filament_ramming_parameters)) ((ConfigOptionBools, filament_multitool_ramming)) @@ -1505,6 +1510,8 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionPercent, prime_tower_infill_gap)) ((ConfigOptionBool, prime_tower_skip_points)) ((ConfigOptionBool, prime_tower_flat_ironing)) + ((ConfigOptionBool, enable_tower_interface_features)) + ((ConfigOptionBool, enable_tower_interface_cooldown_during_tower)) ((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionPercent, wipe_tower_extra_flow)) ((ConfigOptionFloats, flush_volumes_matrix)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index a61477e006..2ff580717f 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -816,9 +816,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_line("preheat_steps", have_ooze_prevention && (preheat_steps > 0)); bool have_prime_tower = config->opt_bool("enable_prime_tower"); - for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "wipe_tower_wall_type", "prime_tower_infill_gap","prime_tower_enable_framework"}) + for (auto el : {"prime_tower_width", "prime_tower_brim_width", "prime_tower_skip_points", "wipe_tower_wall_type", "prime_tower_infill_gap","prime_tower_enable_framework", "enable_tower_interface_features"}) toggle_line(el, have_prime_tower); + toggle_line("enable_tower_interface_cooldown_during_tower", + have_prime_tower && config->opt_bool("enable_tower_interface_features")); + for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"}) toggle_line(el, !bSEMM); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 029b585f60..b979df00c2 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2576,6 +2576,8 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Prime tower"), L"param_tower"); optgroup->append_single_option_line("enable_prime_tower", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("prime_tower_skip_points", "multimaterial_settings_prime_tower"); + optgroup->append_single_option_line("enable_tower_interface_features", "multimaterial_settings_prime_tower"); + optgroup->append_single_option_line("enable_tower_interface_cooldown_during_tower", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("prime_tower_enable_framework", "multimaterial_settings_prime_tower"); optgroup->append_single_option_line("prime_tower_width", "multimaterial_settings_prime_tower#width"); optgroup->append_single_option_line("prime_volume", "multimaterial_settings_prime_tower"); @@ -4040,6 +4042,11 @@ void TabFilament::build() page = add_options_page(L("Multimaterial"), "custom-gcode_multi_material"); // ORCA: icon only visible on placeholders optgroup = page->new_optgroup(L("Wipe tower parameters"), "param_tower"); optgroup->append_single_option_line("filament_minimal_purge_on_wipe_tower", "material_multimaterial#multimaterial-wipe-tower-parameters"); + optgroup->append_single_option_line("filament_tower_interface_pre_extrusion_dist", "material_multimaterial#multimaterial-wipe-tower-parameters"); + optgroup->append_single_option_line("filament_tower_interface_pre_extrusion_length", "material_multimaterial#multimaterial-wipe-tower-parameters"); + optgroup->append_single_option_line("filament_tower_ironing_area", "material_multimaterial#multimaterial-wipe-tower-parameters"); + optgroup->append_single_option_line("filament_tower_interface_purge_volume", "material_multimaterial#multimaterial-wipe-tower-parameters"); + optgroup->append_single_option_line("filament_tower_interface_print_temp", "material_multimaterial#multimaterial-wipe-tower-parameters"); optgroup = page->new_optgroup(L("Multi Filament")); // optgroup->append_single_option_line("filament_flush_temp", "", 0);