From ace55646f2351640f2e01149585dc407e6f8ee02 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Fri, 11 Sep 2026 19:27:08 +0800 Subject: [PATCH] Keep the First Value When a Per-Filament Variant Option Is Too Short update_values_to_printer_extruders_for_multiple_filaments picks each filament's value from the flattened (filament x variant) columns of every per-filament variant option. When a column index fell past the end of the option's values, it skipped that filament and left the zero the output vector was created with. The GUI always hands this function full columns, but the CLI does not: - a CLI override of a single value, such as --nozzle-temperature=211 on a four-filament project, came out as 211,0,0,0, so three filaments would print at 0 C; - loading fewer filament presets than the project has filaments left the remaining filaments' columns missing, so filament_cooling_before_tower came out as 10,10,0,0 and filament_ramming_volumetric_speed as -1,-1,0,0. An out-of-range column now keeps the option's first value, the fallback get_at() and the sibling gather step already use. The seven per-type copies of the loop are replaced by that same gather_option_values helper, moved above the function; it now takes its caller's name for its log lines. An empty option, which has no first value, is given one registered default per filament first; it used to be replaced with zeros. On a partial load a filament whose preset was not loaded takes the first filament's value rather than its own preset's, which the CLI does not load; for the options seen in practice those agree. --- src/libslic3r/PrintConfig.cpp | 215 ++++-------------- .../test_config_variant_expansion.cpp | 28 +++ 2 files changed, 67 insertions(+), 176 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4c27995ba0..e8ac749bd3 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -10936,6 +10936,28 @@ std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicP return variant_index; } +// Regathers a vector option's values through per-slot source indices (one input index per +// output slot). Out-of-range indices keep the first value, matching get_at's fallback. +template +static void gather_option_values(const char *caller, const std::string &key, OptType *opt, const std::vector &slot_param_indices) +{ + if (!opt || opt->values.empty()) { + BOOST_LOG_TRIVIAL(warning) << caller << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key; + return; + } + std::vector new_values; + new_values.reserve(slot_param_indices.size()); + for (int idx : slot_param_indices) { + if (idx < 0 || static_cast(idx) >= opt->values.size()) { + BOOST_LOG_TRIVIAL(warning) << caller << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx; + new_values.emplace_back(opt->values.front()); + } + else + new_values.emplace_back(opt->values[idx]); + } + opt->values = std::move(new_values); +} + void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::set& key_set, std::string id_name, std::string variant_name) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; @@ -11013,155 +11035,18 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; continue; } + // An empty option has no first value to fall back on; give it one registered default per filament. + if (auto *vec = dynamic_cast(this->option(key)); vec && vec->empty() && optdef->default_value) + vec->resize(filament_count, optdef->default_value.get()); switch (optdef->type) { - case coStrings: - { - ConfigOptionStrings * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coInts: - { - ConfigOptionInts * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coFloats: - { - ConfigOptionFloats * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coPercents: - { - ConfigOptionPercents * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coFloatsOrPercents: - { - ConfigOptionFloatsOrPercents * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coBools: - { - ConfigOptionBools * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } - case coEnums: - { - ConfigOptionEnumsGeneric * opt = this->option(key); - if (!opt) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key; - break; - } - std::vector new_values; - - new_values.resize(filament_count); - for (int f_index = 0; f_index < filament_count; f_index++) - { - if (variant_index[f_index] < 0 || static_cast(variant_index[f_index]) >= opt->size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% variant index %3% out of range, skipping")%__LINE__%key%variant_index[f_index]; - continue; - } - new_values[f_index] = opt->get_at(variant_index[f_index]); - } - opt->values = new_values; - break; - } + case coStrings: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coInts: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coFloats: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coPercents: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coFloatsOrPercents: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coBools: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; + case coEnums: gather_option_values(__FUNCTION__, key, this->option(key), variant_index); break; default: BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; break; @@ -11180,28 +11065,6 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen } } -// Regathers a vector option's values through per-slot source indices (one input index per -// output slot). Out-of-range indices keep the first value, matching get_at's fallback. -template -static void gather_option_values(const std::string &key, OptType *opt, const std::vector &slot_param_indices) -{ - if (!opt || opt->values.empty()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key; - return; - } - std::vector new_values; - new_values.reserve(slot_param_indices.size()); - for (int idx : slot_param_indices) { - if (idx < 0 || static_cast(idx) >= opt->values.size()) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx; - new_values.emplace_back(opt->values.front()); - } - else - new_values.emplace_back(opt->values[idx]); - } - opt->values = std::move(new_values); -} - void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config, const std::unordered_map>& filament_variant_uses, int extruder_count, int extruder_nozzle_volume_count, @@ -11296,13 +11159,13 @@ void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(Dy continue; } switch (optdef->type) { - case coStrings: gather_option_values(key, this->option(key), slot_param_indices); break; - case coInts: gather_option_values(key, this->option(key), slot_param_indices); break; - case coFloats: gather_option_values(key, this->option(key), slot_param_indices); break; - case coPercents: gather_option_values(key, this->option(key), slot_param_indices); break; - case coFloatsOrPercents: gather_option_values(key, this->option(key), slot_param_indices); break; - case coBools: gather_option_values(key, this->option(key), slot_param_indices); break; - case coEnums: gather_option_values(key, this->option(key), slot_param_indices); break; + case coStrings: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coInts: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coFloats: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coPercents: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coFloatsOrPercents: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coBools: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; + case coEnums: gather_option_values(__FUNCTION__, key, this->option(key), slot_param_indices); break; default: BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; break; diff --git a/tests/libslic3r/test_config_variant_expansion.cpp b/tests/libslic3r/test_config_variant_expansion.cpp index 2469789d5b..d8e09539bb 100644 --- a/tests/libslic3r/test_config_variant_expansion.cpp +++ b/tests/libslic3r/test_config_variant_expansion.cpp @@ -484,6 +484,34 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 21.})); REQUIRE(config.option("filament_self_index")->values == std::vector({1, 2})); } + + SECTION("a variant option shorter than the filament slots keeps its first value instead of zero") { + DynamicPrintConfig config; + config.option("extruder_type", true)->values = {etDirectDrive, etDirectDrive}; + config.option("nozzle_volume_type", true)->values = {nvtStandard, nvtHighFlow}; + config.option("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow", + "Direct Drive Standard,Direct Drive High Flow"}; + make_filament_arrays(config); + config.option("filament_map", true)->values = {1, 2}; + // no loaded preset carries the key, so only its single registered default is present + config.option("filament_cooling_before_tower", true)->values = {10.}; + // only the first filament's two variant columns were loaded + config.option("filament_ramming_volumetric_speed", true)->values = {-1., -2.}; + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys, + "filament_self_index", "filament_extruder_variant"); + + // filament 2 resolves to column 3 (its extruder's High Flow column), past the end of both vectors + REQUIRE_THAT(config.option("filament_cooling_before_tower")->values, + Catch::Matchers::Approx(std::vector({10., 10.}))); + REQUIRE_THAT(config.option("filament_ramming_volumetric_speed")->values, + Catch::Matchers::Approx(std::vector({-1., -1.}))); + REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 21.})); + } } // update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing