Ported Bambu Studio wipe tower interface features (with improved preheat and cooldown behaviour) - NEW (#12266)

Wipe tower interface features and preheat fixes

Fresh PR branch rebuilt on upstream/main (squash of origin/BBL-studio-wipe-tower-merge) to avoid merge-history issues.
This commit is contained in:
Argo
2026-02-13 15:53:25 +01:00
committed by GitHub
parent 0576aa5c05
commit a80257eb7e
15 changed files with 727 additions and 43 deletions

View File

@@ -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<std::string(unsigned int, const std::vector<float>&)> line_inserter,
std::function<std::string(const std::string&)> line_replacer)
std::function<std::string(const std::string&)> line_replacer,
std::function<bool(size_t)> 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<float> 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<float> 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<int>(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<int>(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<int>(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<float>& 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<float>& 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 */

View File

@@ -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::ToolChangeResult> WipeTower::prime(
return std::vector<ToolChangeResult>();
}
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::vector<std::vector<WipeTower::ToolChangeResult
for (auto layer : m_plan) {
reset_block_status();
m_cur_layer_id = index++;
m_prev_layer_had_interface = m_current_layer_has_interface;
m_current_layer_has_interface = !solid_blocks_id.empty();
set_layer(layer.z, layer.height, 0, false, layer.z == m_plan.back().z);
if (m_layer_info->depth < 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)

View File

@@ -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<float,Polylines> 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();

View File

@@ -972,6 +972,25 @@ public:
return add_wipe_point(Vec2f(x, y));
}
void spiral_flat_ironing(const Vec2f &center, 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<WipeTower::ToolChangeResult> 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<WipeTower::ToolChangeResult> 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)

View File

@@ -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,