mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-07-25 11:52:05 +00:00
feat(config): keep per-(extruder x volume-type) slots in variant expansion
- get_extruder_nozzle_volume_count derives per-extruder volume-type slot lists from extruder_nozzle_stats (absent stats = one slot per extruder) - update_values_to_printer_extruders learns the slot layout: when any extruder mixes volume types, option arrays keep one slot per (extruder x volume type), extruder-ascending then volume-ascending; single-slot resolution takes the filament's volume type on mixed extruders - update_values_to_printer_extruders_for_multiple_filaments applies a per-filament nozzle_volume_type override from filament_volume_map (when sized to the filament count) and remaps filament_self_index through the same pipeline as every other filament key - get_config_index_base + is_auto_filament_map_mode helpers (consumers land with the per-filament config-index resolvers) - callers updated: PresetBundle composition paths, PrintApply (counts hoisted above the extruder_applied guard), Print write-back - new tests: slot counting, Hybrid slot expansion incl. stride 2, per-filament override, non-Hybrid degeneracy Non-Hybrid printers keep their variant layout and values (proven by a 19-fixture byte gate; the only header delta is filament_self_index now flowing through the same variant pipeline as its sibling filament keys). Hybrid slices grow the config-block variant arrays to one entry per sub-nozzle volume type; motion g-code is unchanged until the g-code writer consumes the new slots.
This commit is contained in:
@@ -12,6 +12,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_clipper_offset.cpp
|
||||
test_clipper_utils.cpp
|
||||
test_config.cpp
|
||||
test_config_variant_expansion.cpp
|
||||
test_toolordering_nozzle_group.cpp
|
||||
test_preset_bundle_loading.cpp
|
||||
test_preset_setting_id.cpp
|
||||
|
||||
285
tests/libslic3r/test_config_variant_expansion.cpp
Normal file
285
tests/libslic3r/test_config_variant_expansion.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
|
||||
namespace {
|
||||
|
||||
// A 2-extruder printer whose second extruder holds both a Standard and a High Flow nozzle
|
||||
// (nozzle_volume_type Hybrid), described by extruder_nozzle_stats. The variant lists carry one
|
||||
// column per (extruder x volume type) as composed from the presets.
|
||||
DynamicPrintConfig make_hybrid_printer_config()
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "Standard#3|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"};
|
||||
return config;
|
||||
}
|
||||
|
||||
void add_print_variant_columns(DynamicPrintConfig &config)
|
||||
{
|
||||
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>("outer_wall_speed", true)->values = {30., 200., 50., 500.};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]")
|
||||
{
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
|
||||
SECTION("absent stats fall back to one slot per extruder") {
|
||||
DynamicPrintConfig config;
|
||||
REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 2);
|
||||
REQUIRE(nozzle_volume_types.size() == 2);
|
||||
REQUIRE(nozzle_volume_types[0].empty());
|
||||
REQUIRE(nozzle_volume_types[1].empty());
|
||||
}
|
||||
|
||||
SECTION("stats sized differently from the extruder count are ignored") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1"};
|
||||
REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 2);
|
||||
REQUIRE(nozzle_volume_types[0].empty());
|
||||
REQUIRE(nozzle_volume_types[1].empty());
|
||||
}
|
||||
|
||||
SECTION("single volume type per extruder counts one slot each") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#1", "High Flow#1"};
|
||||
REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 2);
|
||||
REQUIRE(nozzle_volume_types[0] == std::vector<NozzleVolumeType>{nvtStandard});
|
||||
REQUIRE(nozzle_volume_types[1] == std::vector<NozzleVolumeType>{nvtHighFlow});
|
||||
}
|
||||
|
||||
SECTION("a mixed-nozzle extruder contributes one slot per volume type, ascending enum order") {
|
||||
DynamicPrintConfig config;
|
||||
// list High Flow first in the token string: parsing must still order Standard before High Flow
|
||||
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"Standard#3", "High Flow#3|Standard#3"};
|
||||
REQUIRE(config.get_extruder_nozzle_volume_count(2, nozzle_volume_types) == 3);
|
||||
REQUIRE(nozzle_volume_types[0] == std::vector<NozzleVolumeType>{nvtStandard});
|
||||
REQUIRE(nozzle_volume_types[1] == std::vector<NozzleVolumeType>({nvtStandard, nvtHighFlow}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders expands one slot per (extruder x volume type)", "[Config]")
|
||||
{
|
||||
SECTION("Hybrid extruder yields three slots, extruder-ascending then volume-ascending") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
add_print_variant_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");
|
||||
|
||||
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 50., 500.}));
|
||||
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"}));
|
||||
}
|
||||
|
||||
SECTION("stride-2 options keep (normal, silent) pairs together per slot") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
config.option<ConfigOptionInts>("printer_extruder_id", true)->values = {1, 1, 2, 2};
|
||||
config.option<ConfigOptionStrings>("printer_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||
config.option<ConfigOptionFloats>("machine_max_speed_x", true)->values = {100., 50., 110., 55., 120., 60., 130., 65.};
|
||||
|
||||
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,
|
||||
printer_options_with_variant_2, "printer_extruder_id", "printer_extruder_variant", 2);
|
||||
|
||||
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("machine_max_speed_x")->values ==
|
||||
std::vector<double>({100., 50., 120., 60., 130., 65.}));
|
||||
}
|
||||
|
||||
SECTION("single-slot expansion on a Hybrid extruder resolves via the filament volume type") {
|
||||
DynamicPrintConfig printer_config = make_hybrid_printer_config();
|
||||
|
||||
DynamicPrintConfig filament_config;
|
||||
filament_config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow"};
|
||||
filament_config.option<ConfigOptionFloats>("filament_max_volumetric_speed", true)->values = {12., 20.};
|
||||
|
||||
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
|
||||
int extruder_count = 2;
|
||||
int count = printer_config.get_extruder_nozzle_volume_count(extruder_count, nozzle_volume_types);
|
||||
|
||||
SECTION("default filament volume type selects the Standard column") {
|
||||
std::vector<int> variant_index = filament_config.update_values_to_printer_extruders(printer_config, extruder_count, count,
|
||||
nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, 2);
|
||||
REQUIRE(variant_index == std::vector<int>({0}));
|
||||
REQUIRE(filament_config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12.}));
|
||||
}
|
||||
|
||||
SECTION("a High Flow filament volume type selects the High Flow column") {
|
||||
std::vector<int> variant_index = filament_config.update_values_to_printer_extruders(printer_config, extruder_count, count,
|
||||
nozzle_volume_types, filament_options_with_variant, "", "filament_extruder_variant", 1, 2, nvtHighFlow);
|
||||
REQUIRE(variant_index == std::vector<int>({1}));
|
||||
REQUIRE(filament_config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({20.}));
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("an extruder without per-type stats does not overrun the slot table when another is Hybrid") {
|
||||
DynamicPrintConfig config;
|
||||
// e0 carries no per-type stats (empty entry), so the summed volume-type count (2) does
|
||||
// not exceed the extruder count even though the Hybrid e1 emits one slot per volume type.
|
||||
config.option<ConfigOptionStrings>("extruder_nozzle_stats", true)->values = {"", "Standard#3|High Flow#3"};
|
||||
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"};
|
||||
add_print_variant_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 == 2);
|
||||
REQUIRE(nozzle_volume_types[0].empty());
|
||||
|
||||
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");
|
||||
|
||||
// e0 resolves by its configured type; the Hybrid e1 emits one slot per stats volume type
|
||||
REQUIRE(variant_index == std::vector<int>({0, 2, 3}));
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 50., 500.}));
|
||||
}
|
||||
|
||||
SECTION("without Hybrid or extra slots the expansion matches the per-extruder resolution") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHighFlow};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
|
||||
"Direct Drive Standard,Direct Drive High Flow"};
|
||||
add_print_variant_columns(config);
|
||||
|
||||
// compute what the per-extruder loop resolves directly, before the arrays are rewritten
|
||||
std::vector<int> expected_index;
|
||||
for (int e_index = 0; e_index < 2; e_index++)
|
||||
expected_index.push_back(config.get_index_for_extruder(e_index + 1, "print_extruder_id", etDirectDrive,
|
||||
e_index == 0 ? nvtStandard : nvtHighFlow, "print_extruder_variant"));
|
||||
REQUIRE(expected_index == std::vector<int>({0, 3}));
|
||||
|
||||
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 == 2);
|
||||
|
||||
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 == expected_index);
|
||||
REQUIRE(config.option<ConfigOptionFloats>("outer_wall_speed")->values == std::vector<double>({30., 500.}));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves per-filament slots", "[Config]")
|
||||
{
|
||||
auto make_filament_arrays = [](DynamicPrintConfig &config) {
|
||||
config.option<ConfigOptionInts>("filament_self_index", true)->values = {1, 1, 2, 2};
|
||||
config.option<ConfigOptionStrings>("filament_extruder_variant", true)->values = {"Direct Drive Standard", "Direct Drive High Flow",
|
||||
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||
config.option<ConfigOptionFloats>("filament_max_volumetric_speed", true)->values = {12., 20., 13., 21.};
|
||||
};
|
||||
|
||||
std::set<std::string> filament_keys = filament_options_with_variant;
|
||||
filament_keys.insert("filament_self_index");
|
||||
|
||||
SECTION("filament_volume_map picks the concrete volume type on a Hybrid extruder") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
make_filament_arrays(config);
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {2, 2};
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {nvtStandard, nvtHighFlow};
|
||||
|
||||
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_for_multiple_filaments(config, extruder_count, count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant");
|
||||
|
||||
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 21.}));
|
||||
REQUIRE(config.option<ConfigOptionStrings>("filament_extruder_variant")->values ==
|
||||
std::vector<std::string>({"Direct Drive Standard", "Direct Drive High Flow"}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
|
||||
}
|
||||
|
||||
SECTION("a volume map not sized to the filament count is ignored") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
make_filament_arrays(config);
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {2, 2};
|
||||
// the registered default is a single-element vector; it must not override slot resolution
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {nvtStandard};
|
||||
|
||||
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_for_multiple_filaments(config, extruder_count, count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant");
|
||||
|
||||
// Hybrid resolves as Standard when no usable per-filament map exists
|
||||
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 13.}));
|
||||
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") {
|
||||
DynamicPrintConfig config = make_hybrid_printer_config();
|
||||
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<ConfigOptionFloats>("filament_max_volumetric_speed", true)->values = {12., 20.};
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {2};
|
||||
// sized to the (single) filament count, but indistinguishable from the registered
|
||||
// 1-element default, so it must not override slot resolution
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {nvtHighFlow};
|
||||
|
||||
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_for_multiple_filaments(config, extruder_count, count, filament_keys,
|
||||
"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>({12.}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1}));
|
||||
}
|
||||
|
||||
SECTION("without Hybrid or extra slots the volume map is not consulted") {
|
||||
DynamicPrintConfig config;
|
||||
config.option<ConfigOptionEnumsGeneric>("extruder_type", true)->values = {etDirectDrive, etDirectDrive};
|
||||
config.option<ConfigOptionEnumsGeneric>("nozzle_volume_type", true)->values = {nvtStandard, nvtHighFlow};
|
||||
config.option<ConfigOptionStrings>("extruder_variant_list", true)->values = {"Direct Drive Standard,Direct Drive High Flow",
|
||||
"Direct Drive Standard,Direct Drive High Flow"};
|
||||
make_filament_arrays(config);
|
||||
config.option<ConfigOptionInts>("filament_map", true)->values = {1, 2};
|
||||
// sized to the filament count, but inert because no extruder exposes multiple volume types
|
||||
config.option<ConfigOptionInts>("filament_volume_map", true)->values = {nvtHighFlow, nvtStandard};
|
||||
|
||||
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 == 2);
|
||||
|
||||
config.update_values_to_printer_extruders_for_multiple_filaments(config, extruder_count, count, filament_keys,
|
||||
"filament_self_index", "filament_extruder_variant");
|
||||
|
||||
// filament 1 keeps its extruder's Standard column, filament 2 its extruder's High Flow column
|
||||
REQUIRE(config.option<ConfigOptionFloats>("filament_max_volumetric_speed")->values == std::vector<double>({12., 21.}));
|
||||
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user