From 60ebbf7daa9d2f404c137f23f62e786ba8a99243 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sat, 19 Sep 2026 00:36:58 +0800 Subject: [PATCH] Stop estimating a prime tower for a single used filament The no-tower case was gated on there being no purge volume, but the SEMM flush matrix reads every configured slot and is nonzero even when only one filament is used, so the plater preview drew a tower the print would never contain. --- src/libslic3r/GCode/WipeTowerEstimate.cpp | 19 ++++++++++--------- tests/libslic3r/test_wipe_tower_estimate.cpp | 4 ++++ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/libslic3r/GCode/WipeTowerEstimate.cpp b/src/libslic3r/GCode/WipeTowerEstimate.cpp index d8cfe74527..ac1508d9c0 100644 --- a/src/libslic3r/GCode/WipeTowerEstimate.cpp +++ b/src/libslic3r/GCode/WipeTowerEstimate.cpp @@ -107,6 +107,12 @@ WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeT // normalize_fdm_2 clearing enable_prime_tower. Its mixed-filament case is not modelled. const bool need_wipe_tower = smooth_timelapse || wrapping; + // Fewer than two filaments cannot make a tool change, so only wrapping detection or smooth + // timelapse print a tower then. The flush volume is no proof of one: it is read from the + // matrix of every configured slot, nonzero even when a single one of them is used. + if (filaments_cnt < 2 && !need_wipe_tower) + return footprint; + // A tower printed for one of the reasons above has no tool change to purge for; both // planners give it the idle depth below and nothing more. const size_t purge_count = filaments_cnt > 1 ? (dual_nozzle ? filaments_cnt : filaments_cnt - 1) : 0; @@ -151,14 +157,6 @@ WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeT purges[longest_ramming].filament_change_length = float(float_at("filament_change_length", filament_ids[longest_ramming], 0.) * double(nozzles.size() - 1)); } - // Both wall types decide this together: over-reserving only wastes bed area, but reporting - // no tower for one that is built collapses the validation hull to a point. - // A tool change is a reason on its own (see the base commit); Type1 already reserves - // per filament, Type2 has only the volume, which can resolve to zero. - const bool has_purge = type1 ? !purges.empty() : volume > EPSILON; - if (!has_purge && filaments_cnt < 2 && !need_wipe_tower) - return footprint; - const double min_depth = WipeTower::get_limit_depth_by_height(float(max_object_height)); const float perimeter_width = float(nozzle_diameter) * 1.25f; // Width_To_Nozzle_Ratio // With nothing to purge, plan_tower_new sizes the tower for wrapping detection or the @@ -171,7 +169,10 @@ WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeT if (!purges.empty()) side = WipeTower::estimate_rib_tower_bbox_side(purges, float(width), float(layer_height), float(nozzle_diameter), float(extra_spacing), float(rib_width), float(extra_rib_length), float(max_object_height)); else { - const double square = has_purge ? std::sqrt(volume / layer_height * extra_spacing) : idle_depth; + // Type2 squares the tower from its purge volume; Type1 with no purge list (a lone + // filament kept for timelapse or wrapping) sizes for the idle depth. + const bool has_purge = !type1 && volume > EPSILON; + const double square = has_purge ? std::sqrt(volume / layer_height * extra_spacing) : idle_depth; side = WipeTower::rib_footprint_side(float(square), float(square), float(rib_width), float(extra_rib_length), float(max_object_height)); } footprint.width = footprint.depth = side; diff --git a/tests/libslic3r/test_wipe_tower_estimate.cpp b/tests/libslic3r/test_wipe_tower_estimate.cpp index 20644200f3..22b9b2d6c5 100644 --- a/tests/libslic3r/test_wipe_tower_estimate.cpp +++ b/tests/libslic3r/test_wipe_tower_estimate.cpp @@ -335,6 +335,10 @@ TEST_CASE("The shipped defaults size the tower from the flush matrix", "[WipeTow const double flush_volume = WipeTower2::estimate_semm_flush_volume(config, 2); const double expected = std::max(double(WipeTower::get_limit_depth_by_height(5.f)), flush_volume / (0.2 * 50.)); CHECK_THAT(estimate(config, 2, 0.2, 5.).depth, WithinAbs(expected, 1e-6)); + + // The flush volume is nonzero for one slot, but a lone filament makes no tool change. + REQUIRE(WipeTower2::estimate_semm_flush_volume(config, 1) > 0.); + CHECK_THAT(estimate(config, 1, 0.2, 5.).depth, WithinAbs(0., 1e-9)); } TEST_CASE("A config missing a tower key falls back to that key's default", "[WipeTowerEstimate]") {