feat(engine): wire the per-filament volume-map producer pipeline

- Print::update_filament_maps_to_config takes filament/volume/nozzle
  maps, backfills an empty volume map from extruder types, rebuilds
  filament_map_2, re-expands the per-filament variant arrays, and
  recomputes retract overrides keyed by resolved slots
- grouping writes its result back in every non-sequential mode;
  manual multi-nozzle grouping validates the user mapping and raises a
  translatable error on deviation; the engine's concrete volume
  assignment is deliberately not merged yet (per-filament arrays are
  already consumed by filament id, so materializing High Flow now
  would change motion before the layer-aware resolvers land)
- Print::apply treats the three map keys as engine outputs in auto
  modes (erased from the diff and adopted), compares them against used
  filaments in manual mode, and keeps the pre-expansion snapshot in
  sync with the late normalization pass so rebuilt headers reflect the
  sliced state instead of resurrecting stale values
- volume/nozzle maps and extruder_nozzle_stats join the invalidation
  group of filament_map (wipe tower + skirt/brim)
- PresetBundle composes full configs with an optional per-filament
  volume map (plate map, else defaults derived from each extruder's
  flow type); project config keeps the map sized across filament
  count changes
- PartPlate stores per-plate volume/nozzle maps; Plater injects them
  at every slice-composition site (incl. g-code reload and wipe-tower
  estimation); BackgroundSlicingProcess reads engine results back to
  the plate in auto modes
- per-filament map trust guards relaxed to size-match everywhere now
  that every producer sizes the map; single-filament explicit flow
  assignments are honored
- tests: grouping volume maps stay concrete, merge semantics of
  update_used_filament_values, single-filament override honoring

