Compare commits

...

1 Commits

Author SHA1 Message Date
Hanif Koh
ace55646f2 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.
2026-09-11 20:05:57 +08:00
2 changed files with 67 additions and 176 deletions

View File

@@ -10936,6 +10936,28 @@ std::vector<int> 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<typename OptType, typename ValueType>
static void gather_option_values(const char *caller, const std::string &key, OptType *opt, const std::vector<int> &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<ValueType> new_values;
new_values.reserve(slot_param_indices.size());
for (int idx : slot_param_indices) {
if (idx < 0 || static_cast<size_t>(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<std::string>& 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<ConfigOptionVectorBase*>(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<ConfigOptionStrings>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<std::string> 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<size_t>(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<ConfigOptionInts>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<int> 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<size_t>(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<ConfigOptionFloats>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<double> 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<size_t>(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<ConfigOptionPercents>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<double> 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<size_t>(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<ConfigOptionFloatsOrPercents>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<FloatOrPercent> 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<size_t>(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<ConfigOptionBools>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<unsigned char> 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<size_t>(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<ConfigOptionEnumsGeneric>(key);
if (!opt) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found, skipping")%__LINE__%key;
break;
}
std::vector<int> 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<size_t>(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<ConfigOptionStrings, std::string>(__FUNCTION__, key, this->option<ConfigOptionStrings>(key), variant_index); break;
case coInts: gather_option_values<ConfigOptionInts, int>(__FUNCTION__, key, this->option<ConfigOptionInts>(key), variant_index); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(__FUNCTION__, key, this->option<ConfigOptionFloats>(key), variant_index); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(__FUNCTION__, key, this->option<ConfigOptionPercents>(key), variant_index); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(__FUNCTION__, key, this->option<ConfigOptionFloatsOrPercents>(key), variant_index); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(__FUNCTION__, key, this->option<ConfigOptionBools>(key), variant_index); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(__FUNCTION__, key, this->option<ConfigOptionEnumsGeneric>(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<typename OptType, typename ValueType>
static void gather_option_values(const std::string &key, OptType *opt, const std::vector<int> &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<ValueType> new_values;
new_values.reserve(slot_param_indices.size());
for (int idx : slot_param_indices) {
if (idx < 0 || static_cast<size_t>(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<int, std::vector<FilamentVariantUse>>& 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<ConfigOptionStrings, std::string>(key, this->option<ConfigOptionStrings>(key), slot_param_indices); break;
case coInts: gather_option_values<ConfigOptionInts, int>(key, this->option<ConfigOptionInts>(key), slot_param_indices); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(key, this->option<ConfigOptionFloats>(key), slot_param_indices); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(key, this->option<ConfigOptionPercents>(key), slot_param_indices); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(key, this->option<ConfigOptionFloatsOrPercents>(key), slot_param_indices); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(key, this->option<ConfigOptionBools>(key), slot_param_indices); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(key, this->option<ConfigOptionEnumsGeneric>(key), slot_param_indices); break;
case coStrings: gather_option_values<ConfigOptionStrings, std::string>(__FUNCTION__, key, this->option<ConfigOptionStrings>(key), slot_param_indices); break;
case coInts: gather_option_values<ConfigOptionInts, int>(__FUNCTION__, key, this->option<ConfigOptionInts>(key), slot_param_indices); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(__FUNCTION__, key, this->option<ConfigOptionFloats>(key), slot_param_indices); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(__FUNCTION__, key, this->option<ConfigOptionPercents>(key), slot_param_indices); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(__FUNCTION__, key, this->option<ConfigOptionFloatsOrPercents>(key), slot_param_indices); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(__FUNCTION__, key, this->option<ConfigOptionBools>(key), slot_param_indices); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(__FUNCTION__, key, this->option<ConfigOptionEnumsGeneric>(key), slot_param_indices); break;
default:
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key;
break;

View File

@@ -484,6 +484,34 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 21.}));
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
}
SECTION("a variant option shorter than the filament slots keeps its first value instead of zero") {
DynamicPrintConfig config;
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHighFlow};
config.option<ConfigOptionStrings>("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<ConfigOptionInts>("filament_map", true)->values = {1, 2};
// no loaded preset carries the key, so only its single registered default is present
config.option<ConfigOptionFloatsNullable>("filament_cooling_before_tower", true)->values = {10.};
// only the first filament's two variant columns were loaded
config.option<ConfigOptionFloatsNullable>("filament_ramming_volumetric_speed", true)->values = {-1., -2.};
std::vector<std::vector<NozzleVolumeType>> 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<ConfigOptionFloatsNullable>("filament_cooling_before_tower")->values,
Catch::Matchers::Approx(std::vector<double>({10., 10.})));
REQUIRE_THAT(config.option<ConfigOptionFloatsNullable>("filament_ramming_volumetric_speed")->values,
Catch::Matchers::Approx(std::vector<double>({-1., -1.})));
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 21.}));
}
}
// update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing