feat(engine): write per-variant filament values back for selector regroups

When the per-layer filament selector (enable_filament_dynamic_map)
migrates a filament across nozzle variants (e.g. Standard -> High Flow),
the config write-back only stored the derived extruder map; every
per-variant filament value (retraction, nozzle temperature, flow,
flush...) kept the numbers resolved from the pre-slice static mapping.

Now both dynamic write-back sites (the by-layer branch and the
sequential stitch) branch on the result's dynamic support. Migrating
results run a mixed-filament expansion that regathers every
filament_options_with_variant key from the pristine per-variant
superset, giving a migrating filament one config slot per (extruder
type x nozzle volume type) it lands on - filament_self_index,
filament_extruder_variant, and all value arrays grow in lockstep - and
recompute the retract overrides with per-slot machine indices so a nil
slot falls back to its own variant's machine value. Non-migrating
dynamic results take the merged three-map write-back so re-applies
reproduce from the written maps. Unrouted filaments resolve from the
result's own default map, so slot resolution never depends on
filament_map round-tripping through the plate config.

Print::apply reproduces the identical expansion from the persisted
group result (shared dedupe helper, expansion function, and slot
indices on both sides): the expanded keys sit in the psWipeTower /
psGCodeExport invalidate lists, so without the reproduction every
re-apply after a selector slice would diff non-empty and permanently
invalidate. cal_non_support_filaments now resolves the extruder per
layer from the published result for dynamic groupings.

filament_map_2 keeps its apply-time static derivation; nothing on the
dynamic path reads it (the per-slot machine indices key the override
merge), and per-(extruder x volume-type) machine limits in the g-code
processor remain a documented follow-up.

Every change is gated behind is_dynamic_group_reorder() or a persisted
result with dynamic support; no profile sets the flag, so the static
fleet's instruction stream is unchanged (20/20 pinned-slice byte gate
identical, incl. the sequential repro sliced twice, deterministic).

Tests: expansion unit coverage (migrating slots, unrouted fallback via
the default map, mis-sized volume map ignored, nullable retract keys in
lockstep, slot machine index layout), an end-to-end stub-driven
write-back asserting expanded slots, per-layer config-index resolution,
the override merge incl. the nil-slot variant fallback, and re-apply
stability, plus a real selector slice staying valid across re-apply.
Suites green (libslic3r 48987/168, fff_print 633/60).
This commit is contained in:
SoftFever
2026-07-12 11:15:58 +08:00
parent c8db06b1d4
commit abbd420f2a
7 changed files with 569 additions and 26 deletions

View File