Motion g-code is byte-identical fleet-wide including Hybrid projects
(19-fixture gate + repro determinism double-slice). Header deltas:
the map keys now dump real values, and stale pre-normalization values
(e.g. enable_prime_tower on single-used-filament prints) no longer
leak into the config block.
This commit is contained in:
SoftFever
2026-07-11 04:38:31 +08:00
parent 18b9279c26
commit 03a704df7c
15 changed files with 429 additions and 70 deletions
+3
View File
@@ -10467,6 +10467,9 @@ msgstr ""
msgid "Grouping error: " msgid "Grouping error: "
msgstr "" msgstr ""
msgid "Group error in manual mode. Please check nozzle count or regroup."
msgstr ""
msgid " can not be placed in the " msgid " can not be placed in the "
msgstr "" msgstr ""
+4 -2
View File
@@ -4512,8 +4512,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (m_curr_plater){ if (m_curr_plater){
auto filament_volume_map = get_vector_from_string(value); auto filament_volume_map = get_vector_from_string(value);
for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) { for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) {
// Only Standard(0)/High Flow(1) are currently supported; clamp any higher // The map feeds per-filament slot resolution and grouping. Clamp any
// volume-type (TPU High Flow/Hybrid) back to Standard until they are wired in. // higher volume-type back to Standard(0) on load: Hybrid(2) is only an
// in-memory grouping seed that is never persisted, and TPU High Flow(3)
// is clamped with the same information loss on every load.
if (filament_volume_map[idx] > 1) { if (filament_volume_map[idx] > 1) {
filament_volume_map[idx] = 0; filament_volume_map[idx] = 0;
} }
+57 -18
View File
@@ -1332,12 +1332,12 @@ static FilamentGroupContext build_filament_group_context(
context.group_info.has_filament_switcher = has_filament_switcher; context.group_info.has_filament_switcher = has_filament_switcher;
// hybrid flow means no special per-filament nozzle-volume request. // 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 // Orca: honour the config's per-filament volume map only when it is sized to the filament
// sized it to a multi-filament count (the trust guard used by // count. The full-config producers (PresetBundle injection, engine write-back) always size
// update_values_to_printer_extruders_for_multiple_filaments): the registered default is a // it; a mis-sized map (stale project value, CLI runs until the per-filament synthesis lands
// 1-element vector ({Standard}) that a single-filament count cannot tell apart from a real // there) must not displace the hybrid fallback rebuild_nozzle_unprintables relies on, nor be
// map, and it must not displace the hybrid fallback rebuild_nozzle_unprintables relies on. // indexed out of bounds.
if (mode == FilamentMapMode::fmmManual && filament_nums > 1 && if (mode == FilamentMapMode::fmmManual &&
print_config.filament_volume_map.values.size() == filament_nums) print_config.filament_volume_map.values.size() == filament_nums)
context.group_info.filament_volume_map = print_config.filament_volume_map.values; context.group_info.filament_volume_map = print_config.filament_volume_map.values;
else else
@@ -1487,12 +1487,12 @@ MultiNozzleUtils::LayeredNozzleGroupResult ToolOrdering::get_recommended_filamen
std::transform(manual_filament_map.begin(), manual_filament_map.end(), manual_filament_map.begin(), [](int v) { return v - 1; }); 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(); float diameter = print_config.nozzle_diameter.values.empty() ? 0.4f : (float)print_config.nozzle_diameter.values.front();
// Orca: create() indexes the volume/nozzle maps per used filament with no bounds check, so // 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 // pass them only when a producer sized them to the filament count (mis-sized maps can
// 1-element defaults are indistinguishable by size from a real single-filament map. // arrive from stale projects or CLI runs until the per-filament synthesis lands there).
// Without valid maps the fully-manual request cannot be honoured; return the empty result, // Without valid maps the fully-manual request cannot be honoured; return the empty result,
// the same failure an unsatisfiable create() yields. // the same failure an unsatisfiable create() yields.
std::optional<LayeredNozzleGroupResult> nozzle_result; std::optional<LayeredNozzleGroupResult> nozzle_result;
if (filament_nums > 1 && print_config.filament_volume_map.values.size() == filament_nums && if (print_config.filament_volume_map.values.size() == filament_nums &&
print_config.filament_nozzle_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); 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) if (!nozzle_result)
@@ -1556,7 +1556,21 @@ MultiNozzleUtils::LayeredNozzleGroupResult ToolOrdering::get_recommended_filamen
if (has_multiple_nozzle) { if (has_multiple_nozzle) {
auto result_opt = LayeredNozzleGroupResult::create(ret, context.nozzle_info.nozzle_list, used_filaments); auto result_opt = LayeredNozzleGroupResult::create(ret, context.nozzle_info.nozzle_list, used_filaments);
return result_opt ? *result_opt : LayeredNozzleGroupResult(); if (!result_opt)
return LayeredNozzleGroupResult();
auto result = *result_opt;
if (mode == FilamentMapMode::fmmManual) {
// Manual grouping must reproduce the user's filament->extruder map exactly; a
// deviation means the requested assignment cannot be satisfied by the nozzle
// inventory, which must surface as a slicing error instead of silently regrouping.
auto result_map = result.get_extruder_map();
for (auto fid : used_filaments) {
if (result_map[fid] != print_config.filament_map.values[fid] - 1) {
throw Slic3r::RuntimeError(_L("Group error in manual mode. Please check nozzle count or regroup."));
}
}
}
return result;
} }
} }
@@ -1617,11 +1631,11 @@ static MultiNozzleUtils::LayeredNozzleGroupResult build_group_result_from_map(
const size_t filament_nums = print_config.filament_colour.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(), 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; }); [](int v) { return v > 1; });
// Orca: same trust guard as the manual grouping paths — create() indexes the volume/nozzle // Orca: same sizing 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 // maps per used filament with no bounds check, so only maps sized to the filament count are
// be mistaken for a real single-filament map. Unsized maps fall through to the extruder-level // trusted (mis-sized maps can arrive from stale projects or CLI runs until the per-filament
// wrap below. // synthesis lands there). Unsized maps fall through to the extruder-level wrap below.
if (has_multiple_nozzle && filament_nums > 1 && if (has_multiple_nozzle &&
print_config.filament_volume_map.values.size() == filament_nums && print_config.filament_volume_map.values.size() == filament_nums &&
print_config.filament_nozzle_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()); float diameter = print_config.nozzle_diameter.values.empty() ? 0.4f : static_cast<float>(print_config.nozzle_diameter.values.front());
@@ -2042,8 +2056,10 @@ 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); 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(); grouping_result = result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
// Derive the extruder-level map (0-based) for the stats path + config write-back. The full // Derive the extruder-level map for the stats path + config write-back.
// per-nozzle config write-back is not yet wired. // 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.
std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based
if (!derived_maps.empty()) { if (!derived_maps.empty()) {
filament_maps = derived_maps; filament_maps = derived_maps;
@@ -2060,11 +2076,34 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
if (derived_maps.empty()) if (derived_maps.empty())
return; return;
filament_maps = derived_maps; filament_maps = derived_maps;
m_print->update_filament_maps_to_config(filament_maps);
} else if (!derived_maps.empty()) { } else if (!derived_maps.empty()) {
// Manual modes: the result mirrors the user's config map; adopt it for consistency. // Manual modes: the result mirrors the user's config map; adopt it for consistency.
filament_maps = derived_maps; filament_maps = derived_maps;
} }
// Write the maps back for every mode: used filaments adopt the engine's extruder/nozzle
// choice, unused ones keep their config assignment. In manual modes the extruder map
// mirrors the user's map (a deviation throws in get_recommended_filament_maps).
if (!derived_maps.empty()) {
// Orca: the config maps are the merge base; fall back to a synthesized base when no
// producer sized them to the filament count (CLI runs until the per-filament
// synthesis lands there), where indexing per filament would run out of bounds.
std::vector<int> base_filament_map = print_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 = print_config->filament_volume_map.values;
if (base_volume_map.size() != derived_maps.size())
base_volume_map.assign(derived_maps.size(), (int)nvtStandard);
// Orca: the engine's concrete per-filament volume assignment (get_volume_map()) is
// deliberately NOT merged into the write-back yet. The write-back re-expands the
// per-filament filament_* arrays from these maps, and those arrays are already
// consumed by filament id, so materializing a High Flow assignment here would alter
// motion g-code before the layer-aware g-code resolvers consume the assignment.
// Until those consumers land, the config keeps its own (GUI-injected or project)
// volume map, size-corrected above.
m_print->update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
base_volume_map,
grouping_result.get_nozzle_map());
}
std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; }); std::transform(filament_maps.begin(), filament_maps.end(), filament_maps.begin(), [](int value) { return value - 1; });
if (m_print->is_BBL_printer()) if (m_print->is_BBL_printer())
+71 -10
View File
@@ -58,6 +58,9 @@ static std::vector<std::string> s_project_options {
"nozzle_volume_type", "nozzle_volume_type",
"filament_map_mode", "filament_map_mode",
"filament_map", "filament_map",
// Per-filament nozzle-volume choice; project-level like filament_map so the per-filament
// slot resolution survives preset switches.
"filament_volume_map",
// Filament Track Switch device state: whether the switch is installed and ready, and // Filament Track Switch device state: whether the switch is installed and ready, and
// whether dynamic per-nozzle filament mapping is active. Persisted with the project and // whether dynamic per-nozzle filament mapping is active. Persisted with the project and
// restored from a saved 3mf; reset to false on load and set true only by live device sync. // restored from a saved 3mf; reset to false on load and set true only by live device sync.
@@ -79,7 +82,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
const DynamicPrintConfig& project_config, const DynamicPrintConfig& project_config,
std::vector<Preset>& in_filament_presets, std::vector<Preset>& in_filament_presets,
bool apply_extruder, bool apply_extruder,
std::optional<std::vector<int>> filament_maps_new) std::optional<std::vector<int>> filament_maps_new,
std::optional<std::vector<int>> filament_volume_maps_new)
{ {
DynamicPrintConfig &printer_config = in_printer_preset.config; DynamicPrintConfig &printer_config = in_printer_preset.config;
DynamicPrintConfig &print_config = in_print_preset.config; DynamicPrintConfig &print_config = in_print_preset.config;
@@ -94,12 +98,23 @@ DynamicPrintConfig PresetBundle::construct_full_config(
size_t num_filaments = in_filament_presets.size(); size_t num_filaments = in_filament_presets.size();
std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values; std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values;
std::vector<int> filament_volume_maps(num_filaments, (int)nvtStandard);
ConfigOptionInts* filament_volume_map_opt = out.option<ConfigOptionInts>("filament_volume_map");
if (filament_maps_new.has_value()) if (filament_maps_new.has_value())
filament_maps = *filament_maps_new; filament_maps = *filament_maps_new;
if (filament_volume_maps_new.has_value())
filament_volume_maps = *filament_volume_maps_new;
else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments)
filament_volume_maps = filament_volume_map_opt->values;
// in some middle state, they may be different // in some middle state, they may be different
if (filament_maps.size() != num_filaments) { if (filament_maps.size() != num_filaments) {
filament_maps.resize(num_filaments, 1); filament_maps.resize(num_filaments, 1);
} }
if (filament_volume_maps.size() != num_filaments) {
filament_volume_maps.resize(num_filaments, nvtStandard);
}
auto *extruder_diameter = dynamic_cast<const ConfigOptionFloats *>(out.option("nozzle_diameter")); auto *extruder_diameter = dynamic_cast<const ConfigOptionFloats *>(out.option("nozzle_diameter"));
// Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector. // Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector.
@@ -147,7 +162,7 @@ DynamicPrintConfig PresetBundle::construct_full_config(
// BBS: update filament config related with variants // BBS: update filament config related with variants
DynamicPrintConfig filament_config = in_filament_presets[0].config; DynamicPrintConfig filament_config = in_filament_presets[0].config;
if (apply_extruder && ((extruder_count > 1) || different_extruder)) if (apply_extruder && ((extruder_count > 1) || different_extruder))
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
out.apply(filament_config); out.apply(filament_config);
compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition()); compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_condition());
compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition()); compatible_prints_condition.emplace_back(in_filament_presets[0].compatible_prints_condition());
@@ -171,7 +186,7 @@ DynamicPrintConfig PresetBundle::construct_full_config(
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
filament_temp_configs[i] = *(filament_configs[i]); filament_temp_configs[i] = *(filament_configs[i]);
if (apply_extruder && ((extruder_count > 1) || different_extruder)) if (apply_extruder && ((extruder_count > 1) || different_extruder))
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
} }
// loop through options and apply them to the resulting config. // loop through options and apply them to the resulting config.
@@ -246,6 +261,7 @@ DynamicPrintConfig PresetBundle::construct_full_config(
out.option<ConfigOptionString>("printer_settings_id", true)->value = in_printer_preset.name; out.option<ConfigOptionString>("printer_settings_id", true)->value = in_printer_preset.name;
out.option<ConfigOptionStrings>("filament_ids", true)->values = filament_ids; out.option<ConfigOptionStrings>("filament_ids", true)->values = filament_ids;
out.option<ConfigOptionInts>("filament_map", true)->values = filament_maps; out.option<ConfigOptionInts>("filament_map", true)->values = filament_maps;
out.option<ConfigOptionInts>("filament_volume_map", true)->values = filament_volume_maps;
auto add_if_some_non_empty = [&out](std::vector<std::string> &&values, const std::string &key) { auto add_if_some_non_empty = [&out](std::vector<std::string> &&values, const std::string &key) {
bool nonempty = false; bool nonempty = false;
@@ -2701,6 +2717,9 @@ void PresetBundle::update_selections(AppConfig &config)
std::vector<int> filament_maps(filament_colors.size(), 1); std::vector<int> filament_maps(filament_colors.size(), 1);
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps; project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps;
std::vector<int> filament_volume_maps(filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
std::vector<std::string> extruder_ams_count_str; std::vector<std::string> extruder_ams_count_str;
if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) { if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) {
boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(",")); boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(","));
@@ -2845,6 +2864,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
std::vector<int> filament_maps(filament_colors.size(), 1); std::vector<int> filament_maps(filament_colors.size(), 1);
project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps; project_config.option<ConfigOptionInts>("filament_map")->values = filament_maps;
std::vector<int> filament_volume_maps(filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
project_config.option<ConfigOptionInts>("filament_volume_map")->values = filament_volume_maps;
std::vector<std::string> extruder_ams_count_str; std::vector<std::string> extruder_ams_count_str;
if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) { if (config.has_printer_setting(initial_printer_profile_name, "extruder_ams_count")) {
boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(",")); boost::algorithm::split(extruder_ams_count_str, config.get_printer_setting(initial_printer_profile_name, "extruder_ams_count"), boost::algorithm::is_any_of(","));
@@ -3021,7 +3043,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour"); ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map"); ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n); filament_color->resize(n);
// Sync filament multi colour // Sync filament multi colour
@@ -3031,6 +3053,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::vector<std::string> ne
} }
filament_color_type->resize(n); filament_color_type->resize(n);
filament_map->values.resize(n, 1); filament_map->values.resize(n, 1);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n); ams_multi_color_filment.resize(n);
// BBS set new filament color to new_color // BBS set new filament color to new_color
@@ -3058,7 +3081,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour"); ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings* filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map"); ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n); filament_color->resize(n);
// Sync filament multi colour // Sync filament multi colour
@@ -3068,6 +3091,7 @@ void PresetBundle::set_num_filaments(unsigned int n, std::string new_color)
} }
filament_color_type->resize(n); filament_color_type->resize(n);
filament_map->values.resize(n, 1); filament_map->values.resize(n, 1);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n); ams_multi_color_filment.resize(n);
//BBS set new filament color to new_color //BBS set new filament color to new_color
@@ -3108,15 +3132,20 @@ void PresetBundle::update_num_filaments(unsigned int to_del_flament_id)
ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour"); ConfigOptionStrings *filament_multi_color = project_config.option<ConfigOptionStrings>("filament_multi_colour");
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map"); ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
if (filament_color->values.size() > to_del_flament_id) { if (filament_color->values.size() > to_del_flament_id) {
filament_color->values.erase(filament_color->values.begin() + to_del_flament_id); filament_color->values.erase(filament_color->values.begin() + to_del_flament_id);
if (filament_map->values.size() > to_del_flament_id) { if (filament_map->values.size() > to_del_flament_id) {
filament_map->values.erase(filament_map->values.begin() + to_del_flament_id); filament_map->values.erase(filament_map->values.begin() + to_del_flament_id);
} }
if (filament_volume_map->values.size() > to_del_flament_id) {
filament_volume_map->values.erase(filament_volume_map->values.begin() + to_del_flament_id);
}
} }
else { else {
filament_color->values.resize(to_del_flament_id); filament_color->values.resize(to_del_flament_id);
filament_map->values.resize(to_del_flament_id, 1); filament_map->values.resize(to_del_flament_id, 1);
filament_volume_map->values.resize(to_del_flament_id, static_cast<int>(NozzleVolumeType::nvtStandard));
} }
// lambda function to erase or resize the container // lambda function to erase or resize the container
@@ -3339,6 +3368,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ConfigOptionStrings *filament_color = project_config.option<ConfigOptionStrings>("filament_colour"); ConfigOptionStrings *filament_color = project_config.option<ConfigOptionStrings>("filament_colour");
ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type"); ConfigOptionStrings *filament_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map"); ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
if (color_only) { if (color_only) {
auto get_map_index = [&ams_infos](const std::vector<AMSMapInfo> &infos, const AMSMapInfo &temp) { auto get_map_index = [&ams_infos](const std::vector<AMSMapInfo> &infos, const AMSMapInfo &temp) {
for (int i = 0; i < infos.size(); i++) { for (int i = 0; i < infos.size(); i++) {
@@ -3520,6 +3550,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_multi_color_filment = exist_multi_color_filment; ams_multi_color_filment = exist_multi_color_filment;
this->filament_presets = exist_filament_presets; this->filament_presets = exist_filament_presets;
filament_map->values.resize(exist_filament_presets.size(), 1); filament_map->values.resize(exist_filament_presets.size(), 1);
filament_volume_map->values.resize(exist_filament_presets.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
} }
else {//overwrite; else {//overwrite;
bool has_placeholders = std::any_of(ams_infos.begin(), ams_infos.end(), bool has_placeholders = std::any_of(ams_infos.begin(), ams_infos.end(),
@@ -3574,12 +3605,14 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
this->filament_presets = result_presets; this->filament_presets = result_presets;
ams_multi_color_filment = result_multi_colors; ams_multi_color_filment = result_multi_colors;
filament_map->values.resize(total, 1); filament_map->values.resize(total, 1);
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
} else { } else {
// BBL: existing wholesale replace // BBL: existing wholesale replace
filament_color->values = ams_filament_colors; filament_color->values = ams_filament_colors;
filament_color_type->values = ams_filament_color_types; filament_color_type->values = ams_filament_color_types;
this->filament_presets = ams_filament_presets; this->filament_presets = ams_filament_presets;
filament_map->values.resize(ams_filament_colors.size(), 1); filament_map->values.resize(ams_filament_colors.size(), 1);
filament_volume_map->values.resize(ams_filament_colors.size(), static_cast<int>(NozzleVolumeType::nvtStandard));
} }
auto& print_config = this->prints.get_edited_preset().config; auto& print_config = this->prints.get_edited_preset().config;
@@ -3882,10 +3915,26 @@ bool PresetBundle::support_different_extruders() const
return supported; return supported;
} }
DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional<std::vector<int>>filament_maps) const std::vector<int> PresetBundle::get_default_nozzle_volume_types_for_filaments(std::vector<int>& f_maps)
{
std::vector<int> result;
int filament_count = f_maps.size();
result.resize(filament_count, static_cast<int>(NozzleVolumeType::nvtStandard));
auto opt_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(this->project_config.option("nozzle_volume_type"));
for (int index = 0; index < filament_count; index++)
{
if (opt_nozzle_volume_type && opt_nozzle_volume_type->values.size() > (f_maps[index] - 1))
result[index] = opt_nozzle_volume_type->values[f_maps[index] - 1];
}
return result;
}
DynamicPrintConfig PresetBundle::full_config(bool apply_extruder, std::optional<std::vector<int>>filament_maps, std::optional<std::vector<int>> filament_volume_maps) const
{ {
return (this->printers.get_edited_preset().printer_technology() == ptFFF) ? return (this->printers.get_edited_preset().printer_technology() == ptFFF) ?
this->full_fff_config(apply_extruder, filament_maps) : this->full_fff_config(apply_extruder, filament_maps, filament_volume_maps) :
this->full_sla_config(); this->full_sla_config();
} }
@@ -3944,7 +3993,7 @@ const std::set<std::string> ignore_settings_list ={
"print_settings_id", "filament_settings_id", "printer_settings_id" "print_settings_id", "filament_settings_id", "printer_settings_id"
}; };
DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new) const DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps_new, std::optional<std::vector<int>> filament_volume_maps_new) const
{ {
DynamicPrintConfig out; DynamicPrintConfig out;
out.apply(FullPrintConfig::defaults()); out.apply(FullPrintConfig::defaults());
@@ -3958,8 +4007,17 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
size_t num_filaments = this->filament_presets.size(); size_t num_filaments = this->filament_presets.size();
std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values; std::vector<int> filament_maps = out.option<ConfigOptionInts>("filament_map")->values;
std::vector<int> filament_volume_maps(num_filaments, (int)nvtStandard);
ConfigOptionInts* filament_volume_map_opt = out.option<ConfigOptionInts>("filament_volume_map");
if (filament_maps_new.has_value()) if (filament_maps_new.has_value())
filament_maps = *filament_maps_new; filament_maps = *filament_maps_new;
if (filament_volume_maps_new.has_value()) {
filament_volume_maps = *filament_volume_maps_new;
out.option<ConfigOptionInts>("filament_volume_map", true)->values = filament_volume_maps;
}
else if (filament_volume_map_opt && filament_volume_map_opt->values.size() == num_filaments)
filament_volume_maps = filament_volume_map_opt->values;
//in some middle state, they may be different //in some middle state, they may be different
if (filament_maps.size() != num_filaments) { if (filament_maps.size() != num_filaments) {
filament_maps.resize(num_filaments, 1); filament_maps.resize(num_filaments, 1);
@@ -3967,6 +4025,9 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
else { else {
assert(filament_maps.size() == num_filaments); assert(filament_maps.size() == num_filaments);
} }
if (filament_volume_maps.size() != num_filaments) {
filament_volume_maps.resize(num_filaments, nvtStandard);
}
auto* extruder_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter")); auto* extruder_diameter = dynamic_cast<const ConfigOptionFloats*>(out.option("nozzle_diameter"));
// Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector. // Collect the "compatible_printers_condition" and "inherits" values over all presets (print, filaments, printers) into a single vector.
@@ -4023,7 +4084,7 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
//BBS: update filament config related with variants //BBS: update filament config related with variants
DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config; DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config;
if (apply_extruder && ((extruder_count > 1) || different_extruder)) if (apply_extruder && ((extruder_count > 1) || different_extruder))
filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0]); filament_config.update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[0], (NozzleVolumeType)filament_volume_maps[0]);
out.apply(filament_config); out.apply(filament_config);
compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition()); compatible_printers_condition.emplace_back(this->filaments.get_edited_preset().compatible_printers_condition());
compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition()); compatible_prints_condition .emplace_back(this->filaments.get_edited_preset().compatible_prints_condition());
@@ -4117,7 +4178,7 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
for (size_t i = 0; i < num_filaments; ++i) { for (size_t i = 0; i < num_filaments; ++i) {
filament_temp_configs[i] = *(filament_configs[i]); filament_temp_configs[i] = *(filament_configs[i]);
if (apply_extruder && ((extruder_count > 1) || different_extruder)) if (apply_extruder && ((extruder_count > 1) || different_extruder))
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]); filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i], (NozzleVolumeType)filament_volume_maps[i]);
} }
// loop through options and apply them to the resulting config. // loop through options and apply them to the resulting config.
+8 -3
View File
@@ -175,7 +175,8 @@ public:
const DynamicPrintConfig &project_config, const DynamicPrintConfig &project_config,
std::vector<Preset> &in_filament_presets, std::vector<Preset> &in_filament_presets,
bool apply_extruder, bool apply_extruder,
std::optional<std::vector<int>> filament_maps_new); std::optional<std::vector<int>> filament_maps_new,
std::optional<std::vector<int>> filament_volume_maps_new = std::nullopt);
// ORCA: utility function to find the vendor for a given preset name // ORCA: utility function to find the vendor for a given preset name
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type); static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
@@ -383,10 +384,14 @@ public:
bool has_defauls_only() const bool has_defauls_only() const
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); } { return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
DynamicPrintConfig full_config(bool apply_extruder = true, std::optional<std::vector<int>>filament_maps = std::nullopt) const; DynamicPrintConfig full_config(bool apply_extruder = true, std::optional<std::vector<int>>filament_maps = std::nullopt, std::optional<std::vector<int>> filament_volume_maps = std::nullopt) const;
// full_config() with the some "useless" config removed. // full_config() with the some "useless" config removed.
DynamicPrintConfig full_config_secure(std::optional<std::vector<int>>filament_maps = std::nullopt) const; DynamicPrintConfig full_config_secure(std::optional<std::vector<int>>filament_maps = std::nullopt) const;
// Default per-filament nozzle-volume types: each filament inherits the volume type of the
// extruder it maps to (1-based f_maps), Standard when unknown.
std::vector<int> get_default_nozzle_volume_types_for_filaments(std::vector<int>& f_maps);
// Per-extruder flush matrix [extruder_id][from_filament][to_filament] in mm^3, optionally scaled // Per-extruder flush matrix [extruder_id][from_filament][to_filament] in mm^3, optionally scaled
// by the per-extruder flush_multiplier (or flush_multiplier_fast when prime_volume_mode==Fast). // by the per-extruder flush_multiplier (or flush_multiplier_fast when prime_volume_mode==Fast).
// Used by the print-dispatch nozzle-mapping flush-weight estimate. // Used by the print-dispatch nozzle-mapping flush-weight estimate.
@@ -543,7 +548,7 @@ private:
/*ConfigSubstitutions load_config_file_config_bundle( /*ConfigSubstitutions load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/ const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);*/
DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps=std::nullopt) const; DynamicPrintConfig full_fff_config(bool apply_extruder, std::optional<std::vector<int>> filament_maps=std::nullopt, std::optional<std::vector<int>> filament_volume_maps=std::nullopt) const;
DynamicPrintConfig full_sla_config() const; DynamicPrintConfig full_sla_config() const;
// Orca: used for validation only // Orca: used for validation only
+59 -8
View File
@@ -334,8 +334,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "other_layers_print_sequence_nums" || opt_key == "other_layers_print_sequence_nums"
|| opt_key == "toolchange_ordering" || opt_key == "toolchange_ordering"
|| opt_key == "extruder_ams_count" || opt_key == "extruder_ams_count"
|| opt_key == "extruder_nozzle_stats"
|| opt_key == "filament_map_mode" || opt_key == "filament_map_mode"
|| opt_key == "filament_map" || opt_key == "filament_map"
|| opt_key == "filament_nozzle_map"
|| opt_key == "filament_volume_map"
|| opt_key == "filament_adhesiveness_category" || opt_key == "filament_adhesiveness_category"
|| opt_key == "filament_tower_interface_pre_extrusion_dist" || opt_key == "filament_tower_interface_pre_extrusion_dist"
|| opt_key == "filament_tower_interface_pre_extrusion_length" || opt_key == "filament_tower_interface_pre_extrusion_length"
@@ -2509,6 +2512,10 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
std::vector<int>filament_maps = this->get_filament_maps(); std::vector<int>filament_maps = this->get_filament_maps();
auto map_mode = get_filament_map_mode(); auto map_mode = get_filament_map_mode();
// get recommended filament map // get recommended filament map
// Orca: the sequential write-back stays gated to auto modes. In manual modes the
// config maps already carry the user's assignment (the per-object ToolOrdering below
// consumes them directly), so a write-back would only re-store the pre-slice values;
// keeping the gate avoids churning the config on every sequential manual slice.
if (map_mode < FilamentMapMode::fmmManual) { if (map_mode < FilamentMapMode::fmmManual) {
// Grouping returns a nozzle-aware result; the 1-based extruder map // Grouping returns a nozzle-aware result; the 1-based extruder map
// for the by-object path is derived from it. // for the by-object path is derived from it.
@@ -2516,7 +2523,25 @@ void Print::process(long long *time_cost_with_cache, bool use_cache)
auto derived_maps = grouping_result.get_extruder_map(false); auto derived_maps = grouping_result.get_extruder_map(false);
if (!derived_maps.empty()) { if (!derived_maps.empty()) {
filament_maps = derived_maps; filament_maps = derived_maps;
update_filament_maps_to_config(filament_maps); // Write the maps back: used filaments adopt the engine's extruder/nozzle
// choice, unused ones keep their config assignment.
// Orca: the config maps are the merge base; fall back to a synthesized base
// when no producer sized them to the filament count (CLI runs until the
// per-filament synthesis lands there), where indexing per filament would
// run out of bounds.
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);
// Orca: as on the non-sequential path, the engine's concrete volume
// assignment (get_volume_map()) is deliberately not merged in until the
// layer-aware g-code resolvers consume it; the config keeps its own
// (GUI-injected or project) volume map, size-corrected above.
update_filament_maps_to_config(FilamentGroupUtils::update_used_filament_values(base_filament_map, derived_maps, used_filaments),
base_volume_map,
grouping_result.get_nozzle_map());
} }
} }
// check map valid both in auto and mannual mode // check map valid both in auto and mannual mode
@@ -3198,14 +3223,30 @@ void Print::finalize_first_layer_convex_hull()
m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points);
} }
void Print::update_filament_maps_to_config(std::vector<int> f_maps) void Print::update_filament_maps_to_config(std::vector<int> f_maps, std::vector<int> f_volume_maps, std::vector<int> f_nozzle_maps)
{ {
if (m_config.filament_map.values != f_maps) if ((m_config.filament_map.values != f_maps) || (m_config.filament_volume_map.values != f_volume_maps) || (m_config.filament_nozzle_map.values != f_nozzle_maps))
{ {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament maps changed after pre-slicing."); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament maps changed after pre-slicing.");
m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->values = f_maps; m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->values = f_maps;
m_config.filament_map.values = f_maps; m_config.filament_map.values = f_maps;
if (!f_volume_maps.empty()) {
m_ori_full_print_config.option<ConfigOptionInts>("filament_volume_map", true)->values = f_volume_maps;
m_config.filament_volume_map.values = f_volume_maps;
}
else {
m_ori_full_print_config.option<ConfigOptionInts>("filament_volume_map", true)->values.resize(f_maps.size(), nvtStandard);
m_config.filament_volume_map.values.resize(f_maps.size(), nvtStandard);
}
if (!f_nozzle_maps.empty()) {
m_ori_full_print_config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = f_nozzle_maps;
m_config.filament_nozzle_map.values = f_nozzle_maps;
}
}
{
int extruder_count = 1, extruder_volume_type_count = 1; int extruder_count = 1, extruder_volume_type_count = 1;
bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count); bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count);
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types; std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
@@ -3221,15 +3262,22 @@ void Print::update_filament_maps_to_config(std::vector<int> f_maps)
auto opt_extruder_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(m_ori_full_print_config.option("extruder_type")); 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")); 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 // 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 // presets); the backfill and the override are bounds-checked because the change block above
// grouping engine does not produce per-filament volume maps yet, so m_config only carries // is skipped when the maps are unchanged, in which case the stored map may be shorter than
// a map when the project supplied one. // the filament count.
auto* ori_volume_map = m_ori_full_print_config.option<ConfigOptionInts>("filament_volume_map", true);
for (int index = 0; opt_extruder_type && opt_nozzle_volume_type && index < f_maps.size(); index++) 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)); 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)); NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1));
if ((extruder_volume_type_count > extruder_count) if (f_volume_maps.empty()) {
&& f_maps.size() > 1 && m_config.filament_volume_map.values.size() == f_maps.size()) // No per-filament map supplied: backfill from the extruder's own volume type.
if (m_config.filament_volume_map.values.size() > index)
m_config.filament_volume_map.values[index] = nozzle_volume_type;
if (ori_volume_map->values.size() > index)
ori_volume_map->values[index] = nozzle_volume_type;
}
else if ((extruder_volume_type_count > extruder_count) && (m_config.filament_volume_map.values.size() > index))
nozzle_volume_type = (NozzleVolumeType)(m_config.filament_volume_map.values[index]); 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_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");
} }
@@ -3254,8 +3302,11 @@ void Print::update_filament_maps_to_config(std::vector<int> 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); 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);
} }
if ((extruder_count > 1) || support_multi) {
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end()); 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); m_config.apply_only(m_full_print_config, keys, true);
}
if (!print_diff.empty()) { if (!print_diff.empty()) {
m_placeholder_parser.apply_config(filament_overrides); m_placeholder_parser.apply_config(filament_overrides);
m_config.apply(filament_overrides); m_config.apply(filament_overrides);
+1 -1
View File
@@ -1005,7 +1005,7 @@ public:
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const; const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
const ToolOrdering& tool_ordering() const { return m_tool_ordering; } const ToolOrdering& tool_ordering() const { return m_tool_ordering; }
void update_filament_maps_to_config(std::vector<int> f_maps); 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>{});
void apply_config_for_render(const DynamicConfig &config); void apply_config_for_render(const DynamicConfig &config);
// 1 based group ids // 1 based group ids
+56 -5
View File
@@ -1212,7 +1212,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
//BBS: process the filament_map related logic //BBS: process the filament_map related logic
std::unordered_set<std::string> print_diff_set(print_diff.begin(), print_diff.end()); std::unordered_set<std::string> print_diff_set(print_diff.begin(), print_diff.end());
if (print_diff_set.find("filament_map_mode") == print_diff_set.end()) if (!print_diff_set.empty() && print_diff_set.find("filament_map_mode") == print_diff_set.end())
{ {
FilamentMapMode map_mode = new_full_config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value; FilamentMapMode map_mode = new_full_config.option<ConfigOptionEnum<FilamentMapMode>>("filament_map_mode", true)->value;
if (is_auto_filament_map_mode(map_mode)) { if (is_auto_filament_map_mode(map_mode)) {
@@ -1224,9 +1224,29 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
old_opt->set(new_opt); old_opt->set(new_opt);
m_config.filament_map = *new_opt; m_config.filament_map = *new_opt;
} }
if (print_diff_set.find("filament_volume_map") != print_diff_set.end()) {
print_diff_set.erase("filament_volume_map");
//full_config_diff.erase("filament_volume_map");
ConfigOptionInts* old_opt = m_full_print_config.option<ConfigOptionInts>("filament_volume_map", true);
ConfigOptionInts* new_opt = new_full_config.option<ConfigOptionInts>("filament_volume_map", true);
old_opt->set(new_opt);
m_config.filament_volume_map = *new_opt;
}
if (print_diff_set.find("filament_nozzle_map") != print_diff_set.end()) {
print_diff_set.erase("filament_nozzle_map");
//full_config_diff.erase("filament_nozzle_map");
ConfigOptionInts* old_opt = m_full_print_config.option<ConfigOptionInts>("filament_nozzle_map", true);
ConfigOptionInts* new_opt = new_full_config.option<ConfigOptionInts>("filament_nozzle_map", true);
old_opt->set(new_opt);
m_config.filament_nozzle_map = *new_opt;
}
} }
else { else {
print_diff_set.erase("extruder_ams_count"); print_diff_set.erase("extruder_ams_count");
if (map_mode == fmmManual) {
// filament_nozzle_map is an engine output, not a GUI input, in manual mode
print_diff_set.erase("filament_nozzle_map");
}
std::vector<int> old_filament_map = m_config.filament_map.values; std::vector<int> old_filament_map = m_config.filament_map.values;
std::vector<int> new_filament_map = new_full_config.option<ConfigOptionInts>("filament_map", true)->values; std::vector<int> new_filament_map = new_full_config.option<ConfigOptionInts>("filament_map", true)->values;
@@ -1243,8 +1263,32 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
break; break;
} }
} }
if (same_map) if (same_map) {
print_diff_set.erase("filament_map"); print_diff_set.erase("filament_map");
// The extruder retract overrides are keyed by the (unchanged) filament map;
// recompute them and drop diffs whose recomputed value matches the current
// config, so a cosmetic reordering of unused filaments does not invalidate.
const auto& retract_keys = print_config_def.extruder_retract_keys();
const std::string filament_prefix = "filament_";
std::vector<int> old_f_map_indices(old_filament_map.size(), 0);
for (size_t i = 0; i < old_filament_map.size(); i++)
old_f_map_indices[i] = old_filament_map[i] - 1;
for (const auto& rk : retract_keys) {
if (print_diff_set.find(rk) == print_diff_set.end())
continue;
const ConfigOption* opt_old = m_config.option(rk);
const ConfigOption* opt_new_m = new_full_config.option(rk);
const ConfigOption* opt_new_f = new_full_config.option(filament_prefix + rk);
if (opt_old && opt_new_m && opt_new_f) {
std::unique_ptr<ConfigOption> opt_recomputed(opt_new_m->clone());
opt_recomputed->apply_override(opt_new_f, old_f_map_indices);
if (*opt_old == *opt_recomputed)
print_diff_set.erase(rk);
}
}
}
} }
} }
if (print_diff_set.size() != print_diff.size()) if (print_diff_set.size() != print_diff.size())
@@ -1265,11 +1309,12 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
{ {
ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(filament_maps[index] - 1)); 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)); 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 // Orca: honour the per-filament volume map only when a producer sized it to the filament
// to the filament count; the registered default is a 1-element vector (see // count; mis-sized maps (stale project values, CLI runs until the per-filament synthesis
// lands there) must not be indexed per filament (see
// update_values_to_printer_extruders_for_multiple_filaments for the same guard). // update_values_to_printer_extruders_for_multiple_filaments for the same guard).
if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps if ((extruder_volume_type_count > extruder_count) && opt_filament_volume_maps
&& filament_maps.size() > 1 && opt_filament_volume_maps->values.size() == filament_maps.size()) && opt_filament_volume_maps->values.size() == filament_maps.size())
nozzle_volume_type = (NozzleVolumeType)(opt_filament_volume_maps->values[index]); 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"); 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");
} }
@@ -1694,6 +1739,12 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
m_default_object_config.apply_only(new_full_config, new_changed_keys, true); m_default_object_config.apply_only(new_full_config, new_changed_keys, true);
// Handle changes to regions config defaults // Handle changes to regions config defaults
m_default_region_config.apply_only(new_full_config, new_changed_keys, true); m_default_region_config.apply_only(new_full_config, new_changed_keys, true);
// Orca: keep the pre-expansion snapshot in sync with this late normalization pass.
// The engine map write-back rebuilds m_full_print_config from m_ori_full_print_config
// after slicing; a stale snapshot would resurrect the un-normalized values (e.g.
// enable_prime_tower on a single-filament print) in the dumped config and spuriously
// re-invalidate the g-code on the next apply.
m_ori_full_print_config.apply_only(new_full_config, new_changed_keys, true);
m_full_print_config = std::move(new_full_config); m_full_print_config = std::move(new_full_config);
update_filament_self_index_cache(); update_filament_self_index_cache();
} }
+14 -10
View File
@@ -2743,8 +2743,10 @@ void PrintConfigDef::init_fff_params()
// Per-filament nozzle-volume-type override (multi-volume/Hybrid). Round-trips through .3mf // 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 // 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 // extruder exposes several volume types. Producers: the GUI full-config composition injects
// to the filament count until the write-back pipeline lands). // a filament-count-sized map (plate map, else per-extruder defaults) and the project config
// keeps it sized; expansion trusts the map only when its size matches the filament count
// (CLI runs may still carry the 1-element default).
def = this->add("filament_volume_map", coInts); def = this->add("filament_volume_map", coInts);
def->mode = comDevelop; def->mode = comDevelop;
def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)}); def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)});
@@ -2774,8 +2776,10 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<FilamentMapMode>(fmmAutoForFlush)); def->set_default_value(new ConfigOptionEnum<FilamentMapMode>(fmmAutoForFlush));
// Fully-manual mode (fmmNozzleManual): per-filament -> physical-nozzle assignment. // Per-filament -> physical-nozzle assignment. Written back by the grouping engine after
// Inert until the nozzle-assignment engine consumes it. Internal use only, no translation. // slicing (all non-sequential modes) and read back to the plate config; a user input only
// in fully-manual mode (fmmNozzleManual). Dumped in the g-code header for diagnostics.
// Internal use only, no translation.
def = this->add("filament_nozzle_map", coInts); def = this->add("filament_nozzle_map", coInts);
def->mode = comDevelop; def->mode = comDevelop;
def->set_default_value(new ConfigOptionInts{1}); def->set_default_value(new ConfigOptionInts{1});
@@ -10533,12 +10537,12 @@ void DynamicPrintConfig::update_values_to_printer_extruders_for_multiple_filamen
auto opt_filament_volume_maps = dynamic_cast<const ConfigOptionInts*>(printer_config.option("filament_volume_map")); auto opt_filament_volume_maps = dynamic_cast<const ConfigOptionInts*>(printer_config.option("filament_volume_map"));
std::vector<int> filament_volume_maps; std::vector<int> filament_volume_maps;
// Orca: the per-filament volume map is only trustworthy when a producer explicitly // Orca: honour the per-filament volume map only when a producer sized it to the
// sized it to the filament count; the registered default is a 1-element vector and // filament count. The full-config producers (PresetBundle injection, engine
// stale project values may be mis-sized. A single-filament config cannot be told // write-back) always size it; mis-sized maps (stale project values, CLI runs until
// apart from that default by size, so it is ignored too until the pipeline that // the per-filament synthesis lands there) must not distort slot resolution nor be
// seeds the map lands; anything untrusted must not distort slot resolution. // indexed out of bounds.
if (opt_filament_volume_maps && filament_count > 1 && opt_filament_volume_maps->values.size() == filament_count) if (opt_filament_volume_maps && opt_filament_volume_maps->values.size() == filament_count)
filament_volume_maps = opt_filament_volume_maps->values; filament_volume_maps = opt_filament_volume_maps->values;
auto opt_ids = id_name.empty()? nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name)); auto opt_ids = id_name.empty()? nullptr: dynamic_cast<const ConfigOptionInts*>(this->option(id_name));
std::vector<int> variant_index; std::vector<int> variant_index;
@@ -231,6 +231,19 @@ void BackgroundSlicingProcess::process_fff()
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) < FilamentMapMode::fmmManual) { if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) < FilamentMapMode::fmmManual) {
std::vector<int> f_maps = m_fff_print->get_filament_maps(); std::vector<int> f_maps = m_fff_print->get_filament_maps();
m_current_plate->set_filament_maps(f_maps); m_current_plate->set_filament_maps(f_maps);
// Orca: the volume map is not read back to the plate yet. The engine's concrete
// volume assignment is not written into the print config until the layer-aware
// g-code resolvers consume it (see the ToolOrdering write-back), so the print-side
// value here is just the injected plate-or-default map echoed back; persisting it
// would freeze the per-extruder defaults (including the transient Hybrid seed) into
// the plate and the saved project. Restore together with the write-back merge.
}
if (m_current_plate->get_real_filament_map_mode(preset_bundle.project_config) != FilamentMapMode::fmmNozzleManual) {
// The engine-resolved nozzle map is read back for every non-nozzle-manual mode so the
// plate config the next apply overlays matches the engine's written-back state
// (otherwise the full-config diff would invalidate the g-code on every apply).
std::vector<int> f_nozzle_maps = m_fff_print->get_filament_nozzle_maps();
m_current_plate->set_filament_nozzle_maps(f_nozzle_maps);
} }
wxCommandEvent evt(m_event_slicing_completed_id); wxCommandEvent evt(m_event_slicing_completed_id);
// Post the Slicing Finished message for the G-code viewer to update. // Post the Slicing Finished message for the G-code viewer to update.
+63 -2
View File
@@ -319,6 +319,18 @@ std::vector<int> PartPlate::get_real_filament_maps(const DynamicConfig& g_config
return g_maps; return g_maps;
} }
std::vector<int> PartPlate::get_real_filament_volume_maps(const DynamicConfig& g_config, bool* use_global_param) const
{
auto maps = get_filament_volume_maps();
if (!maps.empty()) {
if (use_global_param) { *use_global_param = false; }
return maps;
}
auto g_maps = g_config.option<ConfigOptionInts>("filament_volume_map")->values;
if (use_global_param) { *use_global_param = true; }
return g_maps;
}
FilamentMapMode PartPlate::get_real_filament_map_mode(const DynamicConfig& g_config, bool* use_global_param) const FilamentMapMode PartPlate::get_real_filament_map_mode(const DynamicConfig& g_config, bool* use_global_param) const
{ {
auto mode = get_filament_map_mode(); auto mode = get_filament_map_mode();
@@ -3506,8 +3518,16 @@ int PartPlate::load_gcode_from_file(const std::string& filename)
int ret = 0; int ret = 0;
// process gcode // process gcode
auto& preset_bundle = wxGetApp().preset_bundle;
std::vector<int> filament_maps = this->get_filament_maps(); std::vector<int> filament_maps = this->get_filament_maps();
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps); // Inject the plate's volume map (or the per-extruder defaults) exactly like the apply-time
// composition, so the config applied over the loaded slice result matches the next
// background-process apply and does not invalidate the embedded g-code.
std::vector<int> f_volume_maps = this->get_filament_volume_maps();
if (f_volume_maps.empty()) {
f_volume_maps = preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
}
DynamicPrintConfig full_config = preset_bundle->full_config(false, filament_maps, f_volume_maps);
full_config.apply(m_config, true); full_config.apply(m_config, true);
m_print->apply(*m_model, full_config, false); m_print->apply(*m_model, full_config, false);
//BBS: need to apply two times, for after the first apply, the m_print got its object, //BBS: need to apply two times, for after the first apply, the m_print got its object,
@@ -3814,6 +3834,40 @@ void PartPlate::clear_filament_map()
m_config.erase("filament_map"); m_config.erase("filament_map");
} }
std::vector<int> PartPlate::get_filament_volume_maps() const
{
std::string key = "filament_volume_map";
if (m_config.has(key))
return m_config.option<ConfigOptionInts>(key)->values;
return {};
}
void PartPlate::set_filament_volume_maps(const std::vector<int>& f_maps)
{
m_config.option<ConfigOptionInts>("filament_volume_map", true)->values = f_maps;
}
void PartPlate::clear_filament_volume_map()
{
if (m_config.has("filament_volume_map"))
m_config.erase("filament_volume_map");
}
std::vector<int> PartPlate::get_filament_nozzle_maps() const
{
std::string key = "filament_nozzle_map";
if (m_config.has(key))
return m_config.option<ConfigOptionInts>(key)->values;
return {};
}
void PartPlate::set_filament_nozzle_maps(const std::vector<int>& f_maps)
{
m_config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = f_maps;
}
void PartPlate::clear_filament_map_mode() void PartPlate::clear_filament_map_mode()
{ {
if (m_config.has("filament_map_mode")) if (m_config.has("filament_map_mode"))
@@ -4247,7 +4301,14 @@ void PartPlateList::set_default_wipe_tower_pos_for_plate(int plate_idx, bool ini
coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1); coordf_t plate_bbox_y_max_local_coord = plate_bbox_2d.max(1) - plate_origin(1);
std::vector<int> filament_maps = part_plate->get_real_filament_maps(proj_cfg); std::vector<int> filament_maps = part_plate->get_real_filament_maps(proj_cfg);
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps); // Keep this composition consistent with the apply-time injection (plate volume map, else
// per-extruder defaults); the config below currently only feeds scalar reads, but a
// divergent volume map would silently mis-resolve any future per-filament read here.
std::vector<int> f_volume_maps = part_plate->get_filament_volume_maps();
if (f_volume_maps.empty()) {
f_volume_maps = wxGetApp().preset_bundle->get_default_nozzle_volume_types_for_filaments(filament_maps);
}
DynamicPrintConfig full_config = wxGetApp().preset_bundle->full_config(false, filament_maps, f_volume_maps);
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config; const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value; float w = dynamic_cast<const ConfigOptionFloat *>(print_cfg.option("prime_tower_width"))->value;
float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value; float v = dynamic_cast<const ConfigOptionFloat *>(full_config.option("prime_volume"))->value;
+10
View File
@@ -253,6 +253,7 @@ public:
PrintSequence get_real_print_seq(bool* plate_same_as_global=nullptr) const; PrintSequence get_real_print_seq(bool* plate_same_as_global=nullptr) const;
std::vector<int> get_real_filament_maps(const DynamicConfig& g_config, bool* use_global_param = nullptr)const; std::vector<int> get_real_filament_maps(const DynamicConfig& g_config, bool* use_global_param = nullptr)const;
std::vector<int> get_real_filament_volume_maps(const DynamicConfig& g_config, bool* use_global_param = nullptr) const;
FilamentMapMode get_real_filament_map_mode(const DynamicConfig& g_config,bool * use_global_param = nullptr) const; FilamentMapMode get_real_filament_map_mode(const DynamicConfig& g_config,bool * use_global_param = nullptr) const;
FilamentMapMode get_filament_map_mode() const; FilamentMapMode get_filament_map_mode() const;
@@ -262,6 +263,15 @@ public:
std::vector<int> get_filament_maps() const; std::vector<int> get_filament_maps() const;
void set_filament_maps(const std::vector<int>& f_maps); void set_filament_maps(const std::vector<int>& f_maps);
// per-filament nozzle-volume choice (NozzleVolumeType values, 0 based filament ids)
std::vector<int> get_filament_volume_maps() const;
void set_filament_volume_maps(const std::vector<int>& f_maps);
void clear_filament_volume_map();
// per-filament nozzle-group choice (0 based filament and nozzle ids)
std::vector<int> get_filament_nozzle_maps() const;
void set_filament_nozzle_maps(const std::vector<int>& f_maps);
void clear_filament_map(); void clear_filament_map();
void clear_filament_map_mode(); void clear_filament_map_mode();
+20 -4
View File
@@ -8818,7 +8818,11 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
if (preset_bundle->get_printer_extruder_count() > 1) { if (preset_bundle->get_printer_extruder_count() > 1) {
PartPlate* cur_plate = background_process.get_current_plate(); PartPlate* cur_plate = background_process.get_current_plate();
std::vector<int> f_maps = cur_plate->get_real_filament_maps(preset_bundle->project_config); std::vector<int> f_maps = cur_plate->get_real_filament_maps(preset_bundle->project_config);
invalidated = background_process.apply(this->model, preset_bundle->full_config(false, f_maps)); std::vector<int> f_volume_maps = cur_plate->get_filament_volume_maps();
if (f_volume_maps.empty()) {
f_volume_maps = preset_bundle->get_default_nozzle_volume_types_for_filaments(f_maps);
}
invalidated = background_process.apply(this->model, preset_bundle->full_config(false, f_maps, f_volume_maps));
background_process.fff_print()->set_extruder_filament_info(get_extruder_filament_info()); background_process.fff_print()->set_extruder_filament_info(get_extruder_filament_info());
} }
else else
@@ -18326,7 +18330,11 @@ void Plater::apply_background_progress()
Print::ApplyStatus invalidated; Print::ApplyStatus invalidated;
if (preset_bundle->get_printer_extruder_count() > 1) { if (preset_bundle->get_printer_extruder_count() > 1) {
std::vector<int> f_maps = part_plate->get_real_filament_maps(preset_bundle->project_config); std::vector<int> f_maps = part_plate->get_real_filament_maps(preset_bundle->project_config);
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false, f_maps)); std::vector<int> f_volume_maps = part_plate->get_filament_volume_maps();
if (f_volume_maps.empty()) {
f_volume_maps = preset_bundle->get_default_nozzle_volume_types_for_filaments(f_maps);
}
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false, f_maps, f_volume_maps));
} }
else else
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false)); invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false));
@@ -18371,7 +18379,11 @@ int Plater::select_plate(int plate_index, bool need_slice)
//always apply the current plate's print //always apply the current plate's print
if (preset_bundle->get_printer_extruder_count() > 1) { if (preset_bundle->get_printer_extruder_count() > 1) {
std::vector<int> f_maps = part_plate->get_real_filament_maps(preset_bundle->project_config); std::vector<int> f_maps = part_plate->get_real_filament_maps(preset_bundle->project_config);
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false, f_maps)); std::vector<int> f_volume_maps = part_plate->get_filament_volume_maps();
if (f_volume_maps.empty()) {
f_volume_maps = preset_bundle->get_default_nozzle_volume_types_for_filaments(f_maps);
}
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false, f_maps, f_volume_maps));
} }
else else
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false)); invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false));
@@ -18792,7 +18804,11 @@ int Plater::select_plate_by_hover_id(int hover_id, bool right_click, bool isModi
//always apply the current plate's print //always apply the current plate's print
if (preset_bundle->get_printer_extruder_count() > 1) { if (preset_bundle->get_printer_extruder_count() > 1) {
std::vector<int> f_maps = part_plate->get_real_filament_maps(preset_bundle->project_config); std::vector<int> f_maps = part_plate->get_real_filament_maps(preset_bundle->project_config);
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false, f_maps)); std::vector<int> f_volume_maps = part_plate->get_filament_volume_maps();
if (f_volume_maps.empty()) {
f_volume_maps = preset_bundle->get_default_nozzle_volume_types_for_filaments(f_maps);
}
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false, f_maps, f_volume_maps));
} }
else else
invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false)); invalidated = p->background_process.apply(this->model(), preset_bundle->full_config(false));
@@ -288,14 +288,15 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2})); REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
} }
SECTION("a single-filament map matches the registered default's shape and is ignored") { SECTION("a single-filament explicit assignment on a Hybrid extruder is honored") {
DynamicPrintConfig config = make_hybrid_printer_config(); DynamicPrintConfig config = make_hybrid_printer_config();
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1}; config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1};
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow"}; config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow"};
config.option<ConfigOptionFloats>("filament_max_volumetric_speed", true)->values = {12., 20.}; config.option<ConfigOptionFloats>("filament_max_volumetric_speed", true)->values = {12., 20.};
config.option<ConfigOptionInts>("filament_map", true)->values = {2}; config.option<ConfigOptionInts>("filament_map", true)->values = {2};
// sized to the (single) filament count, but indistinguishable from the registered // sized to the (single) filament count: the producers guarantee sizing, so a
// 1-element default, so it must not override slot resolution // single-filament map is as trustworthy as any other and the explicit High Flow
// request must win over the Hybrid->Standard fallback
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {nvtHighFlow}; config.option<ConfigOptionInts>("filament_volume_map", true)->values = {nvtHighFlow};
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types; std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
@@ -305,8 +306,7 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe
config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys, config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys,
"filament_self_index", "filament_extruder_variant"); "filament_self_index", "filament_extruder_variant");
// Hybrid resolves as Standard, exactly as if no map were present REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({20.}));
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12.}));
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1})); REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1}));
} }
@@ -1,5 +1,6 @@
#include <catch2/catch_all.hpp> #include <catch2/catch_all.hpp>
#include "libslic3r/FilamentGroupUtils.hpp"
#include "libslic3r/MultiNozzleUtils.hpp" #include "libslic3r/MultiNozzleUtils.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/GCode/ToolOrdering.hpp" #include "libslic3r/GCode/ToolOrdering.hpp"
@@ -348,3 +349,45 @@ TEST_CASE("NozzleStatusRecorder tracks nozzle/extruder occupancy", "[MultiNozzle
// Clearing a nozzle leaves the extruder->nozzle association intact. // Clearing a nozzle leaves the extruder->nozzle association intact.
REQUIRE(rec.get_nozzle_in_extruder(1) == 2); REQUIRE(rec.get_nozzle_in_extruder(1) == 2);
} }
TEST_CASE("Hybrid nozzle stats resolve to concrete volume types", "[ToolOrdering][H2C]")
{
// Extruder 0 is Standard-only; extruder 1 carries a mixed Standard + High Flow inventory
// (the "Hybrid" flow selection). The write-back pipeline persists get_volume_map(), so the
// result must always carry concrete per-filament volume types, never the Hybrid seed.
auto stats = get_extruder_nozzle_stats({"Standard#1", "Standard#1|High Flow#1"});
REQUIRE(stats.size() == 2);
REQUIRE(stats[1].size() == 2);
std::vector<unsigned int> used_filaments = {0, 1, 2};
std::vector<int> filament_map = {0, 1, 1}; // 0-based extruder ids
std::vector<int> volume_requests = {(int) nvtStandard, (int) nvtHighFlow, (int) nvtStandard};
std::vector<int> nozzle_requests = {0, 1, 2}; // distinct logical nozzles
auto group = LayeredNozzleGroupResult::create(used_filaments, filament_map, volume_requests, nozzle_requests, stats, 0.4f);
REQUIRE(group.has_value());
auto volume_map = group->get_volume_map();
REQUIRE(volume_map == volume_requests);
for (auto fid : used_filaments)
REQUIRE(volume_map[fid] != (int) nvtHybrid);
// The Hybrid seed itself matches no physical nozzle: such a request is unsatisfiable.
std::vector<int> hybrid_requests = {(int) nvtStandard, (int) nvtHybrid, (int) nvtStandard};
REQUIRE_FALSE(LayeredNozzleGroupResult::create(used_filaments, filament_map, hybrid_requests, nozzle_requests, stats, 0.4f).has_value());
}
TEST_CASE("update_used_filament_values merges only used filaments", "[ToolOrdering][H2C]")
{
// The config write-back merges the engine's per-filament values over the config baseline:
// used filaments adopt the engine value, unused filaments keep their config assignment.
std::vector<int> old_values = {1, 1, 2, 1};
std::vector<int> new_values = {2, 2, 1, 2};
std::vector<unsigned int> used = {0, 2};
auto merged = FilamentGroupUtils::update_used_filament_values(old_values, new_values, used);
REQUIRE(merged == std::vector<int>{2, 1, 1, 1});
// No used filaments => the config baseline is returned untouched.
REQUIRE(FilamentGroupUtils::update_used_filament_values(old_values, new_values, {}) == old_values);
}