From 8535c404669853a24d4abf818121fe6fd2946005 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Thu, 1 Oct 2026 22:09:07 +0800 Subject: [PATCH] Keep User Preset Values on Extruder Variants They Don't List A user preset stores the variant list its parent had when it was saved. When the parent later gains variants, update_diff_values_to_child_config matched variants by name only and left the new ones at the parent's value, so the user's settings were silently replaced there, and a re-save wrote the system values into the user's file. An unmatched parent variant now takes the child's first variant of the same extruder, the rule slicing already uses in get_config_index_base. A child without a variant list covers the parent's first extruder. The name match also no longer indexes the child's extruder ids when it has none. --- src/libslic3r/PrintConfig.cpp | 25 ++++++++++- tests/libslic3r/test_config.cpp | 75 +++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index d4d0c90272..bf457b7978 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -11582,8 +11582,20 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& else variant_index.resize(1, 0); + // A parent variant the child does not list (the parent gained it after the child was saved, or the + // child lists none) takes the child's first variant of the same extruder, as slicing does in + // get_config_index_base. Left unmatched, the parent's value would silently replace the user's. + auto same_extruder = [&](int i, int child_extruder_id) { + return cur_extruder_ids.empty() || child_extruder_id < 0 || + (i < int(cur_extruder_ids.size()) && cur_extruder_ids[i] == child_extruder_id); + }; + if (target_variant_count == 0) { - variant_index[0] = 0; + // The child's one value belongs to the extruder of the parent's first variant. + const int child_extruder_id = cur_extruder_ids.empty() ? -1 : cur_extruder_ids[0]; + for (int i = 0; i < int(variant_index.size()); i++) + if (i == 0 || same_extruder(i, child_extruder_id)) + variant_index[i] = 0; } else if ((cur_extruder_ids.size() > 0) && cur_variant_count != cur_extruder_ids.size()){ //should not happen @@ -11601,13 +11613,22 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& for (int j = 0; j < target_variant_count; j++) { if ((cur_extruder_variants[i] == target_extruder_variants[j]) - &&(cur_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j]))) + &&(cur_extruder_ids.empty() || target_extruder_ids.empty() || (cur_extruder_ids[i] == target_extruder_ids[j]))) { variant_index[i] = j; break; } } } + for (int i = 0; i < cur_variant_count; i++) { + if (variant_index[i] != -1) + continue; + for (int j = 0; j < target_variant_count; j++) + if (same_extruder(i, target_extruder_ids.empty() ? -1 : target_extruder_ids[j])) { + variant_index[i] = j; + break; + } + } } const t_config_option_keys &keys = new_config.keys(); diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 00c4d6d170..29c47cc1f7 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -421,6 +421,81 @@ SCENARIO("update_diff_values_to_child_config tolerates legacy machine-limit vect } } +SCENARIO("update_diff_values_to_child_config keeps a child's values on variants it does not list", + "[Config][Variant]") { + std::set no_keys; + auto variants = [](std::initializer_list names) { return new Slic3r::ConfigOptionStrings(names); }; + + GIVEN("A filament parent with three variants") { + Slic3r::DynamicPrintConfig parent; + parent.set_key_value("filament_extruder_variant", + variants({"Direct Drive Standard", "Bowden Standard", "Direct Drive High Flow"})); + parent.set_deserialize_strict("nozzle_temperature", "220,220,220"); + + WHEN("the child was saved when the parent had only its first variant") { + Slic3r::DynamicPrintConfig child; + child.set_key_value("filament_extruder_variant", variants({"Direct Drive Standard"})); + child.set_deserialize_strict("nozzle_temperature", "199"); + parent.update_diff_values_to_child_config(child, "", "filament_extruder_variant", + Slic3r::filament_options_with_variant, no_keys); + THEN("the child's value applies to every variant") { + REQUIRE(parent.opt_serialize("nozzle_temperature") == "199,199,199"); + } + } + WHEN("the child lists every variant, in another order") { + Slic3r::DynamicPrintConfig child; + child.set_key_value("filament_extruder_variant", + variants({"Bowden Standard", "Direct Drive High Flow", "Direct Drive Standard"})); + child.set_deserialize_strict("nozzle_temperature", "190,205,199"); + parent.update_diff_values_to_child_config(child, "", "filament_extruder_variant", + Slic3r::filament_options_with_variant, no_keys); + THEN("each variant keeps its own value") { + REQUIRE(parent.opt_serialize("nozzle_temperature") == "199,190,205"); + } + } + WHEN("the child lists no variants") { + Slic3r::DynamicPrintConfig child; + child.set_deserialize_strict("nozzle_temperature", "199"); + parent.update_diff_values_to_child_config(child, "", "filament_extruder_variant", + Slic3r::filament_options_with_variant, no_keys); + THEN("the child's value applies to every variant") { + REQUIRE(parent.opt_serialize("nozzle_temperature") == "199,199,199"); + } + } + } + + GIVEN("A two-extruder printer parent with two variants per extruder") { + Slic3r::DynamicPrintConfig parent; + parent.set_key_value("printer_extruder_variant", + variants({"Direct Drive Standard", "Direct Drive High Flow", "Direct Drive Standard", "Direct Drive High Flow"})); + parent.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 1, 2, 2})); + parent.set_deserialize_strict("retraction_length", "0.8,0.8,0.8,0.8"); + + WHEN("the child lists only the Standard variant of each extruder") { + Slic3r::DynamicPrintConfig child; + child.set_key_value("printer_extruder_variant", variants({"Direct Drive Standard", "Direct Drive Standard"})); + child.set_key_value("printer_extruder_id", new Slic3r::ConfigOptionInts({1, 2})); + child.set_deserialize_strict("retraction_length", "1.1,2.2"); + parent.update_diff_values_to_child_config(child, "printer_extruder_id", "printer_extruder_variant", + Slic3r::printer_options_with_variant_1, + Slic3r::printer_options_with_variant_2); + THEN("each extruder's High Flow variant takes that extruder's value") { + REQUIRE(parent.opt_serialize("retraction_length") == "1.1,1.1,2.2,2.2"); + } + } + WHEN("the child lists no variants") { + Slic3r::DynamicPrintConfig child; + child.set_deserialize_strict("retraction_length", "1.1"); + parent.update_diff_values_to_child_config(child, "printer_extruder_id", "printer_extruder_variant", + Slic3r::printer_options_with_variant_1, + Slic3r::printer_options_with_variant_2); + THEN("only the first extruder's variants take the child's value") { + REQUIRE(parent.opt_serialize("retraction_length") == "1.1,1.1,0.8,0.8"); + } + } + } +} + // SCENARIO("DynamicPrintConfig JSON serialization", "[Config]") { // WHEN("DynamicPrintConfig is serialized and deserialized") { // auto now = std::chrono::high_resolution_clock::now();