@@ -1063,12 +1063,24 @@ bool ToolOrdering::cal_non_support_filaments(const PrintConfig &config,
int find_count = 0;
int find_first_filaments_count = 0;
bool has_non_support = has_non_support_filament(config);
for (const LayerTools &layer_tool : m_layer_tools) {
for (const unsigned int &filament : layer_tool.extruders) {
// The selector can move a filament between extruders per layer; resolve the extruder from
// the published result then, so the first filament attributed to an extruder is one it
// actually prints there. Static results keep the cross-layer filament_map arithmetic.
const bool use_dynamic_map = m_nozzle_group_result.is_support_dynamic_nozzle_map() && m_nozzle_group_result.get_layer_count() > 0;
auto extruder_for_filament = [&](unsigned int filament, size_t layer_idx) -> int {
if (use_dynamic_map)
return m_nozzle_group_result.get_extruder_id(static_cast<int>(filament), static_cast<int>(layer_idx));
return config.filament_map.values[filament] - 1;
};
for (size_t layer_idx = 0; layer_idx < m_layer_tools.size(); ++layer_idx) {
for (const unsigned int &filament : m_layer_tools[layer_idx].extruders) {
//check first filament
if (!config.filament_map.values.empty() && initial_filaments[config.filament_map.values[filament] - 1] == -1) {
initial_filaments[config.filament_map.values[filament] - 1] = filament;
find_first_filaments_count++;
if (!config.filament_map.values.empty()) {
const int extruder_id = extruder_for_filament(filament, layer_idx);
if (extruder_id >= 0 && extruder_id < static_cast<int>(initial_filaments.size()) && initial_filaments[extruder_id] == -1) {
initial_filaments[extruder_id] = filament;
find_first_filaments_count++;
}
}
if (has_non_support) {
@@ -1083,8 +1095,9 @@ bool ToolOrdering::cal_non_support_filaments(const PrintConfig &config,
if (config.filament_map.values.empty())
return true;
if (initial_non_support_filaments[config.filament_map.values[filament] - 1] == -1) {
initial_non_support_filaments[config.filament_map.values[filament] - 1] = filament;
const int extruder_id = extruder_for_filament(filament, layer_idx);
if (extruder_id >= 0 && extruder_id < static_cast<int>(initial_non_support_filaments.size()) && initial_non_support_filaments[extruder_id] == -1) {
initial_non_support_filaments[extruder_id] = filament;
find_count++;
}
@@ -2080,17 +2093,15 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
auto result = MultiNozzleUtils::LayeredNozzleGroupResult::create(nozzle_map_per_layer, grouping_context.nozzle_info.nozzle_list, used_filaments, filament_sequences);
grouping_result = result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
// Derive the extruder-level map for the stats path + config write-back.
// Orca: the dynamic (per-layer) result carries no single volume/nozzle map, so only the
// extruder map is written back here; the full per-nozzle config write-back
// (a dedicated dynamic-map path) is a follow-up behind the dev flag.
// Derive the extruder-level map for the stats path; the write-back resolves the
// per-variant slots from the full grouping result itself.
std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based
if (!derived_maps.empty()) {
filament_maps = derived_maps;
// A sequential per-object plan must not write its own map: the objects' plans are
// stitched print-wide afterwards and written back once from there.
if (not_sequential)
m_print->update_filament_maps_to_config(filament_maps);
m_print->update_to_config_by_nozzle_group_result(grouping_result);
}
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
}

View File

@@ -2603,12 +2603,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
stitched_layer_filaments, used_filaments, physical_unprintables,
geometric_unprintables, filament_unprintable_volumes);
this->set_nozzle_group_result(std::make_shared<MultiNozzleUtils::LayeredNozzleGroupResult>(stitched));
// Orca: the dynamic (per-layer) result carries no single volume/nozzle map, so only
// the extruder map is written back (matching the by-layer selector branch); the full
// per-nozzle config write-back is a follow-up behind the dev flag.
std::vector<int> derived_maps = stitched.get_extruder_map(false); // 1-based
if (!derived_maps.empty())
update_filament_maps_to_config(derived_maps);
update_to_config_by_nozzle_group_result(stitched);
}
}
else {
@@ -3370,6 +3365,133 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<
m_has_auto_filament_map_result = true;
}
bool Print::collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
const DynamicPrintConfig& config,
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const
{
auto opt_filament_type = config.option<ConfigOptionStrings>("filament_type");
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(config.option("extruder_type"));
if (!opt_filament_type || !opt_extruder_type)
return false;
const size_t filament_count = opt_filament_type->values.size();
const size_t extruder_count = opt_extruder_type->values.size();
auto add_use = [&](std::set<FilamentVariantUse> &variant_set, const MultiNozzleUtils::NozzleInfo &nozzle) {
// Orca: a persisted result can outlive a printer swap; never index the extruder
// arrays with a stale nozzle record.
if (nozzle.extruder_id < 0 || static_cast<size_t>(nozzle.extruder_id) >= extruder_count)
return;
FilamentVariantUse use;
use.extruder_type = static_cast<ExtruderType>(opt_extruder_type->get_at(nozzle.extruder_id));
use.nozzle_volume_type = nozzle.volume_type;
use.extruder_id = nozzle.extruder_id;
variant_set.insert(use);
};
for (size_t f_index = 0; f_index < filament_count; ++f_index) {
std::set<FilamentVariantUse> variant_set;
for (const MultiNozzleUtils::NozzleInfo &nozzle : group_result.get_nozzles_for_filament(static_cast<int>(f_index)))
add_use(variant_set, nozzle);
// A filament the plan never routes (not printed) still needs a deterministic slot: take
// its default-map assignment from the result itself, so both the slice-time write-back
// and the apply-time reproduction resolve the same slot even when the surrounding
// filament_map has not round-tripped through the plate config in between.
if (variant_set.empty()) {
if (auto default_nozzle = group_result.get_nozzle_for_filament(static_cast<int>(f_index), -1); default_nozzle.has_value())
add_use(variant_set, *default_nozzle);
}
// Filaments still without a variant stay absent from the map: the slot rebuild then
// resolves them from their static filament_map / filament_volume_map assignment.
if (!variant_set.empty())
uses[static_cast<int>(f_index)] = std::vector<FilamentVariantUse>(variant_set.begin(), variant_set.end());
}
return true;
}
void Print::update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result)
{
std::vector<int> derived_maps = group_result.get_extruder_map(false); // 1-based
if (derived_maps.empty())
return;
if (!group_result.is_support_dynamic_nozzle_map()) {
// No filament actually migrated between nozzles, so the plan reduces to a single
// grouping: write all maps like the static paths do, and the next apply re-derives
// identical slots from the written maps.
std::vector<int> base_filament_map = m_config.filament_map.values;
if (base_filament_map.size() != derived_maps.size())
base_filament_map.assign(derived_maps.size(), 1);
std::vector<int> base_volume_map = m_config.filament_volume_map.values;
if (base_volume_map.size() != derived_maps.size())
base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
const std::vector<unsigned int> used_filaments = group_result.get_used_filaments();
update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
FilamentGroupUtils::update_used_filament_values(base_volume_map, group_result.get_volume_map(), used_filaments),
group_result.get_nozzle_map());
return;
}
// Orca: keep the coarse per-filament extruder map published even though the per-layer truth
// lives in the grouping result: the pre-export consumers, the plate read-back after slicing
// and the preview panel all key on filament_map. The write is direct — the full map
// write-back's single-slot rebuild would undo the per-variant expansion below.
m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->values = derived_maps;
m_config.filament_map.values = derived_maps;
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
if (!collect_filament_variant_uses(group_result, m_ori_full_print_config, filament_variant_uses)) {
// Degenerate config (no filament/extruder typing): fall back to the single-slot
// write-back so the maps and overrides stay coherent.
update_filament_maps_to_config(derived_maps);
return;
}
int extruder_count = 1, extruder_volume_type_count = 1;
m_ori_full_print_config.support_different_extruders(extruder_count);
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);
// Note: filament_map_2 keeps its apply-time (static) derivation here; the per-slot machine
// indices below key the override merge instead, so nothing on this path reads it. Its other
// consumers are the three-map write-back (which recomputes it) and the diagnostic copy in
// the g-code header; per-(extruder x volume-type) machine limits in the g-code processor
// remain a follow-up (see GCodeProcessor::get_axis_max_feedrate).
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");
std::vector<int> slot_machine_indices;
m_full_print_config.update_filament_config_values_for_multiple_extruders(m_full_print_config, filament_variant_uses,
extruder_count, extruder_volume_type_count,
filament_keys, "filament_self_index", "filament_extruder_variant",
&slot_machine_indices);
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
const std::string filament_prefix = "filament_";
t_config_option_keys print_diff;
DynamicPrintConfig filament_overrides;
for (auto& opt_key: extruder_retract_keys)
{
const ConfigOption *opt_new_filament = m_full_print_config.option(filament_prefix + opt_key);
const ConfigOption *opt_new_machine = m_full_print_config.option(opt_key);
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, slot_machine_indices);
}
{
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
keys.push_back("filament_self_index");
m_config.apply_only(m_full_print_config, keys, true);
}
if (!print_diff.empty()) {
m_placeholder_parser.apply_config(filament_overrides);
m_config.apply(filament_overrides);
}
update_filament_self_index_cache();
m_has_auto_filament_map_result = true;
}
void Print::apply_config_for_render(const DynamicConfig &config)
{
m_config.apply(config);

View File

@@ -1006,6 +1006,13 @@ public:
const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
void update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps = std::vector<int>{}, std::vector<int> f_nozzle_maps = std::vector<int>{});
// Write-back for a selector (per-layer planned) grouping result. When a filament actually
// migrates between nozzle variants, rebuilds the per-slot filament arrays so it holds one
// slot per variant and recomputes the extruder retract overrides against the expanded
// slots — update_filament_maps_to_config's single-slot rebuild cannot represent a
// migration. A result without migration reduces to a single grouping and takes the
// three-map write-back like the static paths.
void update_to_config_by_nozzle_group_result(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result);
void apply_config_for_render(const DynamicConfig &config);
// 1 based group ids
@@ -1242,6 +1249,15 @@ private:
void _make_wipe_tower();
void finalize_first_layer_convex_hull();
void update_filament_self_index_cache();
// Deduplicates, per filament, the (extruder type x volume type) variants the grouping
// result routes it through; filaments the plan never routes get their default-map
// assignment so the slot resolution never depends on the (mutable) filament_map. config
// must carry extruder_type; returns false when it does not. Both the slice-time write-back
// and the apply-time reproduction call this with m_ori_full_print_config so the two
// expansions resolve identical slots.
bool collect_filament_variant_uses(const MultiNozzleUtils::LayeredNozzleGroupResult& group_result,
const DynamicPrintConfig& config,
std::unordered_map<int, std::vector<FilamentVariantUse>>& uses) const;
// Islands of objects and their supports extruded at the 1st layer.
Polygons first_layer_islands() const;

View File

@@ -224,7 +224,11 @@ static t_config_option_keys print_config_diffs(
const DynamicPrintConfig &new_full_config,
DynamicPrintConfig &filament_overrides,
int plate_index,
std::vector<int>& filament_maps)
std::vector<int>& filament_maps,
// Per-slot machine indices when the filament arrays hold the per-variant expansion of a
// selector result (one slot per variant a filament migrates through); the per-filament
// map cannot index the expanded override arrays. Null on the single-slot path.
const std::vector<int>* dynamic_override_indices = nullptr)
{
const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys();
const std::string filament_prefix = "filament_";
@@ -240,9 +244,14 @@ 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) {
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;
std::vector<int> filament_map_indices;
if (dynamic_override_indices)
filament_map_indices = *dynamic_override_indices;
else {
filament_map_indices.assign(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
@@ -1171,6 +1180,9 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
int extruder_count = 1, extruder_volume_type_count = 1;
bool different_extruder = false;
// Filled only when the filament arrays are rebuilt from a persisted selector result below;
// print_config_diffs then keys the retract overrides per expanded slot.
std::vector<int> dynamic_slot_indices;
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);
@@ -1189,7 +1201,20 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
std::set<std::string> filament_keys = filament_options_with_variant;
filament_keys.insert("filament_self_index");
if ((extruder_count > 1) || different_extruder)
// A persisted selector result with an actual migration means the last slice rebuilt the
// per-slot filament arrays from it (one slot per variant a filament prints through).
// Reproduce that exact expansion here so an unchanged config diffs empty — the expanded
// keys invalidate the wipe tower / g-code export, and the placeholder parser aliases
// the full config — instead of trimming back to one slot per filament.
auto group_result = std::dynamic_pointer_cast<MultiNozzleUtils::LayeredNozzleGroupResult>(this->get_nozzle_group_result());
std::unordered_map<int, std::vector<FilamentVariantUse>> filament_variant_uses;
if (group_result && group_result->is_support_dynamic_nozzle_map()
&& collect_filament_variant_uses(*group_result, m_ori_full_print_config, filament_variant_uses))
new_full_config.update_filament_config_values_for_multiple_extruders(m_ori_full_print_config, filament_variant_uses,
extruder_count, extruder_volume_type_count, filament_keys,
"filament_self_index", "filament_extruder_variant",
&dynamic_slot_indices);
else 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");
}
@@ -1210,7 +1235,8 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
// Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles.
DynamicPrintConfig filament_overrides;
//BBS: add plate index
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps);
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps,
dynamic_slot_indices.empty() ? nullptr : &dynamic_slot_indices);
// Orca: filament_map_2 is engine-derived state, never a user input: the rebuild below
// recomputes it from filament_map/filament_volume_map/the variant slots on every apply
// (all of which are diffed and invalidation-listed on their own), and the grouping

