From 18b9279c2601c3a186d0f1e51f1cb9d1323974fa Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sat, 11 Jul 2026 02:29:40 +0800 Subject: [PATCH] feat(print): add per-filament config-index resolvers and filament_map_2 - Print::get_nozzle_config_index / get_filament_config_indx resolve a filament's variant slot per layer from the nozzle group result, with hashed index caches; when no group result is published (sequential prints), they fall back to the static filament->extruder mapping so behavior is unchanged - filament_map_2 caches each filament's resolved print-variant slot; rebuilt in Print::apply after the filament_map diff handling and in the filament-map write-back - filament retract overrides now key by slot indices: apply_override fallback indexing flips to 0-based, Print::apply passes filament_map/extruder indices, the write-back passes filament_map_2 (identical resolution while slots equal extruders) - filament_volume_map/filament_nozzle_map/filament_map_2/ filament_self_index become PrintConfig static members (required for member access); grouping input guards tightened so their registered 1-element defaults are never mistaken for real per-filament maps (single-filament manual mode keeps the mix-marker fallback) - update_filament_self_index_cache refreshed at every full-config assignment - tests: 0-based apply_override fallback, get_config_index_base hit/miss/mixed-type cases The resolvers are not consumed by the g-code writer yet. Non-Hybrid g-code is unchanged except the config header, which now serializes the three new static keys (defaults until the per-filament producer lands); verified by the 19-fixture byte gate: 3 added header lines per fixture, zero motion changes. --- src/libslic3r/Config.hpp | 5 +- src/libslic3r/GCode/ToolOrdering.cpp | 54 ++++--- src/libslic3r/Print.cpp | 138 +++++++++++++++++- src/libslic3r/Print.hpp | 69 ++++++++- src/libslic3r/PrintApply.cpp | 30 +++- src/libslic3r/PrintConfig.cpp | 18 +-- src/libslic3r/PrintConfig.hpp | 6 +- .../test_config_variant_expansion.cpp | 51 +++++++ 8 files changed, 334 insertions(+), 37 deletions(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 54a09ec96a..63d37315c4 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -716,6 +716,7 @@ public: return false; } // Apply an override option, possibly a nullable one. + //default_index are 0 based bool apply_override(const ConfigOption *rhs, std::vector& default_index) override { if (this->nullable()) throw ConfigurationError("Cannot override a nullable ConfigOption."); @@ -751,8 +752,8 @@ public: this->values[i] = rhs_vec->values[i]; modified = true; } else { - if ((i < default_index.size()) && (default_index[i] - 1 < default_value.size())) - this->values[i] = default_value[default_index[i] - 1]; + if ((i < default_index.size()) && (default_index[i] < default_value.size())) + this->values[i] = default_value[default_index[i]]; else this->values[i] = default_value[0]; } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index caef0fb5b1..1454ced0e7 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -1167,8 +1167,8 @@ static std::vector build_default_nozzle_list(const // Best-effort readers for the multi-nozzle dev config keys. These are registered in the ConfigDef // but not (yet) static PrintConfig members, so the slicing PrintConfig reads them as inert defaults, -// which keeps the auto grouping path bit-exact (all these degrade to the flush-only, non-switcher, -// hybrid-flow case). +// which keeps the auto grouping path bit-exact (all these degrade to the flush-only, non-switcher +// case). static bool cfg_bool(const ConfigBase& c, const char* key, bool def) { if (auto* o = c.option(key)) return o->value; @@ -1179,11 +1179,6 @@ static double cfg_float(const ConfigBase& c, const char* key, double def) if (auto* o = c.option(key)) return o->value; return def; } -static std::vector cfg_ints(const ConfigBase& c, const char* key) -{ - if (auto* o = c.option(key)) return o->values; - return {}; -} // Prepare per-extruder flush matrices. The prime_volume_mode==pvmFast branch: Default reads // flush_multiplier, Fast reads flush_multiplier_fast. @@ -1336,19 +1331,17 @@ static FilamentGroupContext build_filament_group_context( context.group_info.ignore_ext_filament = ignore_ext_filament; context.group_info.has_filament_switcher = has_filament_switcher; - // hybrid flow means no special per-filament nozzle-volume request. In manual mode we honour the - // config's per-filament volume map when it is present and correctly sized; otherwise (the key is - // not a static PrintConfig member) fall back to hybrid so rebuild_nozzle_unprintables - // never indexes out of range. - if (mode == FilamentMapMode::fmmManual) { - auto fvm = cfg_ints(print_config, "filament_volume_map"); - if (fvm.size() == filament_nums) - context.group_info.filament_volume_map = std::move(fvm); - else - context.group_info.filament_volume_map = std::vector(filament_nums, (int)(NozzleVolumeType::nvtHybrid)); - } else { + // hybrid flow means no special per-filament nozzle-volume request. + // Orca: in manual mode the config's per-filament volume map is honoured only when a producer + // sized it to a multi-filament count (the trust guard used by + // update_values_to_printer_extruders_for_multiple_filaments): the registered default is a + // 1-element vector ({Standard}) that a single-filament count cannot tell apart from a real + // map, and it must not displace the hybrid fallback rebuild_nozzle_unprintables relies on. + if (mode == FilamentMapMode::fmmManual && filament_nums > 1 && + print_config.filament_volume_map.values.size() == filament_nums) + context.group_info.filament_volume_map = print_config.filament_volume_map.values; + else context.group_info.filament_volume_map = std::vector(filament_nums, (int)(NozzleVolumeType::nvtHybrid)); - } context.nozzle_info.nozzle_list = build_nozzle_list(nozzle_groups); context.nozzle_info.extruder_nozzle_list = build_extruder_nozzle_list(context.nozzle_info.nozzle_list); @@ -1493,7 +1486,17 @@ MultiNozzleUtils::LayeredNozzleGroupResult ToolOrdering::get_recommended_filamen auto manual_filament_map = print_config.filament_map.values; std::transform(manual_filament_map.begin(), manual_filament_map.end(), manual_filament_map.begin(), [](int v) { return v - 1; }); float diameter = print_config.nozzle_diameter.values.empty() ? 0.4f : (float)print_config.nozzle_diameter.values.front(); - auto nozzle_result = LayeredNozzleGroupResult::create(used_filaments, manual_filament_map, cfg_ints(print_config, "filament_volume_map"), cfg_ints(print_config, "filament_nozzle_map"), get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values), diameter); + // Orca: create() indexes the volume/nozzle maps per used filament with no bounds check, so + // pass them only when a producer sized them to a multi-filament count — the registered + // 1-element defaults are indistinguishable by size from a real single-filament map. + // Without valid maps the fully-manual request cannot be honoured; return the empty result, + // the same failure an unsatisfiable create() yields. + std::optional nozzle_result; + if (filament_nums > 1 && print_config.filament_volume_map.values.size() == filament_nums && + print_config.filament_nozzle_map.values.size() == filament_nums) + nozzle_result = LayeredNozzleGroupResult::create(used_filaments, manual_filament_map, print_config.filament_volume_map.values, print_config.filament_nozzle_map.values, get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values), diameter); + if (!nozzle_result) + BOOST_LOG_TRIVIAL(error) << "Failed to build nozzle group result from filament nozzle map!"; return nozzle_result ? *nozzle_result : LayeredNozzleGroupResult(); } @@ -1611,11 +1614,18 @@ static MultiNozzleUtils::LayeredNozzleGroupResult build_group_result_from_map( { using namespace MultiNozzleUtils; const size_t extruder_nums = print_config.nozzle_diameter.values.size(); + const size_t filament_nums = print_config.filament_colour.values.size(); const bool has_multiple_nozzle = std::any_of(print_config.extruder_max_nozzle_count.values.begin(), print_config.extruder_max_nozzle_count.values.end(), [](int v) { return v > 1; }); - if (has_multiple_nozzle) { + // Orca: same trust guard as the manual grouping paths — create() indexes the volume/nozzle + // maps per used filament with no bounds check, and the registered 1-element defaults must not + // be mistaken for a real single-filament map. Unsized maps fall through to the extruder-level + // wrap below. + if (has_multiple_nozzle && filament_nums > 1 && + print_config.filament_volume_map.values.size() == filament_nums && + print_config.filament_nozzle_map.values.size() == filament_nums) { float diameter = print_config.nozzle_diameter.values.empty() ? 0.4f : static_cast(print_config.nozzle_diameter.values.front()); - if (auto g = LayeredNozzleGroupResult::create(used_filaments, filament_map_0based, cfg_ints(print_config, "filament_volume_map"), cfg_ints(print_config, "filament_nozzle_map"), get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values), diameter)) + if (auto g = LayeredNozzleGroupResult::create(used_filaments, filament_map_0based, print_config.filament_volume_map.values, print_config.filament_nozzle_map.values, get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values), diameter)) return *g; } auto nozzle_list = build_default_nozzle_list(print_config, extruder_nums); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 69f35ce0f4..46ca5b76b8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -3211,6 +3211,29 @@ void Print::update_filament_maps_to_config(std::vector f_maps) std::vector> nozzle_volume_types; extruder_volume_type_count = m_ori_full_print_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types); + //filament_map_2 + // Orca: seed with 0-based extruder indices so the override keying below degenerates to the + // plain per-extruder slot when the rebuild loop is skipped; the loop overwrites every + // entry when it runs. + m_config.filament_map_2.values = f_maps; + for (auto& v : m_config.filament_map_2.values) + --v; + auto opt_extruder_type = dynamic_cast(m_ori_full_print_config.option("extruder_type")); + auto opt_nozzle_volume_type = dynamic_cast(m_ori_full_print_config.option("nozzle_volume_type")); + // Orca: the loop tolerates configs without the extruder options (unit tests, degenerate + // presets); the volume-map override keeps the sizing guard used everywhere else, and the + // grouping engine does not produce per-filament volume maps yet, so m_config only carries + // a map when the project supplied one. + for (int index = 0; opt_extruder_type && opt_nozzle_volume_type && index < f_maps.size(); index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(f_maps[index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1)); + if ((extruder_volume_type_count > extruder_count) + && f_maps.size() > 1 && m_config.filament_volume_map.values.size() == f_maps.size()) + nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]); + m_config.filament_map_2.values[index] = m_ori_full_print_config.get_index_for_extruder(f_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant"); + } + m_full_print_config = m_ori_full_print_config; std::set filament_keys = filament_options_with_variant; filament_keys.insert("filament_self_index"); @@ -3228,7 +3251,7 @@ void Print::update_filament_maps_to_config(std::vector f_maps) const ConfigOption *opt_old_machine = m_config.option(opt_key); if (opt_new_filament) - compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, f_maps); + compute_filament_override_value(opt_key, opt_old_machine, opt_new_machine, opt_new_filament, m_full_print_config, print_diff, filament_overrides, m_config.filament_map_2.values); } t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); @@ -3238,6 +3261,7 @@ void Print::update_filament_maps_to_config(std::vector f_maps) m_config.apply(filament_overrides); } } + update_filament_self_index_cache(); m_has_auto_filament_map_result = true; } @@ -3251,6 +3275,16 @@ std::vector Print::get_filament_maps() const return m_config.filament_map.values; } +std::vector Print::get_filament_nozzle_maps() const +{ + return m_config.filament_nozzle_map.values; +} + +std::vector Print::get_filament_volume_maps() const +{ + return m_config.filament_volume_map.values; +} + FilamentMapMode Print::get_filament_map_mode() const { return m_config.filament_map_mode; @@ -3405,6 +3439,108 @@ bool Print::is_dynamic_group_reorder() const return true; } +int Print::get_filament_config_indx(int filament_id, int layer_id) +{ + return get_config_index(filament_id, layer_id, m_config.filament_extruder_variant.values, m_filament_self_index, m_filament_index_map); +} + +void Print::update_filament_self_index_cache() +{ + std::vector values; + if (m_full_print_config.has("filament_self_index")) { + values = m_full_print_config.option("filament_self_index")->values; + } else if (m_ori_full_print_config.has("filament_self_index")) { + values = m_ori_full_print_config.option("filament_self_index")->values; + } else { + values = m_config.filament_self_index.values; + } + + size_t expected_size = m_config.filament_extruder_variant.values.size(); + m_filament_self_index.clear(); + if (expected_size == 0) { + m_filament_index_map.clear(); + m_nozzle_index_map.clear(); + return; + } + m_filament_self_index.resize(expected_size, 1); + if (!values.empty()) { + for (size_t i = 0; i < expected_size; ++i) { + int v = i < values.size() ? values[i] : 1; + if (v <= 0) + v = 1; + m_filament_self_index[i] = v; + } + } + m_filament_index_map.clear(); + m_nozzle_index_map.clear(); +} + +int Print::get_nozzle_config_index(int filament_id, int layer_id) +{ + // Orca: print_extruder_id/print_extruder_variant are PrintRegionConfig members in this codebase; + // the process-wide expanded values live in the default region config (regions never override them). + return get_config_index(filament_id, layer_id, m_default_region_config.print_extruder_variant.values, m_default_region_config.print_extruder_id.values, m_nozzle_index_map); +} + +int Print::get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, FilamentIndexMap &index_map) +{ + auto group_result = get_layered_nozzle_group_result(); + // Orca: sequential (by-object) prints never publish a Print-level layered group result on this + // branch, so fall back to the static identity: one filament-variant column per filament. + if (!group_result) + return filament_id; + auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id); + if (!nozzle_info.has_value()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ + << boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3%") % __LINE__ % filament_id % + layer_id; + return 0; + } + + ExtruderType extruder_type = ExtruderType(m_config.extruder_type.get_at(nozzle_info->extruder_id)); + NozzleVolumeType nozzle_volume_type = nozzle_info->volume_type; + + FilamentIndexKey key{filament_id, extruder_type, nozzle_volume_type}; + auto iter = index_map.find(key); + if (iter == index_map.end()) { + int index = get_config_index_base(nozzle_volume_type, extruder_type, filament_id + 1, variant_list, self_index_list); + index_map[key] = index; + return index; + } else { + return index_map[key]; + } +} + +int Print::get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, PrintIndexMap &index_map) +{ + auto group_result = get_layered_nozzle_group_result(); + // Orca: same static fallback as the filament overload; the slot degenerates to the filament's + // extruder column (filament_map is 1 based, get_extruder_id guards the filament id range). + if (!group_result) + return (int)get_extruder_id(filament_id); + auto nozzle_info = group_result->get_nozzle_for_filament(filament_id, layer_id); + if (!nozzle_info.has_value()) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ + << boost::format(", Line %1%: could not found group_nozzle_info corresponding to filament_id %2%, layer_id %3%") % __LINE__ % filament_id % + layer_id; + return 0; + } + + int extruder_id = nozzle_info->extruder_id + 1; // to 1 based + ExtruderType extruder_type = ExtruderType(m_config.extruder_type.get_at(nozzle_info->extruder_id)); + NozzleVolumeType nozzle_volume_type = nozzle_info->volume_type; + + PrintIndexKey key{filament_id, extruder_id, extruder_type, nozzle_volume_type}; + auto iter = index_map.find(key); + if (iter == index_map.end()) { + int index = get_config_index_base(nozzle_volume_type, extruder_type, extruder_id, variant_list, self_index_list); + index_map[key] = index; + return index; + } else { + return index_map[key]; + } +} + // Wipe tower support. bool Print::has_wipe_tower() const { diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 2f68ad527e..100db84079 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -23,6 +23,7 @@ #include #include +#include #include "calib.hpp" @@ -1010,6 +1011,8 @@ public: // 1 based group ids std::vector get_filament_maps() const; FilamentMapMode get_filament_map_mode() const; + std::vector get_filament_volume_maps() const; + std::vector get_filament_nozzle_maps() const; // get the group label of filament size_t get_extruder_id(unsigned int filament_id) const; @@ -1152,7 +1155,12 @@ public: bool is_all_objects_are_short() const { return std::all_of(this->objects().begin(), this->objects().end(), [&](PrintObject* obj) { return obj->height() < scale_(this->config().nozzle_height.value); }); } - + + // Post-slicing config-slot resolvers: map a (filament, layer) pair to the index of its + // per-(extruder x volume type) column in the expanded variant arrays, cached by grouping context. + int get_filament_config_indx(int filament_id, int layer_id); + int get_nozzle_config_index(int filament_id, int layer_id); + // Orca: Implement prusa's filament shrink compensation approach // Returns if all used filaments have same shrinkage compensations. bool has_same_shrinkage_compensations() const; @@ -1162,6 +1170,57 @@ public: std::tuple object_skirt_offset(double margin_height = 0) const; protected: + struct FilamentIndexKey + { + int filament_id; + ExtruderType extruder; + NozzleVolumeType nozzle_volume_type; + + bool operator==(const FilamentIndexKey &other) const + { + return filament_id == other.filament_id && extruder == other.extruder && nozzle_volume_type == other.nozzle_volume_type; + } + }; + + struct PrintIndexKey + { + int filament_id; + int extruder_id; + ExtruderType extruder; + NozzleVolumeType nozzle_volume_type; + + bool operator==(const PrintIndexKey &other) const + { + return filament_id == other.filament_id && extruder_id == other.extruder_id && extruder == other.extruder && nozzle_volume_type == other.nozzle_volume_type; + } + }; + + struct FilamentIndexKeyHash + { + std::size_t operator()(const FilamentIndexKey &k) const + { + size_t h1 = std::hash{}(k.filament_id); + size_t h2 = std::hash{}(static_cast(k.extruder)); + size_t h3 = std::hash{}(static_cast(k.nozzle_volume_type)); + return h1 ^ (h2 << 8) ^ (h3 << 12); + } + }; + struct PrintIndexKeyHash + { + std::size_t operator()(const PrintIndexKey &k) const + { + size_t h1 = std::hash{}(k.filament_id); + size_t h2 = std::hash{}(k.extruder_id); + size_t h3 = std::hash{}(static_cast(k.extruder)); + size_t h4 = std::hash{}(static_cast(k.nozzle_volume_type)); + return h1 ^ (h2 << 8) ^ (h3 << 12) ^ (h4 << 16); + } + }; + using FilamentIndexMap = std::unordered_map; + using PrintIndexMap = std::unordered_map; + int get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, FilamentIndexMap &index_map); + int get_config_index(int filament_id, int layer_id, const std::vector &variant_list, const std::vector& self_index_list, PrintIndexMap &index_map); + // Invalidates the step, and its depending steps in Print. bool invalidate_step(PrintStep step); @@ -1175,6 +1234,7 @@ private: void _make_skirt(); void _make_wipe_tower(); void finalize_first_layer_convex_hull(); + void update_filament_self_index_cache(); // Islands of objects and their supports extruded at the 1st layer. Polygons first_layer_islands() const; @@ -1211,6 +1271,13 @@ private: // Logical (extruder, nozzle) grouping result, set by ToolOrdering during reorder. std::shared_ptr m_nozzle_group_result; + // Used to cache filament parameter information + FilamentIndexMap m_filament_index_map; + // Used to cache printer and process parameter information + PrintIndexMap m_nozzle_index_map; + // save the config value of "filament_self_index" + std::vector m_filament_self_index; + // Following section will be consumed by the GCodeGenerator. ToolOrdering m_tool_ordering; WipeTowerData m_wipe_tower_data {m_tool_ordering}; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 84ac790808..fa5a128e32 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -240,7 +240,10 @@ static t_config_option_keys print_config_diffs( const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; if (opt_new_filament != nullptr) { - compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_maps); + std::vector filament_map_indices(filament_maps.size(), 0); + for (int i = 0; i < filament_maps.size(); i++) + filament_map_indices[i] = filament_maps[i] - 1; + compute_filament_override_value(opt_key, opt_old, opt_new, opt_new_filament, new_full_config, print_diff, filament_overrides, filament_map_indices); } else if (*opt_new != *opt_old) { //BBS: add plate_index logic for wipe_tower_x/wipe_tower_y if (!opt_key.compare("wipe_tower_x") || !opt_key.compare("wipe_tower_y")) { @@ -1248,6 +1251,29 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ print_diff.assign(print_diff_set.begin(), print_diff_set.end()); } + //filament_map_2 + // Orca: seed with 0-based extruder indices so the copy stays a valid slot map even when the + // variant options are absent below and the rebuild loop is skipped (unit tests, degenerate + // presets); the loop overwrites every entry when it runs. + m_config.filament_map_2.values = filament_maps; + for (auto& v : m_config.filament_map_2.values) + --v; + auto opt_extruder_type = dynamic_cast(new_full_config.option("extruder_type")); + auto opt_filament_volume_maps = dynamic_cast(new_full_config.option("filament_volume_map")); + auto opt_nozzle_volume_type = dynamic_cast(new_full_config.option("nozzle_volume_type")); + for (int index = 0; opt_extruder_type && opt_nozzle_volume_type && index < filament_maps.size(); index++) + { + ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[index] - 1)); + NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[index] - 1)); + // 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 (see + // update_values_to_printer_extruders_for_multiple_filaments for the same guard). + if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps + && filament_maps.size() > 1 && opt_filament_volume_maps->values.size() == filament_maps.size()) + nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]); + m_config.filament_map_2.values[index] = new_full_config.get_index_for_extruder(filament_maps[index], "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant"); + } + // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) @@ -1295,6 +1321,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ m_default_region_config.apply_only(new_full_config, region_diff, true); //m_full_print_config = std::move(new_full_config); m_full_print_config = new_full_config; + update_filament_self_index_cache(); if (num_extruders != m_config.filament_diameter.size()) { num_extruders = m_config.filament_diameter.size(); num_extruders_changed = true; @@ -1668,6 +1695,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ // Handle changes to regions config defaults m_default_region_config.apply_only(new_full_config, new_changed_keys, true); m_full_print_config = std::move(new_full_config); + update_filament_self_index_cache(); } // All regions now have distinct settings. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index dabf1e2376..c684c9e85b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2731,10 +2731,9 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionInts{1}); // Multi-nozzle: per-filament map to the config slot identified by (extruder, nozzle_volume_type). - // Forward-compat-only registration with no consumer yet. - // Orca: resolves per-variant slots via get_index_for_extruder at PrintApply time rather than a - // read-time filament-config-index lookup, so nothing in src/ reads this key. Kept registered so an - // H2C project/config that carries it loads without an unknown-option substitution warning. + // Engine-internal per-filament slot map: recomputed at apply time from filament_map (plus the + // per-filament volume map when an extruder exposes several volume types) and used to key the + // retract-override fallback; never a user input and never part of the exported full config. // Internal use only, no translation. def = this->add("filament_map_2", coInts); def->label = "Filament map plus for multi nozzle"; @@ -2742,9 +2741,10 @@ void PrintConfigDef::init_fff_params() def->mode = comDevelop; def->set_default_value(new ConfigOptionInts{1}); - // Per-filament nozzle-volume-type override (multi-volume/Hybrid). Registered so the value - // round-trips through .3mf plate metadata (filament_volume_maps); load/save-only for now and - // inert for existing printers (unread by slicing until the multi-volume pipeline lands). + // Per-filament nozzle-volume-type override (multi-volume/Hybrid). Round-trips through .3mf + // plate metadata (filament_volume_maps) and steers per-filament slot resolution when an + // extruder exposes several volume types; inert for existing printers (no producer sizes it + // to the filament count until the write-back pipeline lands). def = this->add("filament_volume_map", coInts); def->mode = comDevelop; def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)}); @@ -11030,7 +11030,7 @@ void DynamicPrintConfig::update_diff_values_to_child_config(DynamicPrintConfig& } void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, - t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_maps) + t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices) { bool is_nil = opt_new_filament->is_nil(); @@ -11052,7 +11052,7 @@ void compute_filament_override_value(const std::string& opt_key, const ConfigOpt } auto opt_copy = opt_new_machine->clone(); - opt_copy->apply_override(opt_new_filament, f_maps); + opt_copy->apply_override(opt_new_filament, f_map_indices); bool changed = *opt_old_machine != *opt_copy; if (changed) { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b94704a160..9c19c23915 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -774,7 +774,7 @@ extern std::set printer_options_with_variant_2; extern std::set empty_options; extern void compute_filament_override_value(const std::string& opt_key, const ConfigOption *opt_old_machine, const ConfigOption *opt_new_machine, const ConfigOption *opt_new_filament, const DynamicPrintConfig& new_full_config, - t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_maps); + t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector& f_map_indices); void handle_legacy_sla(DynamicPrintConfig &config); @@ -1447,8 +1447,12 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInts, required_nozzle_HRC)) ((ConfigOptionEnum, filament_map_mode)) ((ConfigOptionInts, filament_map)) + ((ConfigOptionInts, filament_volume_map)) + ((ConfigOptionInts, filament_nozzle_map)) + ((ConfigOptionInts, filament_map_2)) //used for multi nozzle, map filament to the index identified by extruder+nozzle_volume_type //((ConfigOptionInts, filament_extruder_id)) ((ConfigOptionStrings, filament_extruder_variant)) + ((ConfigOptionInts, filament_self_index)) ((ConfigOptionBool, support_object_skip_flush)) ((ConfigOptionEnum, bed_temperature_formula)) ((ConfigOptionInts, physical_extruder_map)) diff --git a/tests/libslic3r/test_config_variant_expansion.cpp b/tests/libslic3r/test_config_variant_expansion.cpp index 4d4df565fa..9cae7a34bc 100644 --- a/tests/libslic3r/test_config_variant_expansion.cpp +++ b/tests/libslic3r/test_config_variant_expansion.cpp @@ -30,6 +30,57 @@ void add_print_variant_columns(DynamicPrintConfig &config) } // namespace +TEST_CASE("apply_override fills nil entries from the 0-based default index", "[Config]") +{ + ConfigOptionFloats machine({10., 20., 30.}); + ConfigOptionFloatsNullable filament; + filament.values = {ConfigOptionFloatsNullable::nil_value(), 42.}; + + SECTION("a nil entry picks the slot addressed by its 0-based index") { + std::vector slot_index{2, 0}; + ConfigOptionFloats resolved(machine); + REQUIRE(resolved.apply_override(&filament, slot_index)); + REQUIRE(resolved.values == std::vector({30., 42.})); + } + + SECTION("an index past the machine slots falls back to the first slot") { + std::vector slot_index{5, 0}; + ConfigOptionFloats resolved(machine); + REQUIRE(resolved.apply_override(&filament, slot_index)); + REQUIRE(resolved.values == std::vector({10., 42.})); + } + + SECTION("a negative index (unresolved slot) falls back to the first slot") { + std::vector slot_index{-1, 0}; + ConfigOptionFloats resolved(machine); + REQUIRE(resolved.apply_override(&filament, slot_index)); + REQUIRE(resolved.values == std::vector({10., 42.})); + } +} + +TEST_CASE("get_config_index_base resolves (volume type, extruder type, id) to a slot", "[Config]") +{ + const std::vector variant_list = {"Direct Drive Standard", "Direct Drive High Flow", + "Direct Drive Standard", "Direct Drive High Flow"}; + const std::vector variant_ids = {1, 1, 2, 2}; + + SECTION("a matching (variant, id) pair yields its slot") { + REQUIRE(get_config_index_base(nvtStandard, etDirectDrive, 1, variant_list, variant_ids) == 0); + REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 1, variant_list, variant_ids) == 1); + REQUIRE(get_config_index_base(nvtStandard, etDirectDrive, 2, variant_list, variant_ids) == 2); + REQUIRE(get_config_index_base(nvtHighFlow, etDirectDrive, 2, variant_list, variant_ids) == 3); + } + + SECTION("no matching column falls back to slot 0") { + REQUIRE(get_config_index_base(nvtStandard, etDirectDrive, 3, variant_list, variant_ids) == 0); + REQUIRE(get_config_index_base(nvtStandard, etBowden, 1, variant_list, variant_ids) == 0); + } + + SECTION("Hybrid is not a preset variant string and falls back to slot 0") { + REQUIRE(get_config_index_base(nvtHybrid, etDirectDrive, 2, variant_list, variant_ids) == 0); + } +} + TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]") { std::vector> nozzle_volume_types;