diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index ea4ad5bc52..7bf5600f2b 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -120,17 +120,34 @@ DynamicPrintConfig PresetBundle::construct_full_config( inherits.emplace_back(print_inherits); // BBS: update printer config related with variants + std::vector> nozzle_volume_types; + int extruder_count = 1, extruder_volume_type_count = 1; + bool different_extruder = false; if (apply_extruder) { - out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); - out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); - // update print config related with variants - out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + different_extruder = out.support_different_extruders(extruder_count); + extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + if ((extruder_count > 1) || different_extruder) { + // Orca: keep processing variant_1 before variant_2 here; variant_2 slots are resolved + // against the printer id/variant lists as rewritten by the variant_1 pass, and the + // composed values depend on that order. Note the order is load-bearing, not correct + // in general: the variant_2 pass reads the original full-width arrays through indices + // resolved on the shrunk lists, which mis-reads presets whose variant_2 columns differ + // per variant (e.g. X2D machine_max_speed_e/machine_max_acceleration_e). The slicing + // path composes variant_2 first and is unaffected; changing the order here would alter + // long-standing composed values, so any fix must re-baseline them. + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + // update print config related with variants + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } } if (num_filaments <= 1) { // BBS: update filament config related with variants DynamicPrintConfig filament_config = in_filament_presets[0].config; - if (apply_extruder) filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); out.apply(filament_config); compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition()); compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition()); @@ -153,8 +170,8 @@ DynamicPrintConfig PresetBundle::construct_full_config( filament_temp_configs.resize(num_filaments); for (size_t i = 0; i < num_filaments; ++i) { filament_temp_configs[i] = *(filament_configs[i]); - if (apply_extruder) - filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); } // loop through options and apply them to the resulting config. @@ -3979,18 +3996,34 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio different_settings.emplace_back(different_print_settings); //BBS: update printer config related with variants + std::vector> nozzle_volume_types; + int extruder_count = 1, extruder_volume_type_count = 1; + bool different_extruder = false; if (apply_extruder) { - out.update_values_to_printer_extruders(out, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); - out.update_values_to_printer_extruders(out, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); - //update print config related with variants - out.update_values_to_printer_extruders(out, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + different_extruder = out.support_different_extruders(extruder_count); + extruder_volume_type_count = out.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + if ((extruder_count > 1) || different_extruder) { + // Orca: keep processing variant_1 before variant_2 here; variant_2 slots are resolved + // against the printer id/variant lists as rewritten by the variant_1 pass, and the + // composed values depend on that order. Note the order is load-bearing, not correct + // in general: the variant_2 pass reads the original full-width arrays through indices + // resolved on the shrunk lists, which mis-reads presets whose variant_2 columns differ + // per variant (e.g. X2D machine_max_speed_e/machine_max_acceleration_e). The slicing + // path composes variant_2 first and is unaffected; changing the order here would alter + // long-standing composed values, so any fix must re-baseline them. + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + //update print config related with variants + out.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } } if (num_filaments <= 1) { //BBS: update filament config related with variants DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config; - if (apply_extruder) - filament_config.update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); out.apply(filament_config); compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition()); compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition()); @@ -4083,8 +4116,8 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio filament_temp_configs.resize(num_filaments); for (size_t i = 0; i < num_filaments; ++i) { filament_temp_configs[i] = *(filament_configs[i]); - if (apply_extruder) - filament_temp_configs[i].update_values_to_printer_extruders(out, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); + if (apply_extruder && ((extruder_count > 1) || different_extruder)) + filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); } // loop through options and apply them to the resulting config. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 38d5655f1e..69f35ce0f4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -3206,8 +3206,16 @@ void Print::update_filament_maps_to_config(std::vector f_maps) m_ori_full_print_config.option("filament_map", true)->values = f_maps; m_config.filament_map.values = f_maps; + int extruder_count = 1, extruder_volume_type_count = 1; + bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count); + std::vector> nozzle_volume_types; + extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + m_full_print_config = m_ori_full_print_config; - m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant"); + std::set filament_keys = filament_options_with_variant; + filament_keys.insert("filament_self_index"); + if ((extruder_count > 1) || support_multi) + m_full_print_config.update_values_to_printer_extruders_for_multiple_filaments(m_full_print_config, extruder_count, extruder_volume_type_count, filament_keys, "filament_self_index", "filament_extruder_variant"); const std::vector &extruder_retract_keys = print_config_def.extruder_retract_keys(); const std::string filament_prefix = "filament_"; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 0678f75277..84ac790808 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -1162,15 +1162,28 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } //apply extruder related values + std::vector> nozzle_volume_types; + int extruder_count = 1, extruder_volume_type_count = 1; + bool different_extruder = false; + + different_extruder = new_full_config.support_different_extruders(extruder_count); + extruder_volume_type_count = new_full_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); if (!extruder_applied) { - // variant_2 must be processed first, because variant_1 will make `printer_extruder_id` and `printer_extruder_variant` half of the size that makes `get_index_for_extruder` no longer work properly - new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); - new_full_config.update_values_to_printer_extruders(new_full_config, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); - //update print config related with variants - new_full_config.update_values_to_printer_extruders(new_full_config, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + if ((extruder_count > 1) || different_extruder) { + // variant_2 must be processed first, because variant_1 will make `printer_extruder_id` and `printer_extruder_variant` half of the size that makes `get_index_for_extruder` no longer work properly + new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, printer_options_with_variant_1, "printer_extruder_id", "printer_extruder_variant"); + //update print config related with variants + new_full_config.update_values_to_printer_extruders(new_full_config, extruder_count, extruder_volume_type_count, nozzle_volume_types, print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + } m_ori_full_print_config = new_full_config; - new_full_config.update_values_to_printer_extruders_for_multiple_filaments(new_full_config, filament_options_with_variant, "filament_self_index", "filament_extruder_variant"); + + std::set filament_keys = filament_options_with_variant; + filament_keys.insert("filament_self_index"); + if ((extruder_count > 1) || different_extruder) + new_full_config.update_values_to_printer_extruders_for_multiple_filaments(m_ori_full_print_config, extruder_count, extruder_volume_type_count, filament_keys, + "filament_self_index", "filament_extruder_variant"); } // else { // int extruder_count; @@ -1199,7 +1212,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ if (print_diff_set.find("filament_map_mode") == print_diff_set.end()) { FilamentMapMode map_mode = new_full_config.option>("filament_map_mode", true)->value; - if (map_mode < fmmManual) { + if (is_auto_filament_map_mode(map_mode)) { if (print_diff_set.find("filament_map") != print_diff_set.end()) { print_diff_set.erase("filament_map"); //full_config_diff.erase("filament_map"); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e72edfe8f5..dabf1e2376 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -650,6 +650,16 @@ std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolume return variant_string; } +int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector& variant_list, const std::vector& variant_ids_1based) +{ + assert(variant_list.size() == variant_ids_1based.size()); + std::string extruder_variant = get_extruder_variant_string(extruder_type, volume_type); + for (int index = 0; index < int(variant_list.size()); ++index) { + if (extruder_variant == variant_list[index] && variant_ids_1based[index] == variant_id_1based) { return index; } + } + return 0; +} + std::string get_nozzle_volume_type_string(NozzleVolumeType nozzle_volume_type) { if (nozzle_volume_type > nvtMaxNozzleVolumeType) { @@ -10283,184 +10293,229 @@ DynamicPrintConfig::get_filament_type() const return std::string(); } -void DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id) +int DynamicPrintConfig::get_extruder_nozzle_volume_count(int extruder_count, std::vector>& nozzle_volume_types) const { - int extruder_count; - bool different_extruder = printer_config.support_different_extruders(extruder_count); - if ((extruder_count > 1) || different_extruder) - { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: different extruders processing")%__LINE__; - //apply process settings - //auto opt_nozzle_diameters = this->option("nozzle_diameter"); - //int extruder_count = opt_nozzle_diameters->size(); - auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); - auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); - if (!opt_extruder_type || !opt_nozzle_volume_type) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: extruder_type or nozzle_volume_type option not found, skipping")%__LINE__; - return; - } - std::vector variant_index; - - if (extruder_id > 0 && extruder_id <= static_cast (extruder_count)) { - variant_index.resize(1); - ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(extruder_id - 1)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(extruder_id - 1)); - - //variant index - variant_index[0] = get_index_for_extruder(extruder_id, id_name, extruder_type, nozzle_volume_type, variant_name); - - if (variant_index[0] < 0) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, for filament") - % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type]; - assert(false); - } - - extruder_count = 1; - } - else { - variant_index.resize(extruder_count); - - for (int e_index = 0; e_index < extruder_count; e_index++) - { - ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(e_index)); - NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(e_index)); - - //variant index - variant_index[e_index] = get_index_for_extruder(e_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); - if (variant_index[e_index] < 0) { - // Orca: This is expected during transient UI states (e.g. popup windows), - // fall back to 0 silently. - variant_index[e_index] = 0; - } - } - } - - const ConfigDef *config_def = this->def(); - if (!config_def) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; - return; - } - for (auto& key: key_set) + int count = extruder_count; + auto opt_extruder_nozzle_stats = dynamic_cast(this->option("extruder_nozzle_stats")); + nozzle_volume_types.resize(extruder_count, std::vector{}); + if (opt_extruder_nozzle_stats && (int(opt_extruder_nozzle_stats->values.size()) == extruder_count)) { + std::vector> extruder_nozzle_counts = get_extruder_nozzle_stats(opt_extruder_nozzle_stats->values); + count = 0; + for (int i = 0; i < extruder_count; i++) { - const ConfigOptionDef *optdef = config_def->get(key); - if (!optdef) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; - continue; - } - switch (optdef->type) { - case coStrings: - { - ConfigOptionStrings * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - case coInts: - { - ConfigOptionInts * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - case coFloats: - { - ConfigOptionFloats * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - case coPercents: - { - ConfigOptionPercents * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - case coFloatsOrPercents: - { - ConfigOptionFloatsOrPercents * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - case coBools: - { - ConfigOptionBools * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - case coEnums: - { - ConfigOptionEnumsGeneric * opt = this->option(key); - std::vector new_values; - - new_values.resize(extruder_count * stride); - for (int e_index = 0; e_index < extruder_count; e_index++) - { - for (unsigned int i = 0; i < stride; i++) - new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); - } - opt->values = new_values; - break; - } - default: - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; - break; - } + count += extruder_nozzle_counts[i].size(); + // std::map iteration order gives the canonical slot order: ascending NozzleVolumeType + for (auto& iter : extruder_nozzle_counts[i]) + nozzle_volume_types[i].push_back(iter.first); } } + return count; } -void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name) +std::vector DynamicPrintConfig::update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector>& nv_types, + std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride, unsigned int extruder_id, NozzleVolumeType filament_nvt) { - int extruder_count; - bool different_extruder = printer_config.support_different_extruders(extruder_count); - if ((extruder_count > 1) || different_extruder) + std::vector variant_index; + int variant_count = extruder_count; + + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count %2%, extruder_nozzle_volume_count %3%")%__LINE__ %extruder_count %extruder_nozzle_volume_count; + + auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); + if (!opt_extruder_type || !opt_nozzle_volume_type) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: extruder_type or nozzle_volume_type option not found, skipping")%__LINE__; + return variant_index; + } + + if (extruder_id > 0 && extruder_id <= static_cast (extruder_count)) { + variant_index.resize(1); + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(extruder_id - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(extruder_id - 1)); + + if (nozzle_volume_type == nvtHybrid) { + // a mixed-nozzle extruder has no preset column of its own: the filament's concrete + // volume type selects the slot + nozzle_volume_type = filament_nvt; + } + else if (nozzle_volume_type != filament_nvt) { + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ + << boost::format(", Line %1%: nozzle_volume_type is %2%, not equal to filament_nvt %3%") % __LINE__ % nozzle_volume_type % filament_nvt; + } + + //variant index + variant_index[0] = get_index_for_extruder(extruder_id, id_name, extruder_type, nozzle_volume_type, variant_name); + + if (variant_index[0] < 0) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, for filament") + % __LINE__ % s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type]; + } + + variant_count = 1; + } + else { + // Orca: emit the slots first, then size variant_count from what was actually + // emitted. extruder_nozzle_volume_count only equals the emitted total when every + // extruder carries per-type stats; an extruder with an empty stats entry combined + // with a Hybrid extruder would otherwise overrun a table pre-sized from that count. + for (int e_index = 0; e_index < extruder_count; e_index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(e_index)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(e_index)); + + // Orca: latch the decision before the inner loop reassigns nozzle_volume_type; + // re-testing the reassigned value would make every slot after the first reuse + // the first slot's volume type. + const bool per_type_slots = extruder_nozzle_volume_count > extruder_count || nozzle_volume_type == nvtHybrid; + const int nvt_count = per_type_slots ? int(nv_types[e_index].size()) : 1; + for (int nvt_index = 0; nvt_index < nvt_count; nvt_index++) + { + if (per_type_slots) + nozzle_volume_type = nv_types[e_index][nvt_index]; + //variant index + int slot_index = get_index_for_extruder(e_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); + if (slot_index < 0) { + // Orca: This is expected during transient UI states (e.g. popup windows), + // fall back to 0 silently. + slot_index = 0; + } + variant_index.push_back(slot_index); + } + } + variant_count = int(variant_index.size()); + } + + const ConfigDef *config_def = this->def(); + if (!config_def) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; + return variant_index; + } + for (auto& key: key_set) + { + const ConfigOptionDef *optdef = config_def->get(key); + if (!optdef) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; + continue; + } + switch (optdef->type) { + case coStrings: + { + ConfigOptionStrings * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coInts: + { + ConfigOptionInts * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coFloats: + { + ConfigOptionFloats * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coPercents: + { + ConfigOptionPercents * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coFloatsOrPercents: + { + ConfigOptionFloatsOrPercents * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coBools: + { + ConfigOptionBools * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + case coEnums: + { + ConfigOptionEnumsGeneric * opt = this->option(key); + if (!opt) continue; + std::vector new_values; + + new_values.resize(variant_count * stride); + for (int e_index = 0; e_index < variant_count; e_index++) + { + for (unsigned int i = 0; i < stride; i++) + new_values[e_index*stride + i] = opt->get_at(variant_index[e_index]*stride + i); + } + opt->values = new_values; + break; + } + default: + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key; + break; + } + } + + return variant_index; +} + +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; + { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(", Line %1%: extruder_count=%2%, different_extruder=%3%")%__LINE__ %extruder_count %different_extruder; auto opt_filament_map = printer_config.option("filament_map"); if (!opt_filament_map) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: filament_map option not found, skipping")%__LINE__; @@ -10469,14 +10524,22 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen std::vector filament_maps = opt_filament_map->values; size_t filament_count = filament_maps.size(); //apply process settings - //auto opt_nozzle_diameters = this->option("nozzle_diameter"); - //int extruder_count = opt_nozzle_diameters->size(); auto opt_extruder_type = dynamic_cast(printer_config.option("extruder_type")); auto opt_nozzle_volume_type = dynamic_cast(printer_config.option("nozzle_volume_type")); if (!opt_extruder_type || !opt_nozzle_volume_type) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: extruder_type or nozzle_volume_type option not found, skipping")%__LINE__; return; } + + auto opt_filament_volume_maps = dynamic_cast(printer_config.option("filament_volume_map")); + std::vector filament_volume_maps; + // Orca: the per-filament volume map is only trustworthy when a producer explicitly + // sized it to the filament count; the registered default is a 1-element vector and + // stale project values may be mis-sized. A single-filament config cannot be told + // apart from that default by size, so it is ignored too until the pipeline that + // seeds the map lands; anything untrusted must not distort slot resolution. + if (opt_filament_volume_maps && filament_count > 1 && opt_filament_volume_maps->values.size() == filament_count) + filament_volume_maps = opt_filament_volume_maps->values; auto opt_ids = id_name.empty()? nullptr: dynamic_cast(this->option(id_name)); std::vector variant_index; @@ -10487,6 +10550,10 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[f_index] - 1)); NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[f_index] - 1)); + if ((extruder_nozzle_volume_count > extruder_count || nozzle_volume_type == nvtHybrid) && (!filament_volume_maps.empty())) { + nozzle_volume_type = (NozzleVolumeType)(filament_volume_maps[f_index]); + } + //variant index variant_index[f_index] = get_index_for_extruder(f_index+1, id_name, extruder_type, nozzle_volume_type, variant_name); if (variant_index[f_index] < 0) { @@ -10511,8 +10578,11 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: can not find config define")%__LINE__; return; } + bool has_id_key = !id_name.empty() && key_set.count(id_name) > 0; for (auto& key: key_set) { + if (has_id_key && key == id_name) + continue; const ConfigOptionDef *optdef = config_def->get(key); if (!optdef) { BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: can not find opt define for %2%")%__LINE__%key; @@ -10672,6 +10742,16 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen break; } } + // Orca: also require a non-empty id list; get_at on an empty vector is undefined. + if (has_id_key && opt_ids && !opt_ids->values.empty()) { + // remap the id list itself last: the lookups above still need the original ids + std::vector new_values; + new_values.resize(filament_count); + for (int f_index = 0; f_index < filament_count; f_index++) { + new_values[f_index] = opt_ids->get_at(variant_index[f_index]); + } + const_cast(opt_ids)->values = new_values; + } } } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 64fcea68d0..b94704a160 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -468,6 +468,11 @@ enum FilamentMapMode { fmmDefault }; +// All auto modes are ordered before fmmManual (see the enum ordering note above). +inline bool is_auto_filament_map_mode(FilamentMapMode mode) { + return mode < fmmManual; +} + // Dual-extruder purge control. Default reproduces the current // per-extruder flush_multiplier + filament_prime_volume behaviour, so absent/default is inert. // Saving -> reduce prime volume to 15 mm3; Fast -> use flush_multiplier_fast + filament_flush_temp_fast. @@ -479,6 +484,10 @@ enum PrimeVolumeMode { extern std::string get_extruder_variant_string(ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type); +// Base slot lookup: scans a variant list (paired with its 1-based extruder/filament ids) for the +// entry matching the given extruder/volume type and id. Returns 0 when no entry matches. +extern int get_config_index_base(NozzleVolumeType volume_type, ExtruderType extruder_type, int variant_id_1based, const std::vector& variant_list, const std::vector& variant_ids_1based); + static std::set get_valid_nozzle_volume_type() { std::set type; for (int i = 0; i <= nvtMaxNozzleVolumeType; ++i) { @@ -731,9 +740,14 @@ public: //BBS bool is_using_different_extruders(); bool support_different_extruders(int& extruder_count) const; + // Counts the config slots of a printer: one per (extruder x nozzle volume type) as described by + // extruder_nozzle_stats, or simply one per extruder when the stats are absent/mismatched. + // Fills nozzle_volume_types with each extruder's volume types in ascending enum order. + int get_extruder_nozzle_volume_count(int extruder_count, std::vector>& nozzle_volume_types) const; int get_index_for_extruder(int extruder_or_filament_id, std::string id_name, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, std::string variant_name, unsigned int stride = 1) const; - void update_values_to_printer_extruders(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0); - void update_values_to_printer_extruders_for_multiple_filaments(DynamicPrintConfig& printer_config, std::set& key_set, std::string id_name, std::string variant_name); + std::vector update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector>& nv_types, + std::set& key_set, std::string id_name, std::string variant_name, unsigned int stride = 1, unsigned int extruder_id = 0, NozzleVolumeType filament_nvt = nvtStandard); + void 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); void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set& different_keys, std::string extruder_id_name, std::string extruder_variant_name, std::set& key_set1, std::set& key_set2); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index b8571dd0fa..8c05da16b6 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(${_TEST_NAME}_tests test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp + test_config_variant_expansion.cpp test_toolordering_nozzle_group.cpp test_preset_bundle_loading.cpp test_preset_setting_id.cpp diff --git a/tests/libslic3r/test_config_variant_expansion.cpp b/tests/libslic3r/test_config_variant_expansion.cpp new file mode 100644 index 0000000000..4d4df565fa --- /dev/null +++ b/tests/libslic3r/test_config_variant_expansion.cpp @@ -0,0 +1,285 @@ +#include + +#include "libslic3r/PrintConfig.hpp" + +using namespace Slic3r; + +namespace { + +// A 2-extruder printer whose second extruder holds both a Standard and a High Flow nozzle +// (nozzle_volume_type Hybrid), described by extruder_nozzle_stats. The variant lists carry one +// column per (extruder x volume type) as composed from the presets. +DynamicPrintConfig make_hybrid_printer_config() +{ + DynamicPrintConfig config; + config.option("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#3|High Flow#2"}; + config.option("extruder_type", true)->values = {etDirectDrive, etDirectDrive}; + config.option("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid}; + config.option("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow", + "Direct Drive Standard,Direct Drive High Flow"}; + return config; +} + +void add_print_variant_columns(DynamicPrintConfig &config) +{ + config.option("print_extruder_id", true)->values = {1, 1, 2, 2}; + config.option("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow"}; + config.option("outer_wall_speed", true)->values = {30., 200., 50., 500.}; +} + +} // namespace + +TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]") +{ + std::vector> nozzle_volume_types; + + SECTION("absent stats fall back to one slot per extruder") { + DynamicPrintConfig config; + REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 2); + REQUIRE(nozzle_volume_types.size() == 2); + REQUIRE(nozzle_volume_types[0].empty()); + REQUIRE(nozzle_volume_types[1].empty()); + } + + SECTION("stats sized differently from the extruder count are ignored") { + DynamicPrintConfig config; + config.option("extruder_nozzle_stats", true)->values = {"Standard#1"}; + REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 2); + REQUIRE(nozzle_volume_types[0].empty()); + REQUIRE(nozzle_volume_types[1].empty()); + } + + SECTION("single volume type per extruder counts one slot each") { + DynamicPrintConfig config; + config.option("extruder_nozzle_stats", true)->values = {"Standard#1", "High Flow#1"}; + REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 2); + REQUIRE(nozzle_volume_types[0] == std::vector{nvtStandard}); + REQUIRE(nozzle_volume_types[1] == std::vector{nvtHighFlow}); + } + + SECTION("a mixed-nozzle extruder contributes one slot per volume type, ascending enum order") { + DynamicPrintConfig config; + // list High Flow first in the token string: parsing must still order Standard before High Flow + config.option("extruder_nozzle_stats", true)->values = {"Standard#3", "High Flow#3|Standard#3"}; + REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 3); + REQUIRE(nozzle_volume_types[0] == std::vector{nvtStandard}); + REQUIRE(nozzle_volume_types[1] == std::vector({nvtStandard, nvtHighFlow})); + } +} + +TEST_CASE("update_values_to_printer_extruders expands one slot per (extruder x volume type)", "[Config]") +{ + SECTION("Hybrid extruder yields three slots, extruder-ascending then volume-ascending") { + DynamicPrintConfig config = make_hybrid_printer_config(); + add_print_variant_columns(config); + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + REQUIRE(count == 3); + + std::vector variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types, + print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + + REQUIRE(variant_index == std::vector({0, 2, 3})); + REQUIRE(config.option("outer_wall_speed")->values == std::vector({30., 50., 500.})); + REQUIRE(config.option("print_extruder_id")->values == std::vector({1, 2, 2})); + REQUIRE(config.option("print_extruder_variant")->values == + std::vector({"Direct Drive Standard", "Direct Drive Standard", "Direct Drive High Flow"})); + } + + SECTION("stride-2 options keep (normal, silent) pairs together per slot") { + DynamicPrintConfig config = make_hybrid_printer_config(); + config.option("printer_extruder_id", true)->values = {1, 1, 2, 2}; + config.option("printer_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow"}; + config.option("machine_max_speed_x", true)->values = {100., 50., 110., 55., 120., 60., 130., 65.}; + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + std::vector variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types, + printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2); + + REQUIRE(variant_index == std::vector({0, 2, 3})); + REQUIRE(config.option("machine_max_speed_x")->values == + std::vector({100., 50., 120., 60., 130., 65.})); + } + + SECTION("single-slot expansion on a Hybrid extruder resolves via the filament volume type") { + DynamicPrintConfig printer_config = make_hybrid_printer_config(); + + DynamicPrintConfig filament_config; + filament_config.option("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow"}; + filament_config.option("filament_max_volumetric_speed", true)->values = {12., 20.}; + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = printer_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + + SECTION("default filament volume type selects the Standard column") { + std::vector variant_index = filament_config.update_values_to_printer_extruders(printer_config, extruder_count, count, + nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, 2); + REQUIRE(variant_index == std::vector({0})); + REQUIRE(filament_config.option("filament_max_volumetric_speed")->values == std::vector({12.})); + } + + SECTION("a High Flow filament volume type selects the High Flow column") { + std::vector variant_index = filament_config.update_values_to_printer_extruders(printer_config, extruder_count, count, + nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, 2, nvtHighFlow); + REQUIRE(variant_index == std::vector({1})); + REQUIRE(filament_config.option("filament_max_volumetric_speed")->values == std::vector({20.})); + } + } + + SECTION("an extruder without per-type stats does not overrun the slot table when another is Hybrid") { + DynamicPrintConfig config; + // e0 carries no per-type stats (empty entry), so the summed volume-type count (2) does + // not exceed the extruder count even though the Hybrid e1 emits one slot per volume type. + config.option("extruder_nozzle_stats", true)->values = {"", "Standard#3|High Flow#3"}; + config.option("extruder_type", true)->values = {etDirectDrive, etDirectDrive}; + config.option("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid}; + config.option("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow", + "Direct Drive Standard,Direct Drive High Flow"}; + add_print_variant_columns(config); + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + REQUIRE(count == 2); + REQUIRE(nozzle_volume_types[0].empty()); + + std::vector variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types, + print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + + // e0 resolves by its configured type; the Hybrid e1 emits one slot per stats volume type + REQUIRE(variant_index == std::vector({0, 2, 3})); + REQUIRE(config.option("outer_wall_speed")->values == std::vector({30., 50., 500.})); + } + + SECTION("without Hybrid or extra slots the expansion matches the per-extruder resolution") { + 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"}; + add_print_variant_columns(config); + + // compute what the per-extruder loop resolves directly, before the arrays are rewritten + std::vector expected_index; + for (int e_index = 0; e_index < 2; e_index++) + expected_index.push_back(config.get_index_for_extruder(e_index + 1, "print_extruder_id", etDirectDrive, + e_index == 0 ? nvtStandard : nvtHighFlow, "print_extruder_variant")); + REQUIRE(expected_index == std::vector({0, 3})); + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + REQUIRE(count == 2); + + std::vector variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types, + print_options_with_variant, "print_extruder_id", "print_extruder_variant"); + + REQUIRE(variant_index == expected_index); + REQUIRE(config.option("outer_wall_speed")->values == std::vector({30., 500.})); + } +} + +TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]") +{ + auto make_filament_arrays = [](DynamicPrintConfig &config) { + config.option("filament_self_index", true)->values = {1, 1, 2, 2}; + config.option("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow"}; + config.option("filament_max_volumetric_speed", true)->values = {12., 20., 13., 21.}; + }; + + std::set filament_keys = filament_options_with_variant; + filament_keys.insert("filament_self_index"); + + SECTION("filament_volume_map picks the concrete volume type on a Hybrid extruder") { + DynamicPrintConfig config = make_hybrid_printer_config(); + make_filament_arrays(config); + config.option("filament_map", true)->values = {2, 2}; + config.option("filament_volume_map", true)->values = {nvtStandard, nvtHighFlow}; + + 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"); + + REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 21.})); + REQUIRE(config.option("filament_extruder_variant")->values == + std::vector({"Direct Drive Standard", "Direct Drive High Flow"})); + REQUIRE(config.option("filament_self_index")->values == std::vector({1, 2})); + } + + SECTION("a volume map not sized to the filament count is ignored") { + DynamicPrintConfig config = make_hybrid_printer_config(); + make_filament_arrays(config); + config.option("filament_map", true)->values = {2, 2}; + // the registered default is a single-element vector; it must not override slot resolution + config.option("filament_volume_map", true)->values = {nvtStandard}; + + 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"); + + // Hybrid resolves as Standard when no usable per-filament map exists + REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 13.})); + REQUIRE(config.option("filament_self_index")->values == std::vector({1, 2})); + } + + SECTION("a single-filament map matches the registered default's shape and is ignored") { + DynamicPrintConfig config = make_hybrid_printer_config(); + config.option("filament_self_index", true)->values = {1, 1}; + config.option("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow"}; + config.option("filament_max_volumetric_speed", true)->values = {12., 20.}; + config.option("filament_map", true)->values = {2}; + // sized to the (single) filament count, but indistinguishable from the registered + // 1-element default, so it must not override slot resolution + config.option("filament_volume_map", true)->values = {nvtHighFlow}; + + 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"); + + // Hybrid resolves as Standard, exactly as if no map were present + REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12.})); + REQUIRE(config.option("filament_self_index")->values == std::vector({1})); + } + + SECTION("without Hybrid or extra slots the volume map is not consulted") { + 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}; + // sized to the filament count, but inert because no extruder exposes multiple volume types + config.option("filament_volume_map", true)->values = {nvtHighFlow, nvtStandard}; + + std::vector> nozzle_volume_types; + int extruder_count = 2; + int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + REQUIRE(count == 2); + + config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys, + "filament_self_index", "filament_extruder_variant"); + + // filament 1 keeps its extruder's Standard column, filament 2 its extruder's High Flow column + REQUIRE(config.option("filament_max_volumetric_speed")->values == std::vector({12., 21.})); + REQUIRE(config.option("filament_self_index")->values == std::vector({1, 2})); + } +}