fix(engine): keep engine-derived filament_map_2 out of the apply diff

Print::apply rebuilds m_config.filament_map_2 to the real per-filament slot
map on every apply, while the incoming full config only ever carries the
ConfigDef default. The resulting phantom one-key print_diff hit the
invalidator's catch-all branch and killed every print-level step on each
apply, so on multi-extruder printers a fresh slice result was invalidated
the moment the GUI re-applied after slicing completed.

Dropping the key from print_diff loses no information: it is never a user
input, and the rebuild derives it from filament_map, filament_volume_map
and the variant slots, each of which is diffed and invalidation-listed on
its own.

Regression test: re-applying an unchanged config after process() must not
invalidate psSlicingFinished (fails with APPLY_STATUS_INVALIDATED without
the fix). Suites green (libslic3r 48891/154, fff_print 631/59); 19-fixture
byte gate identical incl. the Hybrid repro project, determinism x2.
This commit is contained in:
SoftFever
2026-07-11 22:11:35 +08:00
parent 75c8d0775a
commit dacf26b06f
2 changed files with 43 additions and 0 deletions

View File

@@ -1205,6 +1205,13 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
DynamicPrintConfig filament_overrides;
//BBS: add plate index
t_config_option_keys print_diff = print_config_diffs(m_config, new_full_config, filament_overrides, this->m_plate_index, filament_maps);
// Orca: filament_map_2 is engine-derived state, never a user input: the rebuild below
// recomputes it from filament_map/filament_volume_map/the variant slots on every apply
// (all of which are diffed and invalidation-listed on their own), and the grouping
// write-back overwrites it during process(). The incoming full config only ever carries
// the ConfigDef default, so diffing it would invalidate every print step on each apply
// for any multi-extruder printer and permanently invalidate fresh slice results.
print_diff.erase(std::remove(print_diff.begin(), print_diff.end(), "filament_map_2"), print_diff.end());
t_config_option_keys full_config_diff = full_print_config_diffs(m_full_print_config, new_full_config, this->m_plate_index);
// Collect changes to object and region configs.
t_config_option_keys object_diff = m_default_object_config.diff(new_full_config);

View File

@@ -460,3 +460,39 @@ TEST_CASE("Print config-index resolvers pick per-filament Hybrid slots", "[Print
REQUIRE(print.get_nozzle_config_index(2, 0) == 1); // extruder slot, not the High Flow slot
}
}
TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid", "[Print][H2C]")
{
// apply() rebuilds m_config.filament_map_2 to the real per-filament slot map, while the
// incoming full config only ever carries the ConfigDef default for it. The engine-derived
// key must therefore be kept out of the apply diff: the GUI re-applies right after slicing
// completes, and a phantom filament_map_2 diff would invalidate every freshly sliced result
// on any multi-extruder printer.
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4};
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#1|High Flow#2"};
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHybrid};
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
"Direct Drive Standard,Direct Drive High Flow"};
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1, 1, 2, 2};
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
"Direct Drive Standard", "Direct Drive High Flow"};
config.option<ConfigOptionFloats>("filament_diameter", true)->values = {1.75, 1.75, 1.75};
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF"};
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 2};
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {(int) nvtStandard, (int) nvtStandard, (int) nvtHighFlow};
Model model;
ModelObject *object = model.add_object("cube", "", make_cube(20, 20, 20));
object->add_instance()->set_offset(Vec3d(100., 100., 0.));
Print print;
print.apply(model, config);
print.process();
REQUIRE(print.is_step_done(psSlicingFinished));
auto status = print.apply(model, config);
REQUIRE(status != PrintBase::APPLY_STATUS_INVALIDATED);
REQUIRE(print.is_step_done(psSlicingFinished));
}