View File

@@ -10759,6 +10759,145 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
}
}
// Regathers a vector option's values through per-slot source indices (one input index per
// output slot). Out-of-range indices keep the first value, matching get_at's fallback.
template<typename OptType, typename ValueType>
static void gather_option_values(const std::string &key, OptType *opt, const std::vector<int> &slot_param_indices)
{
if (!opt || opt->values.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% not found or empty, skipping")%__LINE__%key;
return;
}
std::vector<ValueType> new_values;
new_values.reserve(slot_param_indices.size());
for (int idx : slot_param_indices) {
if (idx < 0 || static_cast<size_t>(idx) >= opt->values.size()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: option %2% slot index %3% out of range, keeping first value")%__LINE__%key%idx;
new_values.emplace_back(opt->values.front());
}
else
new_values.emplace_back(opt->values[idx]);
}
opt->values = std::move(new_values);
}
void DynamicPrintConfig::update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
int extruder_count, int extruder_nozzle_volume_count,
std::set<std::string>& key_set, std::string id_name, std::string variant_name,
std::vector<int>* slot_machine_indices)
{
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_filament_map = printer_config.option<ConfigOptionInts>("filament_map");
if (!opt_filament_map) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: filament_map option not found, skipping")%__LINE__;
return;
}
std::vector<int> filament_maps = opt_filament_map->values;
size_t filament_count = filament_maps.size();
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(printer_config.option("extruder_type"));
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(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<const ConfigOptionInts*>(printer_config.option("filament_volume_map"));
std::vector<int> filament_volume_maps;
// Orca: same sizing guard as the single-slot rebuild above — honour the per-filament volume
// map only when a producer sized it to the filament count.
if (opt_filament_volume_maps && 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<const ConfigOptionInts*>(this->option(id_name));
std::vector<int> slot_param_indices;
slot_param_indices.reserve(filament_count * 2);
if (slot_machine_indices) {
slot_machine_indices->clear();
slot_machine_indices->reserve(filament_count * 2);
}
auto resolve_slot = [&](int f_index, ExtruderType extruder_type, NozzleVolumeType nozzle_volume_type, int extruder_id_1based) {
int param_index = get_index_for_extruder(f_index + 1, id_name, extruder_type, nozzle_volume_type, variant_name);
if (param_index < 0) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(", Line %1%: could not found extruder_type %2%, nozzle_volume_type %3%, filament_index %4%, extruder index %5%")
%__LINE__ %s_keys_names_ExtruderType[extruder_type] % s_keys_names_NozzleVolumeType[nozzle_volume_type] % (f_index+1) %extruder_id_1based;
assert(false);
//for some updates happens in a invalid state(caused by popup window)
//we need to avoid crash
param_index = 0;
if (opt_ids) {
for (int i = 0; i < opt_ids->values.size(); i++)
if (opt_ids->values[i] == (f_index + 1)) {
param_index = i;
break;
}
}
}
slot_param_indices.push_back(param_index);
if (slot_machine_indices) {
// Orca: key each output slot to the machine slot of its own variant (the same
// derivation the per-filament slot map uses), so the retract-override nil fallback
// stays aligned when a filament occupies more than one slot.
int machine_index = printer_config.get_index_for_extruder(extruder_id_1based, "print_extruder_id", extruder_type, nozzle_volume_type, "print_extruder_variant");
slot_machine_indices->push_back(std::max(machine_index, 0));
}
};
for (int f_index = 0; f_index < static_cast<int>(filament_count); f_index++) {
auto uses_it = filament_variant_uses.find(f_index);
if (uses_it != filament_variant_uses.end() && !uses_it->second.empty()) {
for (const FilamentVariantUse &use : uses_it->second)
resolve_slot(f_index, use.extruder_type, use.nozzle_volume_type, use.extruder_id + 1);
}
else {
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]);
resolve_slot(f_index, extruder_type, nozzle_volume_type, filament_maps[f_index]);
}
}
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;
}
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;
continue;
}
switch (optdef->type) {
case coStrings: gather_option_values<ConfigOptionStrings, std::string>(key, this->option<ConfigOptionStrings>(key), slot_param_indices); break;
case coInts: gather_option_values<ConfigOptionInts, int>(key, this->option<ConfigOptionInts>(key), slot_param_indices); break;
case coFloats: gather_option_values<ConfigOptionFloats, double>(key, this->option<ConfigOptionFloats>(key), slot_param_indices); break;
case coPercents: gather_option_values<ConfigOptionPercents, double>(key, this->option<ConfigOptionPercents>(key), slot_param_indices); break;
case coFloatsOrPercents: gather_option_values<ConfigOptionFloatsOrPercents, FloatOrPercent>(key, this->option<ConfigOptionFloatsOrPercents>(key), slot_param_indices); break;
case coBools: gather_option_values<ConfigOptionBools, unsigned char>(key, this->option<ConfigOptionBools>(key), slot_param_indices); break;
case coEnums: gather_option_values<ConfigOptionEnumsGeneric, int>(key, this->option<ConfigOptionEnumsGeneric>(key), slot_param_indices); break;
default:
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", Line %1%: unsupported option type for %2%")%__LINE__%key;
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 slot resolution above still needs the original ids
std::vector<int> new_values;
new_values.reserve(slot_param_indices.size());
for (int idx : slot_param_indices)
new_values.push_back(opt_ids->get_at(idx));
const_cast<ConfigOptionInts*>(opt_ids)->values = std::move(new_values);
}
}
namespace {
// Options in printer_options_with_variant_2 are stored as (normal,silent) pairs per printer variant.
// Some legacy presets/projects carry a variant list but still store only one pair; normalize to avoid crashes.

View File

@@ -683,6 +683,23 @@ class StaticPrintConfig;
// Minimum object distance for arrangement, based on printer technology.
double min_object_distance(const ConfigBase &cfg);
// One (extruder type x nozzle volume type) parameter variant a filament prints through, plus a
// representative physical extruder observed using it. Ordering (and set-dedup identity) covers
// the variant pair only, so the same variant reached through two extruders keeps one config slot.
struct FilamentVariantUse
{
ExtruderType extruder_type{etDirectDrive};
NozzleVolumeType nozzle_volume_type{nvtStandard};
int extruder_id{0}; // 0-based, first extruder seen using this variant
bool operator<(const FilamentVariantUse &other) const
{
if (extruder_type != other.extruder_type)
return extruder_type < other.extruder_type;
return nozzle_volume_type < other.nozzle_volume_type;
}
};
// Slic3r dynamic configuration, used to override the configuration
// per object, per modification volume or per printing material.
// The dynamic configuration is also used to store user modifications of the print global parameters,
@@ -748,6 +765,18 @@ public:
std::vector<int> update_values_to_printer_extruders(DynamicPrintConfig& printer_config, int extruder_count, int extruder_nozzle_volume_count, std::vector<std::vector<NozzleVolumeType>>& nv_types,
std::set<std::string>& 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<std::string>& key_set, std::string id_name, std::string variant_name);
// Rebuilds the per-slot filament arrays from a per-layer grouping outcome: a filament that
// prints through several (extruder x nozzle volume type) variants keeps one slot per variant
// (unlike the single-slot rebuild above), so layer-aware consumers can resolve the slot the
// current layer actually prints with. Filaments absent from filament_variant_uses keep a
// single slot resolved from filament_map / filament_volume_map. When slot_machine_indices is
// non-null it receives one machine-variant slot index per output slot (the nil-value fallback
// keying for the extruder retract overrides; a per-filament map cannot index expanded arrays).
void update_filament_config_values_for_multiple_extruders(DynamicPrintConfig& printer_config,
const std::unordered_map<int, std::vector<FilamentVariantUse>>& filament_variant_uses,
int extruder_count, int extruder_nozzle_volume_count,
std::set<std::string>& key_set, std::string id_name, std::string variant_name,
std::vector<int>* slot_machine_indices = nullptr);
void update_non_diff_values_to_base_config(DynamicPrintConfig& new_config, const t_config_option_keys& keys, const std::set<std::string>& different_keys, std::string extruder_id_name, std::string extruder_variant_name,
std::set<std::string>& key_set1, std::set<std::string>& key_set2);