diff --git a/src/libslic3r/GCode/WipeTowerEstimate.cpp b/src/libslic3r/GCode/WipeTowerEstimate.cpp index 9b6c7882dd..e974f9351f 100644 --- a/src/libslic3r/GCode/WipeTowerEstimate.cpp +++ b/src/libslic3r/GCode/WipeTowerEstimate.cpp @@ -4,6 +4,7 @@ #include "WipeTower.hpp" #include "WipeTower2.hpp" #include "../Config.hpp" +#include "../FilamentMixer.hpp" #include "../PrintConfig.hpp" #include "../libslic3r.h" @@ -74,10 +75,19 @@ Polygon estimate_wipe_tower_first_layer_outline(const ConfigBase &config, WipeTo return WipeTower2::cone_base_polygon(width, depth, height, cone ? cone_angle->getFloat() : 0.); } -WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeTowerType tower_type, const std::vector &filament_ids, double layer_height, double max_object_height) +WipeTowerFootprint estimate_wipe_tower_footprint(const ConfigBase &config, WipeTowerType tower_type, const std::vector &filament_slots, double layer_height, double max_object_height) { WipeTowerFootprint footprint; footprint.height = max_object_height; + // A mixed slot is virtual: the tool ordering resolves it to one of its physical components + // on every layer, with or without colour sublayers, so the tower only ever purges those. + // Validation passes the slots as Print::extruders() lists them; the GUI and CLI placement + // expand them beforehand, which repeating here leaves unchanged. + std::vector filament_ids = filament_slots; + const auto *is_mixed_opt = dynamic_cast(option_of(config, "filament_is_mixed")); + const auto *components_opt = dynamic_cast(option_of(config, "filament_mixed_components")); + if (is_mixed_opt != nullptr && components_opt != nullptr && has_any_mixed_filament(is_mixed_opt->values)) + filament_ids = expand_mixed_filaments(filament_slots, is_mixed_opt->values, components_opt->values); const size_t filaments_cnt = filament_ids.size(); if (filaments_cnt == 0 || layer_height < EPSILON) return footprint; diff --git a/src/libslic3r/GCode/WipeTowerEstimate.hpp b/src/libslic3r/GCode/WipeTowerEstimate.hpp index 1f0b674a11..9dad82f51f 100644 --- a/src/libslic3r/GCode/WipeTowerEstimate.hpp +++ b/src/libslic3r/GCode/WipeTowerEstimate.hpp @@ -34,6 +34,8 @@ Polygon estimate_wipe_tower_first_layer_outline(const ConfigBase &config, WipeTo // filament_ids: 0-based filaments purged on the plate. The config cannot see custom G-code tool // changes, so ids derived from the model must include them // (Print::extruders(true)) or a real tower is sized as if it were never built. +// A mixed slot may be passed as itself or already expanded: it is sized as its +// physical components either way, since those are all the tower ever purges. // layer_height: thinnest layer the objects are sliced at. The first layer is folded in here. // // A raft is deliberately not a reason: normalize_fdm_2 clears enable_prime_tower for a plate diff --git a/tests/libslic3r/test_wipe_tower_estimate.cpp b/tests/libslic3r/test_wipe_tower_estimate.cpp index 6f520606a8..22117e686d 100644 --- a/tests/libslic3r/test_wipe_tower_estimate.cpp +++ b/tests/libslic3r/test_wipe_tower_estimate.cpp @@ -133,6 +133,28 @@ TEST_CASE("Type1 sizes the tower from each filament's own prime volume", "[WipeT CHECK_THAT(rib.depth, WithinAbs(WipeTower::estimate_rib_tower_bbox_side({{30.f, 0}, {45.f, 0}}, 35.f, 0.21f, 0.4f, 1.5f, 8.f, 0.f, 5.f), 1e-4)); } +TEST_CASE("A mixed slot is purged as its physical components", "[WipeTowerEstimate]") { + // Slots 4 and 5 blend 1+2 and 1+3: every layer prints one component per mixed slot, so a + // plate using all five slots purges the three physical filaments and nothing more. + const WipeTowerType type = GENERATE(WipeTowerType::Type1, WipeTowerType::Type2); + DynamicPrintConfig config = make_config(); + config.set_key_value("filament_prime_volume", new ConfigOptionFloats({100., 100., 100., 100., 100.})); + config.set_key_value("filament_adhesiveness_category", new ConfigOptionInts({0, 0, 0, 0, 0})); + config.set_key_value("filament_is_mixed", new ConfigOptionBools(std::vector{0, 0, 0, 1, 1})); + config.set_key_value("filament_mixed_components", new ConfigOptionStrings({"", "", "", "1,2", "1,3"})); + const double physical = estimate(config, 3, 0.2, 5., type).depth; + REQUIRE(physical > estimate(config, 2, 0.2, 5., type).depth); + CHECK_THAT(estimate(config, 5, 0.2, 5., type).depth, WithinAbs(physical, 1e-9)); + // The same slots with the mixed flags cleared are five filaments, which is what sized the + // tower before and refused positions the printed tower fits. + DynamicPrintConfig unmixed = config; + unmixed.set_key_value("filament_is_mixed", new ConfigOptionBools(std::vector{0, 0, 0, 0, 0})); + CHECK(estimate(unmixed, 5, 0.2, 5., type).depth > physical); + // Mixed slots alone still purge their components, and a single one already changes tools. + CHECK_THAT(estimate_wipe_tower_footprint(config, type, {3, 4}, 0.2, 5.).depth, WithinAbs(physical, 1e-9)); + CHECK_THAT(estimate_wipe_tower_footprint(config, type, {3}, 0.2, 5.).depth, WithinAbs(estimate(config, 2, 0.2, 5., type).depth, 1e-9)); +} + TEST_CASE("A second nozzle adds the ramming of one nozzle change per layer", "[WipeTowerEstimate]") { // Two filaments on two nozzles: the tool order crosses once per layer, and Type1 rams 10 mm // of filament as three 1.0 mm nozzle-change lines (see the WipeTower case).