Wipe tower sparse layers combination (#15841)

This commit is contained in:
Ian Bassi
2026-09-26 16:27:15 -03:00
committed by GitHub
parent 237cd10eb5
commit ea280ba6f6
15 changed files with 623 additions and 10 deletions
+73 -5
View File
@@ -49,6 +49,48 @@ std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<
return tower_z;
}
bool wipe_tower_sparse_layers_combined(const PrintConfig &config)
{
return config.wipe_tower_sparse_layers_combination.value && ! wipe_tower_sparse_layers_skipped(config) &&
config.timelapse_type.value != TimelapseType::tlSmooth && ! config.enable_wrapping_detection.value;
}
bool wipe_tower_layer_is_combined_away(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes)
{
return ! layer_tool_changes.empty() && layer_tool_changes.front().combined_away;
}
std::vector<char> combine_sparse_wipe_tower_layers(std::vector<float> &layer_height,
const std::vector<char> &layer_is_sparse,
const std::vector<float> &max_layer_height,
size_t first_layer_idx)
{
assert(layer_is_sparse.size() == layer_height.size() && max_layer_height.size() == layer_height.size());
std::vector<char> combined_away(layer_height.size(), 0);
float pending_height = 0.f; // what the layers folded away so far add up to
for (size_t i = 0; i < layer_height.size(); ++i) {
// A toolchange has to purge at its own z, so it neither folds away nor takes over the run
// below it - and a run always flushes on its own last layer, so nothing is ever pending here.
if (! layer_is_sparse[i] || i <= first_layer_idx) {
pending_height = 0.f;
continue;
}
const float merged = pending_height + layer_height[i];
// Hand the run on only if the next layer can swallow the whole thing; a layer already past
// the cap is left alone rather than shrunk.
const bool next_takes_it = i + 1 < layer_height.size() && layer_is_sparse[i + 1] &&
merged + layer_height[i + 1] <= max_layer_height[i + 1] + float(EPSILON);
if (next_takes_it) {
combined_away[i] = 1;
pending_height = merged;
} else {
layer_height[i] = merged;
pending_height = 0.f;
}
}
return combined_away;
}
inline float align_round(float value, float base)
{
return std::round(value / base) * base;
@@ -1904,6 +1946,7 @@ WipeTower::WipeTower(const PrintConfig& config, int plate_idx, Vec3d plate_origi
//m_bridging(float(config.wipe_tower_bridging)),
m_bridging(10.f),
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(config)),
m_gcode_flavor(config.gcode_flavor),
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
m_current_tool(initial_tool),
@@ -2028,6 +2071,16 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
// Orca: max_layer_height is per nozzle, so read it through the filament->nozzle map rather than
// by filament id. Zero means three quarters of the nozzle diameter, as in Slicing.cpp.
{
const std::vector<int> &filament_map = config.filament_map.values; // 1 based nozzle indices
const size_t nozzle_idx = idx < filament_map.size() && filament_map[idx] > 0 ? size_t(filament_map[idx] - 1) : 0;
const float max_layer_height = float(config.max_layer_height.get_at(nozzle_idx));
m_filpar[idx].max_layer_height = max_layer_height > 0.f ? max_layer_height
: 0.75f * float(config.nozzle_diameter.get_at(nozzle_idx));
}
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
if (max_vol_speed!= 0.f)
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
@@ -3001,7 +3054,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer(bool extrude_perimeter, bool
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (! m_sparse_layers_skipped || toolchanges_on_layer)
if (layer_is_printed(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();
@@ -3898,7 +3951,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer_new(bool extrude_perimeter,
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (!m_sparse_layers_skipped || toolchanges_on_layer)
if (layer_is_printed(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();
@@ -4008,7 +4061,7 @@ WipeTower::ToolChangeResult WipeTower::finish_block(const WipeTowerBlock &block,
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (!m_sparse_layers_skipped || toolchanges_on_layer)
if (layer_is_printed(toolchanges_on_layer))
if (filament_id < m_used_filament_length.size())
m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length();
@@ -4125,7 +4178,7 @@ WipeTower::ToolChangeResult WipeTower::finish_block_solid(const WipeTowerBlock &
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (!m_sparse_layers_skipped || toolchanges_on_layer)
if (layer_is_printed(toolchanges_on_layer))
if (filament_id < m_used_filament_length.size())
m_used_filament_length[filament_id] += writer.get_and_reset_used_filament_length();
@@ -4668,6 +4721,15 @@ void WipeTower::calc_block_infill_gap()
m_extra_spacing = 1.f;
}
// A folded layer is generated like any other but thrown away by the emitter, so its extrusions must
// not be charged to the filament used.
bool WipeTower::layer_is_printed(bool toolchanges_on_layer) const
{
if (m_layer_info != m_plan.end() && m_layer_info->combined_away)
return false;
return ! m_sparse_layers_skipped || toolchanges_on_layer;
}
void WipeTower::plan_tower_new()
{
if (m_wipe_tower_brim_width < 0) m_wipe_tower_brim_width = get_auto_brim_by_height(m_wipe_tower_height);
@@ -4820,6 +4882,9 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
if (m_plan.empty())
return;
//m_extra_spacing = 1.f;
// Before planning: the layer heights this rewrites feed the extrusion flow of every later pass.
if (m_sparse_layers_combined)
combine_sparse_wipe_tower_plan(m_plan, m_filpar, m_first_layer_idx, m_current_tool);
m_wipe_tower_height = m_plan.back().z;//real wipe_tower_height
plan_tower_new();
m_layer_info = m_plan.begin();
@@ -5014,6 +5079,9 @@ void WipeTower::generate_new(std::vector<std::vector<WipeTower::ToolChangeResult
if (only_generate_wall && !timelapse_wall.gcode.empty()) {
layer_result.insert(layer_result.begin(), std::move(timelapse_wall));
}
if (layer.combined_away)
for (WipeTower::ToolChangeResult &tcr : layer_result)
tcr.combined_away = true;
result.emplace_back(std::move(layer_result));
}
assert(m_outer_wall.size() == m_plan.size());
@@ -5179,7 +5247,7 @@ WipeTower::ToolChangeResult WipeTower::only_generate_out_wall(bool is_new_mode)
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (!m_sparse_layers_skipped || toolchanges_on_layer)
if (layer_is_printed(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, false);
+67
View File
@@ -152,6 +152,10 @@ public:
bool is_contact = false;
NozzleChangeResult nozzle_change_result;
// Orca: folded into a later, thicker layer, so the emitter drops it. Set by the tower, so
// the two cannot disagree about which layers print.
bool combined_away = false;
// Sum the total length of the extrusion.
float total_extrusion_length_in_plane() {
float e_length = 0.f;
@@ -391,6 +395,8 @@ public:
float filament_tower_interface_pre_extrusion_dist = 0;
float filament_tower_interface_pre_extrusion_length = 0;
float filament_petg_pre_extrusion_offset_dist = 0;
// Tallest layer this filament's nozzle can lay down; caps the sparse layer combination.
float max_layer_height = 0.f;
};
@@ -522,6 +528,7 @@ private:
//float m_extra_loading_move = 0.f;
float m_bridging = 0.f;
bool m_sparse_layers_skipped = false;
bool m_sparse_layers_combined = false;
// BBS: remove useless config
//bool m_set_extruder_trimpot = false;
bool m_adhesion = true;
@@ -595,6 +602,8 @@ private:
}
// Calculates depth for all layers and propagates them downwards
void plan_tower();
// Whether the layer reaches the G-code, and so whether its extrusions count as filament used.
bool layer_is_printed(bool toolchanges_on_layer) const;
// Goes through m_plan and recalculates depths and width of the WT to make it exactly square - experimental
void make_wipe_tower_square();
@@ -634,6 +643,8 @@ private:
float depth; // depth of the layer based on all layers above
float extra_spacing;
bool extruder_fill{true};
// Folded into a later, thicker layer, so this one prints nothing at all.
bool combined_away{false};
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
std::vector<ToolChange> tool_changes;
@@ -700,6 +711,62 @@ std::vector<float> compute_compacted_wipe_tower_z(const std::vector<std::vector<
float base_z = 0.f);
// Combination rule for wipe_tower_sparse_layers_combination. Nothing is compacted - the tower keeps
// following the object - but a run of consecutive toolchange-free layers prints as one thicker layer,
// the way infill combination merges sparse infill. Shared so that neither tower generator nor the
// G-code emitter can combine on its own.
// Whether sparse layers are really combined. Skipping them outright is the stronger answer to the
// same problem and wins over this; smooth timelapse and wrapping detection need a tower on every
// layer, so they rule it out too.
bool wipe_tower_sparse_layers_combined(const PrintConfig &config);
// A planned layer folded into a later, thicker one prints nothing at all.
bool wipe_tower_layer_is_combined_away(const std::vector<WipeTower::ToolChangeResult> &layer_tool_changes);
// Folds runs of sparse layers into one. layer_height is raised in place on the layer that prints a
// run - always its last, so the merged extrusion lands on top of what it covers - and the returned
// mask marks the layers that now print nothing. A run stops growing once one more layer would pass
// max_layer_height of the nozzle that prints it. first_layer_idx and below never combine: the
// tower's first layer carries the brim.
std::vector<char> combine_sparse_wipe_tower_layers(std::vector<float> &layer_height,
const std::vector<char> &layer_is_sparse,
const std::vector<float> &max_layer_height,
size_t first_layer_idx);
// Applies the rule above to a planned tower. Either generator's plan fits: both carry height,
// tool_changes and combined_away per layer, and index their filament parameters by tool.
template<class PlanLayers, class FilamentParams>
void combine_sparse_wipe_tower_plan(PlanLayers &plan, const FilamentParams &filpar, size_t first_layer_idx, size_t initial_tool)
{
const size_t n = plan.size();
std::vector<float> heights(n);
std::vector<char> sparse(n);
std::vector<float> caps(n);
// A layer with no toolchange prints with the filament the layer below left loaded.
size_t tool = initial_tool;
for (const auto &layer : plan)
if (! layer.tool_changes.empty()) {
tool = layer.tool_changes.front().old_tool;
break;
}
for (size_t i = 0; i < n; ++i) {
heights[i] = plan[i].height;
sparse[i] = plan[i].tool_changes.empty() ? 1 : 0;
caps[i] = tool < filpar.size() ? filpar[tool].max_layer_height : 0.f;
if (! plan[i].tool_changes.empty())
tool = plan[i].tool_changes.back().new_tool;
}
const std::vector<char> combined_away = combine_sparse_wipe_tower_layers(heights, sparse, caps, first_layer_idx);
for (size_t i = 0; i < n; ++i) {
plan[i].height = heights[i];
plan[i].combined_away = combined_away[i] != 0;
}
}
} // namespace Slic3r
#endif // WipeTowerPrusaMM_hpp_
+22 -1
View File
@@ -1033,6 +1033,7 @@ WipeTower2::WipeTower2(const PrintConfig& config, const PrintRegionConfig& defau
m_z_pos(0.f),
m_bridging(float(config.wipe_tower_bridging)),
m_sparse_layers_skipped(wipe_tower_sparse_layers_skipped(config)),
m_sparse_layers_combined(wipe_tower_sparse_layers_combined(config)),
m_gcode_flavor(config.gcode_flavor),
m_travel_speed(config.travel_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
m_infill_speed(default_region_config.sparse_infill_speed.get_at(get_extruder_index(config, (unsigned int)initial_tool))),
@@ -1150,6 +1151,16 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config)
float nozzle_diameter = float(config.nozzle_diameter.get_at(idx));
m_filpar[idx].nozzle_diameter = nozzle_diameter; // to be used in future with (non-single) multiextruder MM
// Orca: max_layer_height is per nozzle, so read it through the filament->nozzle map rather than
// by filament id. Zero means three quarters of the nozzle diameter, as in Slicing.cpp.
{
const std::vector<int> &filament_map = config.filament_map.values; // 1 based nozzle indices
const size_t nozzle_idx = idx < filament_map.size() && filament_map[idx] > 0 ? size_t(filament_map[idx] - 1) : 0;
const float max_layer_height = float(config.max_layer_height.get_at(nozzle_idx));
m_filpar[idx].max_layer_height = max_layer_height > 0.f ? max_layer_height
: 0.75f * float(config.nozzle_diameter.get_at(nozzle_idx));
}
float max_vol_speed = float(config.filament_max_volumetric_speed.get_at(idx));
if (max_vol_speed!= 0.f)
m_filpar[idx].max_e_speed = (max_vol_speed / filament_area());
@@ -2103,7 +2114,9 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer()
// Ask our writer about how much material was consumed.
// Skip this in case the layer is sparse and config option to not print sparse layers is enabled.
if (! m_sparse_layers_skipped || toolchanges_on_layer || first_layer) {
// A folded layer prints nothing, so it consumes nothing and adds no height of its own.
const bool combined_away = m_layer_info != m_plan.end() && m_layer_info->combined_away;
if ((! m_sparse_layers_skipped || toolchanges_on_layer || first_layer) && ! combined_away) {
if (m_current_tool < m_used_filament_length.size())
m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length();
m_current_height += m_layer_info->height;
@@ -2435,6 +2448,10 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
if (m_plan.empty())
return;
// Before planning: the layer heights this rewrites feed the extrusion flow of every later pass.
if (m_sparse_layers_combined)
combine_sparse_wipe_tower_plan(m_plan, m_filpar, m_first_layer_idx, m_current_tool);
plan_tower();
#if 1
for (int i=0;i<5;++i) {
@@ -2533,6 +2550,10 @@ void WipeTower2::generate(std::vector<std::vector<WipeTower::ToolChangeResult>>
layer_result[idx] = merge_tcr(layer_result[idx], finish_layer_tcr);
}
if (layer.combined_away)
for (WipeTower::ToolChangeResult &tcr : layer_result)
tcr.combined_away = true;
result.emplace_back(std::move(layer_result));
if (m_used_filament_length_until_layer.empty() || m_used_filament_length_until_layer.back().first != layer.z)
+5
View File
@@ -200,6 +200,8 @@ public:
float tower_interface_pre_extrusion_length = 0.f;
float tower_ironing_area = 4.f;
float tower_interface_purge_length = 0.f;
// Tallest layer this filament's nozzle can lay down; caps the sparse layer combination.
float max_layer_height = 0.f;
};
private:
@@ -268,6 +270,7 @@ private:
float m_extra_loading_move = 0.f;
float m_bridging = 0.f;
bool m_sparse_layers_skipped = false;
bool m_sparse_layers_combined = false;
bool m_set_extruder_trimpot = false;
bool m_adhesion = true;
GCodeFlavor m_gcode_flavor;
@@ -368,6 +371,8 @@ private:
float z; // z position of the layer
float height; // layer height
float depth; // depth of the layer based on all layers above
// Folded into a later, thicker layer, so this one prints nothing at all.
bool combined_away{false};
float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; }
std::vector<ToolChange> tool_changes;