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.
This commit is contained in:
SoftFever
2026-07-11 02:29:40 +08:00
parent 13cd8d4864
commit 18b9279c26
8 changed files with 334 additions and 37 deletions

View File

@@ -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<int>& 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];
}

View File

@@ -1167,8 +1167,8 @@ static std::vector<MultiNozzleUtils::NozzleInfo> 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<ConfigOptionBool>(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<ConfigOptionFloat>(key)) return o->value;
return def;
}
static std::vector<int> cfg_ints(const ConfigBase& c, const char* key)
{
if (auto* o = c.option<ConfigOptionInts>(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<int>(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<int>(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<LayeredNozzleGroupResult> 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<float>(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);

View File

@@ -3211,6 +3211,29 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps)
std::vector<std::vector<NozzleVolumeType>> 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<const ConfigOptionEnumsGeneric*>(m_ori_full_print_config.option("extruder_type"));
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(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<std::string> 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<int> 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<int> 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<int> Print::get_filament_maps() const
return m_config.filament_map.values;
}
std::vector<int> Print::get_filament_nozzle_maps() const
{
return m_config.filament_nozzle_map.values;
}
std::vector<int> 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<int> values;
if (m_full_print_config.has("filament_self_index")) {
values = m_full_print_config.option<ConfigOptionInts>("filament_self_index")->values;
} else if (m_ori_full_print_config.has("filament_self_index")) {
values = m_ori_full_print_config.option<ConfigOptionInts>("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<std::string> &variant_list, const std::vector<int>& 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<std::string> &variant_list, const std::vector<int>& 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
{

View File

@@ -23,6 +23,7 @@
#include <functional>
#include <set>
#include <unordered_map>
#include "calib.hpp"
@@ -1010,6 +1011,8 @@ public:
// 1 based group ids
std::vector<int> get_filament_maps() const;
FilamentMapMode get_filament_map_mode() const;
std::vector<int> get_filament_volume_maps() const;
std::vector<int> 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<float, float> 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<int>{}(k.filament_id);
size_t h2 = std::hash<int>{}(static_cast<int>(k.extruder));
size_t h3 = std::hash<int>{}(static_cast<int>(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<int>{}(k.filament_id);
size_t h2 = std::hash<int>{}(k.extruder_id);
size_t h3 = std::hash<int>{}(static_cast<int>(k.extruder));
size_t h4 = std::hash<int>{}(static_cast<int>(k.nozzle_volume_type));
return h1 ^ (h2 << 8) ^ (h3 << 12) ^ (h4 << 16);
}
};
using FilamentIndexMap = std::unordered_map<FilamentIndexKey, int, FilamentIndexKeyHash>;
using PrintIndexMap = std::unordered_map<PrintIndexKey, int, PrintIndexKeyHash>;
int get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& self_index_list, FilamentIndexMap &index_map);
int get_config_index(int filament_id, int layer_id, const std::vector<std::string> &variant_list, const std::vector<int>& 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<MultiNozzleUtils::NozzleGroupResultBase> 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<int> m_filament_self_index;
// Following section will be consumed by the GCodeGenerator.
ToolOrdering m_tool_ordering;
WipeTowerData m_wipe_tower_data {m_tool_ordering};

View File

@@ -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<int> 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<const ConfigOptionEnumsGeneric*>(new_full_config.option("extruder_type"));
auto opt_filament_volume_maps = dynamic_cast<const ConfigOptionInts*>(new_full_config.option("filament_volume_map"));
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(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.

View File

@@ -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<int>& f_maps)
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& 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) {

View File

@@ -774,7 +774,7 @@ extern std::set<std::string> printer_options_with_variant_2;
extern std::set<std::string> 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<int>& f_maps);
t_config_option_keys& diff_keys, DynamicPrintConfig& filament_overrides, std::vector<int>& f_map_indices);
void handle_legacy_sla(DynamicPrintConfig &config);
@@ -1447,8 +1447,12 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionInts, required_nozzle_HRC))
((ConfigOptionEnum<FilamentMapMode>, 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<BedTempFormula>, bed_temperature_formula))
((ConfigOptionInts, physical_extruder_map))

View File

@@ -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<int> slot_index{2, 0};
ConfigOptionFloats resolved(machine);
REQUIRE(resolved.apply_override(&filament, slot_index));
REQUIRE(resolved.values == std::vector<double>({30., 42.}));
}
SECTION("an index past the machine slots falls back to the first slot") {
std::vector<int> slot_index{5, 0};
ConfigOptionFloats resolved(machine);
REQUIRE(resolved.apply_override(&filament, slot_index));
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
}
SECTION("a negative index (unresolved slot) falls back to the first slot") {
std::vector<int> slot_index{-1, 0};
ConfigOptionFloats resolved(machine);
REQUIRE(resolved.apply_override(&filament, slot_index));
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
}
}
TEST_CASE("get_config_index_base resolves (volume type, extruder type, id) to a slot", "[Config]")
{
const std::vector<std::string> variant_list = {"Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard", "Direct Drive High Flow"};
const std::vector<int> 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<std::vector<NozzleVolumeType>> nozzle_volume_types;