diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 9d429b07dd2..28a17e02ff0 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -2177,14 +2177,6 @@ void ToolOrdering::resolve_mixed_filaments(const PrintConfig &config) continue; slots[i].ratios = parse_mixed_ratios( i < ratio_strs.size() ? ratio_strs[i] : "", slots[i].components.size()); - if (!sublayer_enabled) { - for (double &r : slots[i].ratios) - r = snap_to_simple_fraction(r); - double sum = 0; - for (double r : slots[i].ratios) sum += r; - if (sum > 0) - for (double &r : slots[i].ratios) r /= sum; - } slots[i].accum.assign(slots[i].components.size(), 0LL); } @@ -2218,6 +2210,23 @@ void ToolOrdering::resolve_mixed_filaments(const PrintConfig &config) gradient_info[i].curve = parse_gradient_curve(gradient_curve_strs[i]); } + // A valid two-component gradient always needs sublayer resolution so its + // height-dependent ratios can be represented, even when the independent + // mixed-color sublayer option is disabled. Non-gradient slots retain the + // deficit round-robin normalization in that configuration. + std::vector use_sublayers(slots.size(), sublayer_enabled); + for (size_t i = 0; i < slots.size(); ++i) { + use_sublayers[i] = use_sublayers[i] || is_gradient[i]; + if (slots[i].components.empty() || use_sublayers[i]) + continue; + for (double &r : slots[i].ratios) + r = snap_to_simple_fraction(r); + double sum = 0; + for (double r : slots[i].ratios) sum += r; + if (sum > 0) + for (double &r : slots[i].ratios) r /= sum; + } + // Pass 1: identify continuous runs for each gradient slot (Per-Run). // A "run" is a maximal sequence of consecutive layers where the slot appears. struct GradientRunInfo { @@ -2418,7 +2427,7 @@ void ToolOrdering::resolve_mixed_filaments(const PrintConfig &config) auto &s = slots[ext]; // Skip sublayer splitting for the first layer to preserve bed adhesion. - if (sublayer_enabled && layer_idx > 0) { + if (use_sublayers[ext] && layer_idx > 0) { double lh = calc_slot_lh(ext, lt.print_z); size_t n = s.components.size(); diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp index 2a45bd20034..22443246ce4 100644 --- a/tests/fff_print/test_printgcode.cpp +++ b/tests/fff_print/test_printgcode.cpp @@ -1,6 +1,7 @@ #include #include "libslic3r/libslic3r.h" +#include "libslic3r/GCode/ToolOrdering.hpp" #include "libslic3r/GCodeReader.hpp" #include "test_data.hpp" @@ -15,6 +16,34 @@ boost::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter"); boost::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill"); boost::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt"); +namespace { + +DynamicPrintConfig mixed_filament_config(bool gradient_enabled) +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_num_filaments(3); + config.option("filament_is_mixed")->values[2] = true; + config.option("filament_mixed_components")->values[2] = "1,2"; + config.option("filament_mixed_sublayer_ratios")->values[2] = "0.5,0.5"; + config.option("filament_mixed_gradient")->values[2] = gradient_enabled; + config.option("filament_mixed_gradient_range")->values[2] = "0.10,0.90"; + config.set_key_value("enable_mixed_color_sublayer", new ConfigOptionBool(false)); + config.set_key_value("wall_filament", new ConfigOptionInt(3)); + config.set_key_value("sparse_infill_filament", new ConfigOptionInt(3)); + config.set_key_value("solid_infill_filament", new ConfigOptionInt(3)); + config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); + config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(0.2)); + return config; +} + +void init_mixed_filament_print(Print &print, Model &model, bool gradient_enabled) +{ + init_print({make_cube(10., 10., 4.)}, print, model, mixed_filament_config(gradient_enabled)); + print.process(); +} + +} // namespace + SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { GIVEN("A default configuration and a print test object") { WHEN("the output is executed with no support material") { @@ -269,3 +298,55 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { } } } + +TEST_CASE("mixed filament gradient enables sublayer resolution", "[PrintGCode][MixedFilament]") +{ + Print print; + Model model; + init_mixed_filament_print(print, model, true); + ToolOrdering ordering(print, unsigned(-1)); + ordering.sort_and_build_data(print, unsigned(-1)); + const std::vector &layers = ordering.layer_tools(); + + REQUIRE(layers.size() > 3); + CHECK(layers.front().mixed_sub_layer_groups.empty()); + REQUIRE(layers.front().mixed_filament_resolution.count(2) == 1); + + std::vector gradient_groups; + for (const LayerTools &layer : layers) { + for (const LayerTools::MixedSubLayerGroup &group : layer.mixed_sub_layer_groups) { + REQUIRE(group.mixed_slot_0based == 2); + REQUIRE(group.is_gradient); + REQUIRE((group.components_0based == std::vector{0, 1})); + REQUIRE(group.sub_heights.size() == 2); + CHECK(group.sub_heights[0] >= 0.); + CHECK(group.sub_heights[1] >= 0.); + CHECK(group.sub_heights[0] <= group.layer_height); + CHECK(group.sub_heights[1] <= group.layer_height); + CHECK(group.sub_heights[0] + group.sub_heights[1] == Approx(group.layer_height)); + gradient_groups.push_back(&group); + } + } + + REQUIRE(gradient_groups.size() > 1); + CHECK(gradient_groups.front()->sub_heights[0] < gradient_groups.back()->sub_heights[0]); +} + +TEST_CASE("non-gradient mixed filament keeps per-layer resolution", "[PrintGCode][MixedFilament]") +{ + Print print; + Model model; + init_mixed_filament_print(print, model, false); + ToolOrdering ordering(print, unsigned(-1)); + ordering.sort_and_build_data(print, unsigned(-1)); + + std::set resolved_components; + for (const LayerTools &layer : ordering.layer_tools()) { + CHECK(layer.mixed_sub_layer_groups.empty()); + REQUIRE(layer.mixed_filament_resolution.count(2) == 1); + const unsigned int component = layer.mixed_filament_resolution.at(2); + CHECK((component == 0 || component == 1)); + resolved_components.insert(component); + } + CHECK((resolved_components == std::set{0, 1})); +}