Merge branch 'main' into pr/tommasobbianchi/15238

This commit is contained in:
SoftFever
2026-09-08 14:28:31 +08:00
4759 changed files with 44361 additions and 18139 deletions
+1
View File
@@ -8,6 +8,7 @@ add_executable(${_TEST_NAME}_tests
test_arachne_walls.cpp
test_arrange.cpp
test_bambu_networking.cpp
test_buildvolume.cpp
test_calib.cpp
test_clipper_offset.cpp
test_clipper_utils.cpp
+40
View File
@@ -0,0 +1,40 @@
#include <catch2/catch_all.hpp>
#include "libslic3r/BuildVolume.hpp"
using namespace Slic3r;
static std::vector<Vec2d> rect_area(double w, double d)
{
return { { 0., 0. }, { w, 0. }, { w, d }, { 0., d } };
}
// extruder_printable_height and extruder_printable_area are independent config options, so a
// profile can leave the heights short. BuildVolume must not index past the end of the heights.
TEST_CASE("BuildVolume falls back to the bed height when extruder_printable_height is short", "[BuildVolume]")
{
const std::vector<Vec2d> bed = rect_area(200., 200.);
const std::vector<std::vector<Vec2d>> areas = { rect_area(200., 200.), rect_area(100., 200.) };
const std::vector<double> heights = { 180. };
const BuildVolume build_volume(bed, 250., areas, heights);
REQUIRE(build_volume.get_extruder_area_count() == 2);
// The extruder with a height of its own keeps it, and differs from the bed, so it gets its own volume.
CHECK_THAT(build_volume.get_extruder_area_volume(0).bboxf.max.z(), Catch::Matchers::WithinAbs(180., 1e-6));
// The extruder without one falls back to the bed's printable_height instead of reading out of range.
CHECK_THAT(build_volume.get_extruder_area_volume(1).bboxf.max.z(), Catch::Matchers::WithinAbs(250., 1e-6));
}
TEST_CASE("BuildVolume keeps per-extruder heights when both vectors match", "[BuildVolume]")
{
const std::vector<Vec2d> bed = rect_area(200., 200.);
const std::vector<std::vector<Vec2d>> areas = { rect_area(120., 200.), rect_area(100., 200.) };
const std::vector<double> heights = { 180., 200.5 };
const BuildVolume build_volume(bed, 250., areas, heights);
REQUIRE(build_volume.get_extruder_area_count() == 2);
CHECK_THAT(build_volume.get_extruder_area_volume(0).bboxf.max.z(), Catch::Matchers::WithinAbs(180., 1e-6));
CHECK_THAT(build_volume.get_extruder_area_volume(1).bboxf.max.z(), Catch::Matchers::WithinAbs(200.5, 1e-6));
}
+263
View File
@@ -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");
}
}
@@ -130,6 +130,12 @@ TEST_CASE("get_config_index_base resolves (volume type, extruder type, id) to a
}
}
TEST_CASE("support interface pattern registry includes spiral inset", "[Config]")
{
const auto &values = ConfigOptionEnum<SupportMaterialInterfacePattern>::get_enum_values();
REQUIRE(values.at("spiralinset") == SupportMaterialInterfacePattern::smipSpiralInset);
}
TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]")
{
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;
@@ -479,3 +485,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);
}
}
@@ -1,5 +1,6 @@
#include <catch2/catch_all.hpp>
#include <algorithm>
#include <boost/filesystem.hpp>
#include <fstream>
@@ -184,6 +185,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;
@@ -567,6 +606,88 @@ TEST_CASE("A printer specific filament supersedes the generic library filament w
}
namespace {
// One system printer plus the filament presets a machine facing dialog has to choose between:
// an Orca Filament Library generic with no compatible_printers, a same alias vendor filament
// that names the printer, a library filament with no vendor twin, and a vendor filament that
// belongs to a different printer.
struct MachineFilaments
{
PresetBundle bundle;
VendorProfile library{PresetBundle::ORCA_FILAMENT_LIBRARY};
VendorProfile vendor{"Vendor"};
MachineFilaments()
{
// VendorProfile's constructor takes an id; the library rule keys off the name.
library.name = PresetBundle::ORCA_FILAMENT_LIBRARY;
vendor.name = "Vendor";
Preset &printer = add_inmemory_preset(bundle.printers, "Printer A 0.4 nozzle");
printer.is_system = true;
printer.vendor = &vendor;
printer.config.option<ConfigOptionString>("printer_model", true)->value = "Printer A";
add_filament(library, "Generic ABS @System", "Generic ABS", {});
add_filament(vendor, "Generic ABS @Printer A", "Generic ABS", { "Printer A 0.4 nozzle" });
add_filament(library, "FilAr ABS @System", "FilAr ABS", {});
add_filament(vendor, "Vendor PLA @Printer B", "Vendor PLA", { "Printer B 0.4 nozzle" });
// update_library_profile_excluded_from() is protected and has its own test above; record
// the exclusion it derives from the same alias vendor filament.
Preset *shadowed = bundle.filaments.find_preset("Generic ABS @System");
REQUIRE(shadowed != nullptr);
shadowed->m_excluded_from.insert("Printer A 0.4 nozzle");
}
void add_filament(const VendorProfile &owner, const std::string &name, const std::string &alias,
std::vector<std::string> compatible_printers)
{
Preset &preset = add_inmemory_preset(bundle.filaments, name);
preset.is_system = true;
preset.alias = alias;
preset.vendor = &owner;
compatible_list(bundle.filaments, name, "compatible_printers") = std::move(compatible_printers);
}
bool offers(const std::string &preset_name, bool include_user_presets = false)
{
const std::vector<Preset *> offered =
bundle.get_filament_presets_for_machine("Printer A", "0.4", include_user_presets);
return std::any_of(offered.begin(), offered.end(),
[&preset_name](const Preset *p) { return p->name == preset_name; });
}
};
} // namespace
TEST_CASE("Filaments offered for a machine follow the app's compatibility rule", "[Preset][Bundle]")
{
MachineFilaments f;
SECTION("a library filament with no compatible_printers is offered") {
CHECK(f.offers("FilAr ABS @System"));
}
SECTION("a same alias vendor filament shadows the library generic") {
CHECK(f.offers("Generic ABS @Printer A"));
CHECK_FALSE(f.offers("Generic ABS @System"));
}
SECTION("a filament naming a different printer is not offered") {
CHECK_FALSE(f.offers("Vendor PLA @Printer B"));
}
SECTION("a user filament is offered only when the printer supports user presets") {
add_inmemory_preset(f.bundle.filaments, "My PLA");
CHECK_FALSE(f.offers("My PLA", /*include_user_presets=*/false));
CHECK(f.offers("My PLA", /*include_user_presets=*/true));
}
}
namespace {
const char *kMixedKeys[] = {
+3 -3
View File
@@ -5,10 +5,10 @@
using namespace Slic3r;
// Golden vectors from the Python reference generate_preset_setting_id (defined in
// scripts/assign_vendor_setting_ids.py). The C++ generate_preset_setting_id() MUST stay
// byte-identical to it, otherwise app-side on-the-fly ids would diverge from the
// scripts/orca_id_tool.py). The C++ generate_preset_setting_id() MUST stay byte-identical
// to it, otherwise app-side on-the-fly ids would diverge from the
// script-assigned ones in the profiles. Regenerate a vector with:
// python3 -c "from assign_vendor_setting_ids import generate_preset_setting_id as g; print(g('Afinia','filament','Afinia ABS @Afinia H400'))"
// python3 -c "import sys; sys.path.insert(0, 'scripts'); from orca_id_tool import generate_preset_setting_id as g; print(g('Afinia','filament','Afinia ABS @Afinia H400'))"
TEST_CASE("preset setting_id matches the Python reference", "[Preset][setting_id]") {
struct Vec { const char* vendor; const char* type; const char* name; const char* expected; };
const Vec vectors[] = {