WIP: add wait_for_temp_on_wipe_tower feature

This commit is contained in:
SoftFever
2026-08-03 14:35:48 +08:00
parent 97a7eb459f
commit 0f48ad4157
12 changed files with 244 additions and 18 deletions

View File

@@ -412,6 +412,7 @@ public:
const Vec2f& pos() const { return m_current_pos; }
const Vec2f start_pos_rotated() const { return m_start_pos; }
const Vec2f pos_rotated() const { return this->rotate(m_current_pos); }
const Vec2f rotated(const Vec2f &pt) const { return this->rotate(pt); }
float elapsed_time() const { return m_elapsed_time; }
float get_and_reset_used_filament_length() { float temp = m_used_filament_length; m_used_filament_length = 0.f; return temp; }
@@ -623,10 +624,13 @@ public:
}
// Set extruder temperature, don't wait by default.
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false)
WipeTowerWriter2& set_extruder_temp(int temperature, bool wait = false, const std::string& comment = std::string())
{
m_gcode += "G4 S0\n"; // to flush planner queue
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature) + "\n";
m_gcode += "M" + std::to_string(wait ? 109 : 104) + " S" + std::to_string(temperature);
if (!comment.empty())
m_gcode += " " + comment;
m_gcode += "\n";
return *this;
}
@@ -1034,7 +1038,8 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_wall_type((int)config.wipe_tower_wall_type),
m_use_gap_wall(use_gap_wall(config)),
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_enable_tower_interface_cooldown_during_tower(config.enable_tower_interface_cooldown_during_tower.value),
m_wait_for_temp_on_wipe_tower(config.wait_for_temp_on_wipe_tower.value && !config.single_extruder_multi_material.value)
{
// Read absolute value of first layer speed, if given as percentage,
// it is taken over following default. Speeds from config are not
@@ -1066,6 +1071,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
// Calculate where the priming lines should be - very naive test not detecting parallelograms etc.
const std::vector<Vec2d>& bed_points = config.printable_area.values;
BoundingBoxf bb(bed_points);
m_bed_bbox = bb;
m_bed_width = float(bb.size().x());
m_bed_shape = (bed_points.size() == 4 ? RectangularBed : CircularBed);
@@ -1235,7 +1241,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower2::prime(
unsigned int tool = tools[idx_tool];
m_left_to_right = true;
toolchange_Change(writer, tool, m_filpar[tool].material); // Select the tool, set a speed override for soluble and flex materials.
toolchange_Change(writer, tool, m_filpar[tool].material, m_filpar[tool].first_layer_temperature, false); // Select the tool, set a speed override for soluble and flex materials.
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.
@@ -1354,13 +1360,19 @@ WipeTower::ToolChangeResult WipeTower2::tool_change(size_t tool)
toolchange_Unload(writer, cleaning_box, m_filpar[m_current_tool].material,
(is_first_layer() ? m_filpar[m_current_tool].first_layer_temperature : m_filpar[m_current_tool].temperature),
new_tool_temp);
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
// Wait-at-tower target: the interface temp when an interface boost applies on this layer,
// otherwise the print temp (nozzle_temperature == 0 means "use the first layer temp").
int wait_for_temp = interface_layer && m_filpar[tool].interface_print_temperature > 0 ?
m_filpar[tool].interface_print_temperature :
(is_first_layer() || m_filpar[tool].temperature == 0 ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
toolchange_Change(writer, tool, m_filpar[tool].material, wait_for_temp, true); // 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
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)
// With wait-for-temp-on-wipe-tower the toolchange already blocked for the interface temp.
if (interface_temp > 0 && interface_temp != base_temp && !m_wait_for_temp_on_wipe_tower)
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);
@@ -1670,7 +1682,9 @@ void WipeTower2::toolchange_Unload(
void WipeTower2::toolchange_Change(
WipeTowerWriter2 &writer,
const size_t new_tool,
const std::string& new_material)
const std::string& new_material,
const int wait_for_temp,
const bool wait_beside_tower)
{
// Ask the writer about how much of the old filament we consumed:
if (m_current_tool < m_used_filament_length.size())
@@ -1684,6 +1698,70 @@ void WipeTower2::toolchange_Change(
if (m_is_mk4mmu3)
writer.switch_filament_monitoring(true);
const bool wait_for_temp_here = m_wait_for_temp_on_wipe_tower && wait_for_temp > 0;
// The Tn above was issued without a blocking temperature wait (OozePrevention::post_toolchange
// only restores the target); block here, before the deretraction below, which must not extrude
// on a cold nozzle. Like the Bambu H2C, park beside the tower for the heat-up so drool lands
// next to it instead of on its top surface — nearest x side first, the other side as fallback,
// in place if both would leave the bed. Raw pre-rotated moves (see the repositioning move
// below) keep the writer's tracked position at the tower entry. The tag keeps the
// interface-temp deduplication pass in append_tcr2 from stripping the M109.
if (wait_for_temp_here && wait_beside_tower) {
// The rib wall and the stabilization cone bulge past the nominal width rectangle
// (widest near the bottom), and the first-layer brim is printed around the wall later
// in the layer — clear the widest of them, not just the rectangle, so the park point
// and its drool stay off the tower.
float min_x = 0.f, max_x = m_wipe_tower_width;
if (m_wall_type == (int)wtwRib) {
WipeTower::box_coordinates wt_box(Vec2f(0.f, 0.f), m_wipe_tower_width, m_layer_info->depth + m_perimeter_width);
const BoundingBox rib_bbox = get_extents(generate_rib_polygon(wt_box)); // the fillet stays within this bbox
min_x = std::min(min_x, unscaled<float>(rib_bbox.min.x()));
max_x = std::max(max_x, unscaled<float>(rib_bbox.max.x()));
} else if (m_wall_type == (int)wtwCone) {
const double support_scale = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth,
m_wipe_tower_cone_angle).second;
const double z = m_no_sparse_layers ? (m_current_height + m_layer_info->height) : m_layer_info->z;
const double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle / 2.f)) * (m_wipe_tower_height - z);
const double w = m_layer_info->depth + m_perimeter_width;
if (r > 0.5 * w + 0.01) { // same guard as generate_support_cone_wall
const float bulge = float(std::sqrt(r * r - 0.25 * w * w) / support_scale);
min_x = std::min(min_x, m_wipe_tower_width / 2.f - bulge);
max_x = std::max(max_x, m_wipe_tower_width / 2.f + bulge);
}
}
if (is_first_layer()) {
const float brim = m_wipe_tower_brim_width < 0.f ? WipeTower::get_auto_brim_by_height(m_wipe_tower_height) :
m_wipe_tower_brim_width;
min_x -= brim;
max_x += brim;
}
constexpr float gap = 2.f;
const float near_x = writer.x() < m_wipe_tower_width / 2.f ? min_x - gap : max_x + gap;
const float far_x = near_x < m_wipe_tower_width / 2.f ? max_x + gap : min_x - gap;
const float a = float(m_wipe_tower_rotation_angle * M_PI / 180.);
const float c = std::cos(a), s = std::sin(a);
for (float side_x : { near_x, far_x }) {
const Vec2f stop = writer.rotated(Vec2f(side_x, writer.y()));
const Vec2f wt = stop + m_rib_offset;
const Vec2f bed_pt(c * wt.x() - s * wt.y() + m_wipe_tower_pos.x(),
s * wt.x() + c * wt.y() + m_wipe_tower_pos.y());
bool on_bed = false;
if (m_bed_shape == RectangularBed)
on_bed = m_bed_bbox.contains(bed_pt.cast<double>());
else if (m_bed_shape == CircularBed)
on_bed = (bed_pt.cast<double>() - m_bed_bbox.center()).norm() <= m_bed_width / 2.;
if (!on_bed)
continue;
writer.feedrate(m_travel_speed * 60.f)
.append(std::string("G1 X") + Slic3r::float_to_string_decimal_point(stop.x())
+ " Y" + Slic3r::float_to_string_decimal_point(stop.y())
+ never_skip_tag() + "\n");
break;
}
writer.set_extruder_temp(wait_for_temp, true, wait_for_temp_tag());
}
// Travel to where we assume we are. Custom toolchange or some special T code handling (parking extruder etc)
// gcode could have left the extruder somewhere, we cannot just start extruding. We should also inform the
// postprocessor that we absolutely want to have this in the gcode, even if it thought it is the same as before.
@@ -1694,6 +1772,10 @@ void WipeTower2::toolchange_Change(
+ never_skip_tag() + "\n"
);
// Priming has no tower to park beside — wait right at the priming line instead.
if (wait_for_temp_here && !wait_beside_tower)
writer.set_extruder_temp(wait_for_temp, true, wait_for_temp_tag());
writer.append("[deretraction_from_wipe_tower_generator]");
// The toolchange Tn command will be inserted later, only in case that the user does