mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Merge branch 'main' into feat/printer-agent-impl
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
|
||||
#include "test_helpers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
@@ -25,3 +28,68 @@ TEST_CASE("Cooling consumes its internal speed markers", "[Cooling]")
|
||||
const std::string gcode = slice({ cube(20) }, { { "layer_height", 0.2 } });
|
||||
CHECK(gcode.find(";_EXTRUDE_SET_SPEED") == std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("Overhang fan transitions do not depend on overhang speed", "[Cooling][Regression]")
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "bridge_speed", 2.0 },
|
||||
{ "enable_arc_fitting", false },
|
||||
{ "enable_overhang_bridge_fan", true },
|
||||
{ "enable_overhang_speed", false },
|
||||
{ "initial_layer_print_height", 0.3 },
|
||||
{ "inner_wall_speed", 30.0 },
|
||||
{ "layer_height", 0.3 },
|
||||
{ "outer_wall_speed", 30.0 },
|
||||
{ "overhang_1_4_speed", "30" },
|
||||
{ "overhang_2_4_speed", "29" },
|
||||
{ "overhang_3_4_speed", "6" },
|
||||
{ "overhang_4_4_speed", "3" },
|
||||
{ "slow_down_for_layer_cooling", false },
|
||||
{ "slowdown_for_curled_perimeters", false },
|
||||
});
|
||||
config.set_key_value("fan_max_speed", new ConfigOptionFloats{20.0});
|
||||
config.set_key_value("fan_min_speed", new ConfigOptionFloats{20.0});
|
||||
config.set_key_value("overhang_fan_speed", new ConfigOptionInts{100});
|
||||
config.set_key_value("overhang_fan_threshold", new ConfigOptionEnumsGeneric{Overhang_threshold_2_4});
|
||||
config.set_key_value("layer_change_gcode", new ConfigOptionString{";TEST_LAYER_Z=[layer_z]"});
|
||||
|
||||
const auto fan_commands = [](const std::string &gcode) {
|
||||
std::vector<std::pair<std::string, std::string>> commands;
|
||||
std::istringstream input(gcode);
|
||||
std::string layer;
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
if (line.rfind(";TEST_LAYER_Z=", 0) == 0)
|
||||
layer = line;
|
||||
else if (!layer.empty() && (line.rfind("M106", 0) == 0 || line.rfind("M107", 0) == 0))
|
||||
commands.emplace_back(layer, line);
|
||||
}
|
||||
return commands;
|
||||
};
|
||||
const auto feedrates = [](const std::string &gcode) {
|
||||
std::vector<std::string> values;
|
||||
std::istringstream input(gcode);
|
||||
std::string word;
|
||||
while (input >> word)
|
||||
if (!word.empty() && word.front() == 'F')
|
||||
values.push_back(word);
|
||||
return values;
|
||||
};
|
||||
|
||||
constexpr double sphere_radius = 50.0; // 100 mm diameter.
|
||||
const std::string without_speed_gcode = slice({make_sphere(sphere_radius, PI / 24.0)}, config);
|
||||
config.set_deserialize_strict({{"enable_overhang_speed", true}});
|
||||
const std::string with_speed_gcode = slice({make_sphere(sphere_radius, PI / 24.0)}, config);
|
||||
|
||||
const auto without_speed_fan = fan_commands(without_speed_gcode);
|
||||
const auto with_speed_fan = fan_commands(with_speed_gcode);
|
||||
const auto without_speed_feedrates = feedrates(without_speed_gcode);
|
||||
const auto with_speed_feedrates = feedrates(with_speed_gcode);
|
||||
|
||||
REQUIRE_FALSE(without_speed_fan.empty());
|
||||
REQUIRE(std::any_of(without_speed_fan.begin(), without_speed_fan.end(),
|
||||
[](const auto &command) { return command.second.find("S255") != std::string::npos; }));
|
||||
REQUIRE(with_speed_feedrates != without_speed_feedrates);
|
||||
CHECK(with_speed_fan == without_speed_fan);
|
||||
}
|
||||
|
||||
@@ -828,3 +828,266 @@ SCENARIO("ConfigOptionVector::set_to_index throws on incompatible type", "[Confi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli applies valid values and collects non-option arguments", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--nozzle-temperature", "210,190", "--reduce-crossing-wall=1", "model.3mf"};
|
||||
REQUIRE(config.read_cli(5, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{210, 190});
|
||||
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value);
|
||||
REQUIRE(extra == t_config_option_keys{"model.3mf"});
|
||||
REQUIRE(keys == t_config_option_keys{"nozzle_temperature", "reduce_crossing_wall"});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects nil for a non-nullable vector option", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--nozzle-temperature", "nil"};
|
||||
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects an invalid boolean value", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--reduce-crossing-wall=maybe"};
|
||||
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli accepts the common spellings of a boolean value", "[Config]") {
|
||||
const auto [text, expected] = GENERATE(table<const char*, bool>({
|
||||
{"--reduce-crossing-wall=1", true},
|
||||
{"--reduce-crossing-wall=true", true},
|
||||
{"--reduce-crossing-wall=Yes", true},
|
||||
{"--reduce-crossing-wall=on", true},
|
||||
{"--reduce-crossing-wall=enabled", true},
|
||||
{"--reduce-crossing-wall=TRUE", true},
|
||||
{"--reduce-crossing-wall=oN", true},
|
||||
{"--reduce-crossing-wall=0", false},
|
||||
{"--reduce-crossing-wall=false", false},
|
||||
{"--reduce-crossing-wall=No", false},
|
||||
{"--reduce-crossing-wall=off", false},
|
||||
{"--reduce-crossing-wall=disabled", false},
|
||||
{"--reduce-crossing-wall=FALSE", false},
|
||||
{"--reduce-crossing-wall=DiSaBlEd", false},
|
||||
}));
|
||||
|
||||
DYNAMIC_SECTION(text) {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", text};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value == expected);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli accepts the common boolean spellings inside a bools vector", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble=true,no,1"};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0, 1});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli trims whitespace around boolean spellings", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--reduce-crossing-wall= true ", "--filament-soluble= true , no ,1"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value);
|
||||
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0, 1});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli normalizes boolean spellings when a bools vector is repeated", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble=true", "--filament-soluble=off"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli keeps nil alongside boolean spellings in a nullable bools vector", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--enable-overhang-speed=nil,yes,off"};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
auto* opt = config.opt<ConfigOptionBoolsNullable>("enable_overhang_speed");
|
||||
REQUIRE(opt != nullptr);
|
||||
REQUIRE(opt->values.size() == 3);
|
||||
REQUIRE(opt->is_nil(0));
|
||||
REQUIRE(opt->values[1] == 1);
|
||||
REQUIRE(opt->values[2] == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects an empty item inside a bools vector", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble=true,,1"};
|
||||
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects an unknown spelling next to a valid one in a bools vector", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble=true,affirmative"};
|
||||
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
// The normalization lives in read_cli's boolean branches, so options of other types keep the
|
||||
// value verbatim - a path named "on" or a colour named "true" must not turn into "1".
|
||||
TEST_CASE("read_cli leaves boolean spellings alone for non-boolean options", "[Config]") {
|
||||
SECTION("string option") {
|
||||
Slic3r::DynamicPrintAndCLIConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--logfile=true"};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionString>("logfile")->value == "true");
|
||||
}
|
||||
SECTION("strings vector option") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-colour=on;off"};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionStrings>("filament_colour")->values == std::vector<std::string>{"on", "off"});
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli treats a bare boolean flag as true without consuming the next argument", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--reduce-crossing-wall", "model.3mf"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBool>("reduce_crossing_wall")->value);
|
||||
REQUIRE(extra == t_config_option_keys{"model.3mf"});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects an invalid scalar numeric value", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--top-shell-layers", "several"};
|
||||
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli appends values when a vector option is repeated", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--nozzle-temperature", "210", "--nozzle-temperature", "190,200"};
|
||||
REQUIRE(config.read_cli(5, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionInts>("nozzle_temperature")->values == std::vector<int>{210, 190, 200});
|
||||
// the key is recorded once, on first use
|
||||
REQUIRE(keys == t_config_option_keys{"nozzle_temperature"});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli parses a bools vector given in the --flag=values form", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble=1,0,1"};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1, 0, 1});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects an invalid value inside a bools vector", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble=1,maybe"};
|
||||
REQUIRE_FALSE(config.read_cli(2, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli appends true for a bare bools vector flag", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-soluble"};
|
||||
REQUIRE(config.read_cli(2, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionBools>("filament_soluble")->values == std::vector<unsigned char>{1});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli splits a strings vector on semicolons and unescapes quoted items", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-colour", "#FF0000;\"a\\nb\";#00FF00"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
auto& values = config.opt<ConfigOptionStrings>("filament_colour")->values;
|
||||
REQUIRE(values == std::vector<std::string>{"#FF0000", "a\nb", "#00FF00"});
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli rejects a strings vector with an unterminated quote", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-colour", "\"oops"};
|
||||
REQUIRE_FALSE(config.read_cli(3, argv, &extra, &keys));
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli parses a points vector in the NxM coordinate form", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--printable-area", "0x0,200x0,200x200,0x200"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
auto& points = config.opt<ConfigOptionPoints>("printable_area")->values;
|
||||
REQUIRE(points.size() == 4);
|
||||
REQUIRE_THAT(points[1].x(), Catch::Matchers::WithinAbs(200.0, 1e-9));
|
||||
REQUIRE_THAT(points[1].y(), Catch::Matchers::WithinAbs(0.0, 1e-9));
|
||||
REQUIRE_THAT(points[3].x(), Catch::Matchers::WithinAbs(0.0, 1e-9));
|
||||
REQUIRE_THAT(points[3].y(), Catch::Matchers::WithinAbs(200.0, 1e-9));
|
||||
}
|
||||
|
||||
// logfile is a CLI-only option, so it needs the config type whose def pulls in cli_misc_config_def.
|
||||
TEST_CASE("read_cli stores the log file path as a string", "[Config]") {
|
||||
Slic3r::DynamicPrintAndCLIConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--logfile", "orca.log"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
REQUIRE(config.opt<ConfigOptionString>("logfile")->value == "orca.log");
|
||||
}
|
||||
|
||||
TEST_CASE("read_cli accepts nil entries for a nullable vector option", "[Config]") {
|
||||
Slic3r::DynamicPrintConfig config;
|
||||
t_config_option_keys extra, keys;
|
||||
const char* argv[] = {"orca-slicer", "--filament-retraction-length", "nil,2.5"};
|
||||
REQUIRE(config.read_cli(3, argv, &extra, &keys));
|
||||
auto* opt = config.opt<ConfigOptionFloatsNullable>("filament_retraction_length");
|
||||
REQUIRE(opt != nullptr);
|
||||
REQUIRE(opt->values.size() == 2);
|
||||
REQUIRE(opt->is_nil(0));
|
||||
REQUIRE_FALSE(opt->is_nil(1));
|
||||
REQUIRE_THAT(opt->values[1], Catch::Matchers::WithinAbs(2.5, 1e-9));
|
||||
}
|
||||
|
||||
// get_at() returns values.front() for an out-of-range index, so calling it on an empty vector
|
||||
// option is UB. filament_id and filament_is_support are unpopulated on a CLI from-scratch slice.
|
||||
TEST_CASE("get_filament_type treats empty vector options as absent", "[Config][Filament]")
|
||||
{
|
||||
DynamicPrintConfig config;
|
||||
std::string displayed;
|
||||
|
||||
SECTION("an empty filament_type yields no type at all")
|
||||
{
|
||||
config.set_key_value("filament_type", new ConfigOptionStrings());
|
||||
REQUIRE(config.get_filament_type(displayed, 0) == "");
|
||||
}
|
||||
|
||||
SECTION("an empty filament_is_support falls back to the plain filament type")
|
||||
{
|
||||
config.set_key_value("filament_type", new ConfigOptionStrings({"PETG"}));
|
||||
config.set_key_value("filament_is_support", new ConfigOptionBools());
|
||||
REQUIRE(config.get_filament_type(displayed, 0) == "PETG");
|
||||
REQUIRE(displayed == "PETG");
|
||||
}
|
||||
|
||||
SECTION("a support filament with an empty filament_id resolves from the type alone")
|
||||
{
|
||||
config.set_key_value("filament_type", new ConfigOptionStrings({"PLA"}));
|
||||
config.set_key_value("filament_is_support", new ConfigOptionBools({true}));
|
||||
config.set_key_value("filament_id", new ConfigOptionStrings());
|
||||
REQUIRE(config.get_filament_type(displayed, 0) == "PLA-S");
|
||||
REQUIRE(displayed == "Sup.PLA");
|
||||
}
|
||||
|
||||
SECTION("a populated filament_id still selects the support type by id")
|
||||
{
|
||||
config.set_key_value("filament_type", new ConfigOptionStrings({"PETG"}));
|
||||
config.set_key_value("filament_is_support", new ConfigOptionBools({true}));
|
||||
config.set_key_value("filament_id", new ConfigOptionStrings({"GFS00"}));
|
||||
REQUIRE(config.get_filament_type(displayed, 0) == "PLA-S");
|
||||
REQUIRE(displayed == "Sup.PLA");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,3 +479,84 @@ TEST_CASE("update_values_to_printer_extruders_for_multiple_filaments resolves pe
|
||||
REQUIRE(config.option<ConfigOptionInts>("filament_self_index")->values == std::vector<int>({1, 2}));
|
||||
}
|
||||
}
|
||||
|
||||
// update_values_from_multi_to_multi_2 walks the DESTINATION PRINTER's variant list while writing
|
||||
// into a row taken from the destination PRINT preset, whose arrays are sized to its own
|
||||
// print_extruder_variant. Those two widths disagree until the print preset is re-selected for the
|
||||
// new printer -- Tab::load_current_preset() runs this migration first -- so a project authored on
|
||||
// a single-variant printer, opened and switched to a wider one, wrote past the end of the row.
|
||||
TEST_CASE("update_values_from_multi_to_multi_2 sizes the destination row to the variant count",
|
||||
"[Config][VariantExpansion]")
|
||||
{
|
||||
const std::vector<std::string> src_variants{"Direct Drive Standard"};
|
||||
const std::vector<std::string> dst_variants{"Direct Drive Standard", "Direct Drive High Flow",
|
||||
"Direct Drive Standard", "Direct Drive High Flow"};
|
||||
const std::set<std::string> keys{"outer_wall_speed"};
|
||||
|
||||
// The per-object override as authored on the single-variant printer.
|
||||
const auto object_override = [] {
|
||||
DynamicPrintConfig c;
|
||||
c.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {42.};
|
||||
return c;
|
||||
};
|
||||
|
||||
SECTION("a row narrower than the variant list is grown, not overrun") {
|
||||
DynamicPrintConfig object_config = object_override();
|
||||
DynamicPrintConfig dst;
|
||||
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200.};
|
||||
|
||||
REQUIRE(object_config.update_values_from_multi_to_multi_2(src_variants, dst_variants, dst, keys) == 0);
|
||||
|
||||
const auto& out = object_config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->values;
|
||||
REQUIRE(out.size() == dst_variants.size());
|
||||
// Both "Direct Drive Standard" columns match the source variant, so they take the override.
|
||||
CHECK(out[0] == Catch::Approx(42.));
|
||||
CHECK(out[2] == Catch::Approx(42.));
|
||||
// The High Flow columns have no matching source variant: nil, so the destination keeps
|
||||
// tracking the print preset rather than being pinned to another variant's value.
|
||||
CHECK(std::isnan(out[1]));
|
||||
CHECK(std::isnan(out[3]));
|
||||
}
|
||||
|
||||
// The regression guard: where the row already matches the variant list -- every case that was
|
||||
// not corrupting the heap -- the resize is a no-op and the output is unchanged.
|
||||
SECTION("a correctly sized row is untouched") {
|
||||
DynamicPrintConfig object_config = object_override();
|
||||
DynamicPrintConfig dst;
|
||||
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200., 500., 210., 510.};
|
||||
|
||||
REQUIRE(object_config.update_values_from_multi_to_multi_2(src_variants, dst_variants, dst, keys) == 0);
|
||||
|
||||
const auto& out = object_config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->values;
|
||||
REQUIRE(out.size() == 4);
|
||||
CHECK(out[0] == Catch::Approx(42.)); // matched -> override
|
||||
CHECK(out[1] == Catch::Approx(500.)); // unmatched -> preset value preserved
|
||||
CHECK(out[2] == Catch::Approx(42.));
|
||||
CHECK(out[3] == Catch::Approx(510.));
|
||||
}
|
||||
|
||||
// is_nil(idx) indexes values[idx] with no bounds check, so a source shorter than its own
|
||||
// variant list read out of range before the guard was added.
|
||||
SECTION("a source shorter than its variant list is read in range") {
|
||||
DynamicPrintConfig object_config = object_override(); // one value...
|
||||
DynamicPrintConfig dst;
|
||||
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200., 500.};
|
||||
|
||||
REQUIRE(object_config.update_values_from_multi_to_multi_2(
|
||||
{"Direct Drive Standard", "Direct Drive Standard"}, // ...but two source variants
|
||||
{"Direct Drive Standard", "Direct Drive High Flow"}, dst, keys) == 0);
|
||||
|
||||
const auto& out = object_config.option<ConfigOptionFloatsNullable>("outer_wall_speed")->values;
|
||||
REQUIRE(out.size() == 2);
|
||||
CHECK(out[0] == Catch::Approx(42.));
|
||||
CHECK(out[1] == Catch::Approx(500.));
|
||||
}
|
||||
|
||||
SECTION("an empty destination variant list is refused") {
|
||||
DynamicPrintConfig object_config = object_override();
|
||||
DynamicPrintConfig dst;
|
||||
dst.option<ConfigOptionFloatsNullable>("outer_wall_speed", true)->values = {200.};
|
||||
|
||||
CHECK(object_config.update_values_from_multi_to_multi_2(src_variants, {}, dst, keys) == -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +184,44 @@ TEST_CASE("Printer extruder count tolerates missing nozzle diameter", "[Preset][
|
||||
CHECK(bundle.get_printer_extruder_count() == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Selected printer uses its default or saved bed type", "[Preset][Bundle]")
|
||||
{
|
||||
PresetBundle bundle;
|
||||
Preset& printer = add_inmemory_preset(bundle.printers, "Test Printer");
|
||||
printer.is_system = true;
|
||||
printer.config.option<ConfigOptionString>("printer_model")->value = "TEST-MODEL";
|
||||
printer.config.option<ConfigOptionString>("printer_variant")->value = "0.4";
|
||||
printer.config.option<ConfigOptionString>("default_bed_type")->value = "Engineering Plate";
|
||||
|
||||
AppConfig app_config;
|
||||
app_config.set("curr_bed_type", std::to_string(static_cast<int>(btPTE)));
|
||||
PresetBundle::PresetPreferences preferred_selection;
|
||||
BedType expected_bed_type;
|
||||
|
||||
SECTION("New printer uses its symbolic default") {
|
||||
expected_bed_type = btEP;
|
||||
preferred_selection = {"TEST-MODEL", "0.4"};
|
||||
}
|
||||
SECTION("Re-enabled printer uses its saved selection") {
|
||||
expected_bed_type = btPC;
|
||||
preferred_selection = {"TEST-MODEL", "0.4"};
|
||||
app_config.set_printer_setting("Test Printer", "curr_bed_type",
|
||||
std::to_string(static_cast<int>(expected_bed_type)));
|
||||
}
|
||||
SECTION("Existing printer keeps its saved selection after presets reload") {
|
||||
expected_bed_type = btPCT;
|
||||
app_config.set("presets", PRESET_PRINTER_NAME, "Test Printer");
|
||||
app_config.set_printer_setting("Test Printer", "curr_bed_type",
|
||||
std::to_string(static_cast<int>(expected_bed_type)));
|
||||
}
|
||||
|
||||
bundle.load_selections(app_config, preferred_selection);
|
||||
bundle.export_selections(app_config);
|
||||
|
||||
CHECK(bundle.project_config.opt_enum<BedType>("curr_bed_type") == expected_bed_type);
|
||||
CHECK(app_config.get_printer_setting("Test Printer", "curr_bed_type") == std::to_string(static_cast<int>(expected_bed_type)));
|
||||
}
|
||||
|
||||
TEST_CASE("find_preset resolves a system preset's renamed_from", "[Preset][Rename]")
|
||||
{
|
||||
RenameTestCollection coll;
|
||||
|
||||
Reference in New Issue
Block a user