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

View File

@@ -319,6 +319,18 @@ std::vector<int> PartPlate::get_real_filament_maps(const DynamicConfig& g_config
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
{
auto mode = get_filament_map_mode();
@@ -3506,8 +3518,16 @@ int PartPlate::load_gcode_from_file(const std::string& filename)
int ret = 0;
// process gcode
auto& preset_bundle = wxGetApp().preset_bundle;
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);
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,
@@ -3814,6 +3834,40 @@ void PartPlate::clear_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()
{
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);
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;
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;