mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-02 07:42:14 +00:00
Fix post-slice self-invalidation on custom multi-extruder printers
This commit is contained in:
@@ -43,18 +43,33 @@ TEST_CASE("apply_override fills nil entries from the 0-based default index", "[C
|
||||
REQUIRE(resolved.values == std::vector<double>({30., 42.}));
|
||||
}
|
||||
|
||||
SECTION("an index past the machine slots falls back to the first slot") {
|
||||
SECTION("an index past the machine slots keeps the slot's own value") {
|
||||
std::vector<int> slot_index{5, 0};
|
||||
ConfigOptionFloats resolved(machine);
|
||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||
}
|
||||
|
||||
SECTION("a negative index (unresolved slot) falls back to the first slot") {
|
||||
std::vector<int> slot_index{-1, 0};
|
||||
SECTION("a negative index (unresolved slot) keeps the slot's own value") {
|
||||
ConfigOptionFloatsNullable all_nil;
|
||||
all_nil.values = {ConfigOptionFloatsNullable::nil_value(), ConfigOptionFloatsNullable::nil_value(),
|
||||
ConfigOptionFloatsNullable::nil_value()};
|
||||
std::vector<int> slot_index{2, -1, 0};
|
||||
ConfigOptionFloats resolved(machine);
|
||||
REQUIRE(resolved.apply_override(&filament, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({10., 42.}));
|
||||
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({30., 20., 10.}));
|
||||
}
|
||||
|
||||
SECTION("all-nil overrides keyed by unresolved slots leave the machine values intact") {
|
||||
// The failed-lookup map a degenerate print_extruder_id used to produce; the negative
|
||||
// slots must not collapse the machine array to its first value.
|
||||
ConfigOptionFloats per_extruder({100., 70., 70., 70., 100.});
|
||||
ConfigOptionFloatsNullable all_nil;
|
||||
all_nil.values.assign(5, ConfigOptionFloatsNullable::nil_value());
|
||||
std::vector<int> slot_index{0, -1, -1, -1, 0};
|
||||
ConfigOptionFloats resolved(per_extruder);
|
||||
REQUIRE(!resolved.apply_override(&all_nil, slot_index));
|
||||
REQUIRE(resolved.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +253,102 @@ TEST_CASE("update_values_to_printer_extruders expands one slot per (extruder x v
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders synthesizes degenerate process variant columns", "[Config]")
|
||||
{
|
||||
// Non-BBL process presets and 3mf project configs keep the length-1 defaults for
|
||||
// print_extruder_id/print_extruder_variant; only BBL system presets ship full-width columns.
|
||||
auto add_degenerate_print_columns = [](DynamicPrintConfig &config) {
|
||||
config.option<ConfigOptionInts>("print_extruder_id", true)->values = {1};
|
||||
config.option<ConfigOptionStrings>("print_extruder_variant", true)->values = {"Direct Drive Standard"};
|
||||
config.option<ConfigOptionFloats>("outer_wall_speed", true)->values = {30.};
|
||||
};
|
||||
|
||||
SECTION("a single-column pair on a multi-extruder machine expands to one column per extruder") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(variant_index == std::vector<int>({0, 1}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard"}));
|
||||
// width-1 data arrays replicate their only column into every slot
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30.}));
|
||||
}
|
||||
|
||||
SECTION("a multi-variant list synthesizes one column per (extruder x variant)") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
REQUIRE(count == 3);
|
||||
|
||||
std::vector<int> variant_index = config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
// same slot resolution as the explicit BBL-style 4-column layout
|
||||
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1, 2, 2}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 30., 30.}));
|
||||
}
|
||||
|
||||
SECTION("a single-extruder single-column layout is not treated as degenerate") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 1;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values == std::vector<int>({1}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30.}));
|
||||
}
|
||||
|
||||
SECTION("a second expansion leaves the synthesized layout unchanged") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtStandard};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard", "Direct Drive Standard"};
|
||||
add_degenerate_print_columns(config);
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
DynamicPrintConfig once = config;
|
||||
config.update_values_to_printer_extruders(config, extruder_count, count, nozzle_volume_types,
|
||||
print_options_with_variant, "print_extruder_id", "print_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionInts>("print_extruder_id")->values ==
|
||||
once.option<ConfigOptionInts>("print_extruder_id")->values);
|
||||
REQUIRE(config.option<ConfigOptionStrings>("print_extruder_variant")->values ==
|
||||
once.option<ConfigOptionStrings>("print_extruder_variant")->values);
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values ==
|
||||
once.option<ConfigOptionFloats>("outer_wall_speed")->values);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]")
|
||||
{
|
||||
auto make_filament_arrays = [](DynamicPrintConfig &config) {
|
||||
|
||||
@@ -500,6 +500,52 @@ TEST_CASE("Re-applying an unchanged config after slicing keeps the result valid"
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
}
|
||||
|
||||
TEST_CASE("A degenerate process variant map on a custom multi-extruder printer slices to a stable result", "[Print][Regression]")
|
||||
{
|
||||
// Non-BBL multi-extruder printers get machine-scope variant columns synthesized on preset
|
||||
// load (extend_extruder_variant), but nothing ships process-scope print_extruder_id /
|
||||
// print_extruder_variant: presets and 3mf project configs carry the length-1 defaults. The
|
||||
// apply-time expansion must synthesize the process columns from extruder_variant_list;
|
||||
// otherwise the failed per-extruder lookups collapse the per-extruder retract overrides
|
||||
// during slicing and the post-slice re-apply invalidates every fresh result, forever.
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.option<ConfigOptionFloats>("nozzle_diameter", true)->values = {0.4, 0.4, 0.4, 0.4, 0.4};
|
||||
config.set_num_extruders(5);
|
||||
// per-extruder machine values that a first-slot collapse would destroy
|
||||
config.option<ConfigOptionPercents>("retract_before_wipe", true)->values = {100., 70., 70., 70., 100.};
|
||||
config.option<ConfigOptionEnumsGeneric>("z_hop_types", true)->values = {zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope};
|
||||
// filament presets carry the nullable override twins (all-nil = "no override"); they are what
|
||||
// routes the machine values through apply_override in the in-slice override recompute
|
||||
config.option<ConfigOptionPercentsNullable>("filament_retract_before_wipe", true)->values =
|
||||
std::vector<double>(5, ConfigOptionPercentsNullable::nil_value());
|
||||
config.option<ConfigOptionEnumsGenericNullable>("filament_z_hop_types", true)->values =
|
||||
std::vector<int>(5, ConfigOptionEnumsGenericNullable::nil_value());
|
||||
config.option<ConfigOptionFloats>("filament_diameter", true)->values = std::vector<double>(5, 1.75);
|
||||
config.option<ConfigOptionStrings>("filament_colour", true)->values = {"#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#00FFFF"};
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2, 3, 4, 1};
|
||||
|
||||
Model model;
|
||||
model.add_object("cube", "", make_cube(20, 20, 20))->add_instance()->set_offset(Vec3d(100., 100., 0.));
|
||||
|
||||
Print print;
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
|
||||
// BackgroundSlicingProcess reads the engine-computed maps back into the plate config after
|
||||
// slicing; the next apply overlays that written-back state.
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = print.get_filament_maps();
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = print.get_filament_volume_maps();
|
||||
config.option<ConfigOptionInts>("filament_nozzle_map", true)->values = print.get_filament_nozzle_maps();
|
||||
|
||||
auto status = print.apply(model, config);
|
||||
REQUIRE(status == PrintBase::APPLY_STATUS_UNCHANGED);
|
||||
REQUIRE(print.is_step_done(psSlicingFinished));
|
||||
// the per-extruder machine values must survive the in-slice override recompute
|
||||
REQUIRE(print.config().retract_before_wipe.values == std::vector<double>({100., 70., 70., 70., 100.}));
|
||||
REQUIRE(print.config().z_hop_types.values == std::vector<int>({zhtSlope, zhtNormal, zhtNormal, zhtNormal, zhtSlope}));
|
||||
}
|
||||
|
||||
TEST_CASE("normalize_nozzle_map_per_layer makes per-filament assignments gap-free", "[MultiNozzle][H2C][Dynamic]")
|
||||
{
|
||||
SECTION("gaps inherit the last used nozzle, entries on used layers stay untouched") {
|
||||
|
||||
Reference in New Issue
Block a user