Refactor tool change handling in GCode and enhance local-Z logic

- Simplified the tool change process for local-Z phases by directly switching extruders, avoiding unnecessary template replays that could degrade wipe tower quality.
- Introduced a new function to determine the starting component for local-Z alternating layers, improving the logic for height adjustments based on pass sequences.
- Updated related GCode processing to ensure consistent behavior during local-Z operations, enhancing overall print quality and efficiency.

Hopefully this fixes the issues with local z dithering and the prime tower becoming mush
This commit is contained in:
Rad
2026-03-02 22:05:09 +01:00
parent 4122b94279
commit 9880f2cb46
2 changed files with 54 additions and 78 deletions

View File

@@ -930,66 +930,17 @@ std::string WipeTowerIntegration::tool_change(GCode& gcodegen, int extruder_id,
if (extruder_id < 0 || !gcodegen.writer().need_toolchange(extruder_id))
return "";
auto pick_toolchange_template_from_layer =
[&](int layer_idx) -> const WipeTower::ToolChangeResult* {
if (layer_idx < 0 || layer_idx >= int(m_tool_changes.size()))
return nullptr;
const auto& layer_changes = m_tool_changes[layer_idx];
for (const WipeTower::ToolChangeResult& candidate : layer_changes) {
if (candidate.initial_tool != candidate.new_tool)
return &candidate;
}
return nullptr;
};
// Prefer a real toolchange template (initial_tool != new_tool). Using "empty grid"
// / finish-layer templates here can suppress [change_filament_gcode] injection and
// produce no actual emitted T-change.
const WipeTower::ToolChangeResult* template_tcr = nullptr;
if (m_layer_idx >= 0 && m_layer_idx < int(m_tool_changes.size()))
template_tcr = pick_toolchange_template_from_layer(m_layer_idx);
if (template_tcr == nullptr) {
const int start_back = std::min<int>(m_layer_idx - 1, int(m_tool_changes.size()) - 1);
for (int back = start_back; back >= 0; --back) {
template_tcr = pick_toolchange_template_from_layer(back);
if (template_tcr != nullptr)
break;
}
}
if (template_tcr == nullptr) {
const int start_fwd = std::max<int>(m_layer_idx + 1, 0);
for (int fwd = start_fwd; fwd < int(m_tool_changes.size()); ++fwd) {
template_tcr = pick_toolchange_template_from_layer(fwd);
if (template_tcr != nullptr)
break;
}
}
if (template_tcr == nullptr) {
BOOST_LOG_TRIVIAL(warning) << "Wipe tower local-z unplanned toolchange fallback unavailable (no real template),"
<< " using raw toolchange"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id;
return gcodegen.set_extruder(unsigned(extruder_id),
gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
}
WipeTower::ToolChangeResult tcr = *template_tcr;
tcr.initial_tool = gcodegen.writer().extruder() ? int(gcodegen.writer().extruder()->id()) : extruder_id;
tcr.new_tool = extruder_id;
tcr.print_z = float(gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
tcr.priming = false;
tcr.force_travel = true;
BOOST_LOG_TRIVIAL(debug) << "Wipe tower local-z unplanned toolchange emitted"
// Local-Z phase-b may introduce extra intra-layer toolchanges that were not part
// of the preplanned wipe tower sequence. Replaying a full wipe-tower template for
// each of those extra switches overprints the same tower layer at micro-step Zs
// and turns the tower into mush. Keep these extra switches local-Z-only by doing
// a direct toolchange here and leave the normal wipe tower plan untouched.
BOOST_LOG_TRIVIAL(debug) << "Local-Z unplanned toolchange using direct extruder switch"
<< " layer_idx=" << m_layer_idx
<< " extruder_id=" << extruder_id
<< " tool_change_idx=" << m_tool_change_idx;
std::string out;
out += "; local-z unplanned wipe-tower toolchange begin\n";
out += append_tcr2(gcodegen, tcr, extruder_id);
out += "; local-z unplanned wipe-tower toolchange end\n";
return out;
return gcodegen.set_extruder(unsigned(extruder_id),
gcodegen.writer().get_position().z() - gcodegen.config().z_offset.value);
};
if (local_z_unplanned)
@@ -5230,6 +5181,12 @@ LayerResult GCode::process_layer(const Print& print,
local_z_phase_b_changed_extruder = true;
if (has_wipe_tower && m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, int(local_extruder_id), false, true);
// Local-Z phase-b uses the wipe tower outside the normal per-layer
// extruder loop, so mirror the usual toolchange bookkeeping here.
// This forces the next object path to refresh WIDTH/HEIGHT tags
// after prime tower G-code, keeping the preview in sync with the
// actual local-Z pass height.
m_last_processor_extrusion_role = erWipeTower;
} else {
gcode += this->set_extruder(local_extruder_id, pass_plan.print_z);
}
@@ -5292,6 +5249,7 @@ LayerResult GCode::process_layer(const Print& print,
gcode += "; local-z phase-b restore pre-pass extruder for wipe tower\n";
if (m_wipe_tower) {
gcode += m_wipe_tower->tool_change(*this, local_z_phase_b_start_extruder, false, true);
m_last_processor_extrusion_role = erWipeTower;
} else {
gcode += this->set_extruder(static_cast<unsigned int>(local_z_phase_b_start_extruder), print_z);
}

View File

@@ -1109,6 +1109,36 @@ static inline void compute_local_z_gradient_component_heights(int mix_b_percent,
h_b = lo + pct_b * (hi - lo);
}
static bool choose_local_z_start_with_component_a(const std::vector<double> &pass_heights,
double expected_h_a,
double expected_h_b,
size_t cadence_index)
{
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? expected_h_a : expected_h_b;
const double expected_ba = (pass_i % 2) == 0 ? expected_h_b : expected_h_a;
err_ab += std::abs(pass_heights[pass_i] - expected_ab);
err_ba += std::abs(pass_heights[pass_i] - expected_ba);
}
if (err_ab + 1e-6 < err_ba)
return true;
if (err_ba + 1e-6 < err_ab)
return false;
// When the requested component heights are equal (for example 50/50),
// either A/B or B/A is numerically identical. Preserve the existing
// row cadence so equal-split layers keep the normal local-Z A/B/A/B
// sequence instead of flipping AB|BA between nominal layers.
if (std::abs(expected_h_a - expected_h_b) <= 1e-6) {
return (cadence_index % 2) == 0;
}
return expected_h_a >= expected_h_b;
}
static std::vector<double> build_local_z_alternating_pass_heights(double base_height,
double lower_bound,
double upper_bound,
@@ -2259,16 +2289,10 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mf.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < row_passes.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? row_h_a : row_h_b;
const double expected_ba = (pass_i % 2) == 0 ? row_h_b : row_h_a;
err_ab += std::abs(row_passes[pass_i] - expected_ab);
err_ba += std::abs(row_passes[pass_i] - expected_ba);
}
if (err_ba + 1e-6 < err_ab)
start_with_a = false;
start_with_a = choose_local_z_start_with_component_a(row_passes,
row_h_a,
row_h_b,
row_cadence_index[row_idx]);
}
double z_cursor = interval.z_lo;
@@ -2375,17 +2399,11 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
double row_h_a = 0.0;
double row_h_b = 0.0;
compute_local_z_gradient_component_heights(mf.mix_b_percent, mixed_lower, mixed_upper, row_h_a, row_h_b);
double err_ab = 0.0;
double err_ba = 0.0;
for (size_t pass_i = 0; pass_i < pass_heights.size(); ++pass_i) {
const double expected_ab = (pass_i % 2) == 0 ? row_h_a : row_h_b;
const double expected_ba = (pass_i % 2) == 0 ? row_h_b : row_h_a;
err_ab += std::abs(pass_heights[pass_i] - expected_ab);
err_ba += std::abs(pass_heights[pass_i] - expected_ba);
}
if (err_ba + 1e-6 < err_ab)
start_with_component_a[size_t(mixed_idx)] = uint8_t(0);
start_with_component_a[size_t(mixed_idx)] =
choose_local_z_start_with_component_a(pass_heights,
row_h_a,
row_h_b,
row_cadence_index[size_t(mixed_idx)]) ? uint8_t(1) : uint8_t(0);
}
}