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
+4 -2
View File
@@ -4512,8 +4512,10 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
if (m_curr_plater){
auto filament_volume_map = get_vector_from_string(value);
for (size_t idx = 0; idx < filament_volume_map.size(); ++idx) {
// Only Standard(0)/High Flow(1) are currently supported; clamp any higher
// volume-type (TPU High Flow/Hybrid) back to Standard until they are wired in.
// The map feeds per-filament slot resolution and grouping. Clamp any
// 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) {
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;
// hybrid flow means no special per-filament nozzle-volume request.
// Orca: in manual mode the config's per-filament volume map is honoured only when a producer
// sized it to a multi-filament count (the trust guard used by
// update_values_to_printer_extruders_for_multiple_filaments): the registered default is a
// 1-element vector ({Standard}) that a single-filament count cannot tell apart from a real
// map, and it must not displace the hybrid fallback rebuild_nozzle_unprintables relies on.
if (mode == FilamentMapMode::fmmManual && filament_nums > 1 &&
// Orca: honour the config's per-filament volume map only when it is sized to the filament
// count. The full-config producers (PresetBundle injection, engine write-back) always size
// it; a mis-sized map (stale project value, CLI runs until the per-filament synthesis lands
// there) must not displace the hybrid fallback rebuild_nozzle_unprintables relies on, nor be
// indexed out of bounds.
if (mode == FilamentMapMode::fmmManual &&
print_config.filament_volume_map.values.size() == filament_nums)
context.group_info.filament_volume_map = print_config.filament_volume_map.values;
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; });
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
// pass them only when a producer sized them to a multi-filament count — the registered
// 1-element defaults are indistinguishable by size from a real single-filament map.
// pass them only when a producer sized them to the filament count (mis-sized maps can
// 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,
// the same failure an unsatisfiable create() yields.
std::optional<LayeredNozzleGroupResult> nozzle_result;
if (filament_nums > 1 && print_config.filament_volume_map.values.size() == filament_nums &&
if (print_config.filament_volume_map.values.size() == filament_nums &&
print_config.filament_nozzle_map.values.size() == filament_nums)
nozzle_result = LayeredNozzleGroupResult::create(used_filaments, manual_filament_map, print_config.filament_volume_map.values, print_config.filament_nozzle_map.values, get_extruder_nozzle_stats(print_config.extruder_nozzle_stats.values), diameter);
if (!nozzle_result)
@@ -1556,7 +1556,21 @@ MultiNozzleUtils::LayeredNozzleGroupResult ToolOrdering::get_recommended_filamen
if (has_multiple_nozzle) {
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 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; });
// Orca: same trust guard as the manual grouping paths — create() indexes the volume/nozzle
// maps per used filament with no bounds check, and the registered 1-element defaults must not
// be mistaken for a real single-filament map. Unsized maps fall through to the extruder-level
// wrap below.
if (has_multiple_nozzle && filament_nums > 1 &&
// Orca: same sizing guard as the manual grouping paths — create() indexes the volume/nozzle
// maps per used filament with no bounds check, so only maps sized to the filament count are
// trusted (mis-sized maps can arrive from stale projects or CLI runs until the per-filament
// synthesis lands there). Unsized maps fall through to the extruder-level wrap below.
if (has_multiple_nozzle &&
print_config.filament_volume_map.values.size() == filament_nums &&
print_config.filament_nozzle_map.values.size() == filament_nums) {
float diameter = print_config.nozzle_diameter.values.empty() ? 0.4f : static_cast<float>(print_config.nozzle_diameter.values.front());
@@ -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);
grouping_result = result ? *result : MultiNozzleUtils::LayeredNozzleGroupResult();
// Derive the extruder-level map (0-based) for the stats path + config write-back. The full
// per-nozzle config write-back is not yet wired.
// 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.
std::vector<int> derived_maps = grouping_result.get_extruder_map(false); // 1-based
if (!derived_maps.empty()) {
filament_maps = derived_maps;
@@ -2060,11 +2076,34 @@ void ToolOrdering::reorder_extruders_for_minimum_flush_volume(bool reorder_first
if (derived_maps.empty())
return;
filament_maps = derived_maps;
m_print->update_filament_maps_to_config(filament_maps);
} else if (!derived_maps.empty()) {
// Manual modes: the result mirrors the user's config map; adopt it for consistency.
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; });
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",
"filament_map_mode",
"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
// 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.
@@ -79,7 +82,8 @@ DynamicPrintConfig PresetBundle::construct_full_config(
const DynamicPrintConfig& project_config,
std::vector<Preset>& in_filament_presets,
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 &print_config = in_print_preset.config;
@@ -94,12 +98,23 @@ DynamicPrintConfig PresetBundle::construct_full_config(
size_t num_filaments = in_filament_presets.size();
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())
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
if (filament_maps.size() != num_filaments) {
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"));
// 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
DynamicPrintConfig filament_config = in_filament_presets[0].config;
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);
compatible_printers_condition.emplace_back(in_filament_presets[0].compatible_printers_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) {
filament_temp_configs[i] = *(filament_configs[i]);
if (apply_extruder && ((extruder_count > 1) || different_extruder))
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]);
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.
@@ -246,6 +261,7 @@ DynamicPrintConfig PresetBundle::construct_full_config(
out.option<ConfigOptionString>("printer_settings_id", true)->value = in_printer_preset.name;
out.option<ConfigOptionStrings>("filament_ids", true)->values = filament_ids;
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) {
bool nonempty = false;
@@ -2701,6 +2717,9 @@ void PresetBundle::update_selections(AppConfig &config)
std::vector<int> filament_maps(filament_colors.size(), 1);
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;
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(","));
@@ -2845,6 +2864,9 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
std::vector<int> filament_maps(filament_colors.size(), 1);
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;
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(","));
@@ -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_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n);
// 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_map->values.resize(n, 1);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n);
// 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_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts* filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts* filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
filament_color->resize(n);
// 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_map->values.resize(n, 1);
filament_volume_map->values.resize(n, static_cast<int>(NozzleVolumeType::nvtStandard));
ams_multi_color_filment.resize(n);
//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_color_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
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) {
filament_color->values.erase(filament_color->values.begin() + 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);
}
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 {
filament_color->values.resize(to_del_flament_id);
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
@@ -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_type = project_config.option<ConfigOptionStrings>("filament_colour_type");
ConfigOptionInts * filament_map = project_config.option<ConfigOptionInts>("filament_map");
ConfigOptionInts * filament_volume_map = project_config.option<ConfigOptionInts>("filament_volume_map");
if (color_only) {
auto get_map_index = [&ams_infos](const std::vector<AMSMapInfo> &infos, const AMSMapInfo &temp) {
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;
this->filament_presets = exist_filament_presets;
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;
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;
ams_multi_color_filment = result_multi_colors;
filament_map->values.resize(total, 1);
filament_volume_map->values.resize(total, static_cast<int>(NozzleVolumeType::nvtStandard));
} else {
// BBL: existing wholesale replace
filament_color->values = ams_filament_colors;
filament_color_type->values = ams_filament_color_types;
this->filament_presets = ams_filament_presets;
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;
@@ -3882,10 +3915,26 @@ bool PresetBundle::support_different_extruders() const
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) ?
this->full_fff_config(apply_extruder, filament_maps) :
this->full_fff_config(apply_extruder, filament_maps, filament_volume_maps) :
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"
};
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;
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();
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())
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
if (filament_maps.size() != num_filaments) {
filament_maps.resize(num_filaments, 1);
@@ -3967,6 +4025,9 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
else {
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"));
// 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
DynamicPrintConfig filament_config = this->filaments.get_edited_preset().config;
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);
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());
@@ -4117,7 +4178,7 @@ DynamicPrintConfig PresetBundle::full_fff_config(bool apply_extruder, std::optio
for (size_t i = 0; i < num_filaments; ++i) {
filament_temp_configs[i] = *(filament_configs[i]);
if (apply_extruder && ((extruder_count > 1) || different_extruder))
filament_temp_configs[i].update_values_to_printer_extruders(out, extruder_count, extruder_volume_type_count, nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, filament_maps[i]);
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.
+8 -3
View File
@@ -175,7 +175,8 @@ public:
const DynamicPrintConfig &project_config,
std::vector<Preset> &in_filament_presets,
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
static std::string find_preset_vendor(const std::string& preset_name, Preset::Type type);
@@ -383,10 +384,14 @@ public:
bool has_defauls_only() const
{ 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.
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
// 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.
@@ -543,7 +548,7 @@ private:
/*ConfigSubstitutions load_config_file_config_bundle(
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;
// Orca: used for validation only
+61 -10
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 == "toolchange_ordering"
|| opt_key == "extruder_ams_count"
|| opt_key == "extruder_nozzle_stats"
|| opt_key == "filament_map_mode"
|| opt_key == "filament_map"
|| opt_key == "filament_nozzle_map"
|| opt_key == "filament_volume_map"
|| opt_key == "filament_adhesiveness_category"
|| opt_key == "filament_tower_interface_pre_extrusion_dist"
|| 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();
auto map_mode = get_filament_map_mode();
// 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) {
// Grouping returns a nozzle-aware result; the 1-based extruder map
// 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);
if (!derived_maps.empty()) {
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
@@ -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);
}
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.");
m_ori_full_print_config.option<ConfigOptionInts>("filament_map", true)->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;
bool support_multi = m_ori_full_print_config.support_different_extruders(extruder_count);
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_nozzle_volume_type = dynamic_cast<const ConfigOptionEnumsGeneric*>(m_ori_full_print_config.option("nozzle_volume_type"));
// Orca: the loop tolerates configs without the extruder options (unit tests, degenerate
// presets); the volume-map override keeps the sizing guard used everywhere else, and the
// grouping engine does not produce per-filament volume maps yet, so m_config only carries
// a map when the project supplied one.
// presets); the backfill and the override are bounds-checked because the change block above
// is skipped when the maps are unchanged, in which case the stored map may be shorter than
// 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++)
{
ExtruderType extruder_type = (ExtruderType)(opt_extruder_type->get_at(f_maps[index] - 1));
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(f_maps[index] - 1));
if ((extruder_volume_type_count > extruder_count)
&& f_maps.size() > 1 && m_config.filament_volume_map.values.size() == f_maps.size())
if (f_volume_maps.empty()) {
// 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]);
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);
}
t_config_option_keys keys(filament_options_with_variant.begin(), filament_options_with_variant.end());
m_config.apply_only(m_full_print_config, keys, true);
if ((extruder_count > 1) || support_multi) {
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);
+1 -1
View File
@@ -1005,7 +1005,7 @@ public:
const WipeTowerData& wipe_tower_data(size_t filaments_cnt = 0) const;
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);
// 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
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;
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);
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 {
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> 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;
}
}
if (same_map)
if (same_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())
@@ -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));
NozzleVolumeType nozzle_volume_type = (NozzleVolumeType)(opt_nozzle_volume_type->get_at(filament_maps[index] - 1));
// Orca: the per-filament volume map is only trustworthy when a producer explicitly sized it
// to the filament count; the registered default is a 1-element vector (see
// Orca: honour the per-filament volume map only when a producer sized it to the filament
// 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).
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]);
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);
// Handle changes to regions config defaults
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);
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
// plate metadata (filament_volume_maps) and steers per-filament slot resolution when an
// extruder exposes several volume types; inert for existing printers (no producer sizes it
// to the filament count until the write-back pipeline lands).
// extruder exposes several volume types. Producers: the GUI full-config composition injects
// 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->mode = comDevelop;
def->set_default_value(new ConfigOptionInts{(int)(NozzleVolumeType::nvtStandard)});
@@ -2774,8 +2776,10 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionEnum<FilamentMapMode>(fmmAutoForFlush));
// Fully-manual mode (fmmNozzleManual): per-filament -> physical-nozzle assignment.
// Inert until the nozzle-assignment engine consumes it. Internal use only, no translation.
// Per-filament -> physical-nozzle assignment. Written back by the grouping engine after
// 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->mode = comDevelop;
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"));
std::vector<int> filament_volume_maps;
// Orca: the per-filament volume map is only trustworthy when a producer explicitly
// sized it to the filament count; the registered default is a 1-element vector and
// stale project values may be mis-sized. A single-filament config cannot be told
// apart from that default by size, so it is ignored too until the pipeline that
// seeds the map lands; anything untrusted must not distort slot resolution.
if (opt_filament_volume_maps && filament_count > 1 && opt_filament_volume_maps->values.size() == filament_count)
// Orca: honour the per-filament volume map only when a producer sized it to the
// filament count. The full-config producers (PresetBundle injection, engine
// write-back) always size it; mis-sized maps (stale project values, CLI runs until
// the per-filament synthesis lands there) must not distort slot resolution nor be
// indexed out of bounds.
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> variant_index;