Merge branch 'main' into feature/texture_displacement

This commit is contained in:
SoftFever
2026-07-21 19:50:35 +08:00
631 changed files with 2326755 additions and 1243600 deletions

View File

@@ -16,6 +16,7 @@ add_executable(${_TEST_NAME}_tests
test_toolordering_nozzle_group.cpp
test_preset_bundle_loading.cpp
test_preset_setting_id.cpp
test_preset_diff.cpp
test_elephant_foot_compensation.cpp
test_geometry.cpp
test_placeholder_parser.cpp

View File

@@ -59,7 +59,9 @@ TEST_CASE("NetworkLibraryVersionInfo::from_static", "[BambuNetworking]") {
REQUIRE(info.suffix == "");
REQUIRE(info.display_name == "02.03.00.62");
REQUIRE(info.url_override == "");
REQUIRE(info.is_latest == true);
// from_static no longer propagates the static is_latest flag; it is a placeholder
// that get_all_available_versions() assigns once the list is sorted newest-first.
REQUIRE(info.is_latest == false);
REQUIRE(info.warning == "");
REQUIRE(info.is_discovered == false);
}

View File

@@ -5,10 +5,14 @@
#include "libslic3r/LocalesUtils.hpp"
#include <cereal/types/polymorphic.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/types/string.hpp>
#include <cereal/types/vector.hpp>
#include <cereal/archives/binary.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/fstream.hpp>
#include <nlohmann/json.hpp>
using namespace Slic3r;
SCENARIO("Generic config validation performs as expected.", "[Config]") {
@@ -402,6 +406,136 @@ SCENARIO("update_diff_values_to_child_config tolerates legacy machine-limit vect
// }
// }
TEST_CASE("save_to_json round-trips plugin capability references as strings", "[Config][plugins]") {
namespace fs = boost::filesystem;
const fs::path tmp = fs::temp_directory_path() / fs::unique_path("orca_plugins_%%%%-%%%%.json");
const std::vector<std::string> refs = {
"local_plugin;;inset",
"cloud_plugin;550e8400-e29b-41d4-a716-446655440000;inset"
};
std::unique_ptr<DynamicPrintConfig> config_ptr(
DynamicPrintConfig::new_from_defaults_keys({"slicing_pipeline_plugin"}));
DynamicPrintConfig config = std::move(*config_ptr);
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = refs;
config.save_to_json(tmp.string(), "test_preset", "User", "1.0.0.0");
nlohmann::json j;
{
boost::nowide::ifstream ifs(tmp.string());
ifs >> j;
}
REQUIRE(j["slicing_pipeline_plugin"] == nlohmann::json(refs));
CHECK_FALSE(j.contains("plugins"));
DynamicPrintConfig reloaded = DynamicPrintConfig::full_print_config();
ConfigSubstitutionContext substitutions(ForwardCompatibilitySubstitutionRule::Disable);
std::map<std::string, std::string> key_values;
std::string reason;
REQUIRE(reloaded.load_from_json(tmp.string(), substitutions, true, key_values, reason) == 0);
CHECK(reason.empty());
CHECK(reloaded.option<ConfigOptionStrings>("slicing_pipeline_plugin")->values == refs);
fs::remove(tmp);
}
TEST_CASE("plugin capability references survive string-map serialization", "[Config][plugins]") {
const std::vector<std::string> refs = {
"master_plugin;;header-stamp",
"Sample Plugin;1f998ea9-0183-4cc5-957f-4eef659ba4e6;G-code Benchmark (.py)"
};
DynamicPrintConfig original = DynamicPrintConfig::full_print_config();
original.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = refs;
std::map<std::string, std::string> serialized{
{"slicing_pipeline_plugin", original.option<ConfigOptionStrings>("slicing_pipeline_plugin")->serialize()}
};
CHECK(serialized["slicing_pipeline_plugin"].find("\"master_plugin;;header-stamp\"") != std::string::npos);
DynamicPrintConfig reloaded = DynamicPrintConfig::full_print_config();
reloaded.load_string_map(serialized, ForwardCompatibilitySubstitutionRule::Disable);
CHECK(reloaded.option<ConfigOptionStrings>("slicing_pipeline_plugin")->values == refs);
}
TEST_CASE("parse_capability_ref parses local and cloud references", "[Config][plugin]") {
const auto local = Slic3r::parse_capability_ref("local_plugin;;post_process");
REQUIRE(local.has_value());
CHECK(local->name == "local_plugin");
CHECK(local->capability_name == "post_process");
CHECK(local->uuid.empty());
const auto cloud = Slic3r::parse_capability_ref(
"cloud_plugin;550e8400-e29b-41d4-a716-446655440000;post_process");
REQUIRE(cloud.has_value());
CHECK(cloud->name == "cloud_plugin");
CHECK(cloud->capability_name == "post_process");
CHECK(cloud->uuid == "550e8400-e29b-41d4-a716-446655440000");
}
TEST_CASE("parse_capability_ref rejects malformed input", "[Config][plugin]") {
CHECK_FALSE(Slic3r::parse_capability_ref("").has_value());
CHECK_FALSE(Slic3r::parse_capability_ref("plugin").has_value());
CHECK_FALSE(Slic3r::parse_capability_ref("plugin;uuid").has_value());
CHECK_FALSE(Slic3r::parse_capability_ref(";;capability").has_value());
CHECK_FALSE(Slic3r::parse_capability_ref(";uuid;capability").has_value());
CHECK_FALSE(Slic3r::parse_capability_ref("plugin;;").has_value());
CHECK_FALSE(Slic3r::parse_capability_ref("plugin;uuid;").has_value());
}
namespace {
// Installs a stub capability resolver that echoes the capability type into the reference, so tests
// can assert each plugin-backed option resolved with its own ConfigOptionDef::plugin_type. Resets
// the global resolver on teardown -- tests run in random order and other cases assert the
// no-resolver behavior (an absent "plugins" manifest).
struct PluginResolverFixture {
PluginResolverFixture() {
ConfigBase::set_resolve_capability_fn([](const std::string& name, const std::string& type) {
return name.empty() ? std::string() : name + ";;" + type;
});
}
~PluginResolverFixture() { ConfigBase::set_resolve_capability_fn(nullptr); }
};
} // namespace
TEST_CASE_METHOD(PluginResolverFixture,
"update_plugin_manifest derives references generically from plugin-backed options",
"[Config][plugins]") {
// Both scalar (printer_agent) and vector (slicing_pipeline_plugin) options opt in via a non-empty
// ConfigOptionDef::plugin_type (is_plugin_backed) and are resolved with it -- there is no hardcoded
// per-option switch. printer_agent in particular relies on its plugin_type metadata being wired up
// (it is edited via a dedicated widget, not the plugin_picker).
std::unique_ptr<DynamicPrintConfig> config_ptr(DynamicPrintConfig::new_from_defaults_keys(
{"slicing_pipeline_plugin", "printer_agent"}));
DynamicPrintConfig config = std::move(*config_ptr);
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = {"sp"};
config.option<ConfigOptionString>("printer_agent", true)->value = "agent";
config.update_plugin_manifest();
const std::vector<std::string> manifest = config.option<ConfigOptionStrings>("plugins")->values;
using Catch::Matchers::VectorContains;
REQUIRE_THAT(manifest, VectorContains(std::string("sp;;slicing-pipeline")));
REQUIRE_THAT(manifest, VectorContains(std::string("agent;;printer-connection")));
CHECK(manifest.size() == 2);
}
TEST_CASE_METHOD(PluginResolverFixture,
"update_plugin_manifest de-duplicates references and skips unset options",
"[Config][plugins]") {
std::unique_ptr<DynamicPrintConfig> config_ptr(DynamicPrintConfig::new_from_defaults_keys(
{"slicing_pipeline_plugin", "printer_agent"}));
DynamicPrintConfig config = std::move(*config_ptr);
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = {"x", "x"}; // duplicate
// printer_agent stays at its default empty value -> contributes nothing to the manifest.
config.update_plugin_manifest();
const std::vector<std::string> manifest = config.option<ConfigOptionStrings>("plugins")->values;
CHECK(manifest == std::vector<std::string>{"x;;slicing-pipeline"});
}
TEST_CASE("H2C/A2L-era multi-nozzle and pre-heat config keys exist", "[config]") {
// Foundation keys backing H2C 6-nozzle cluster grouping, the pre-heat/pre-cool time
// model, and wipe-tower nozzle-change handling. Defaults must keep existing

View File

@@ -0,0 +1,35 @@
#include <catch2/catch_all.hpp>
#include "libslic3r/Preset.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <algorithm>
using namespace Slic3r;
// Regression test for the python-plugin branch's intentional divergence from
// upstream in add_correct_opts_to_diff() (src/libslic3r/Preset.cpp): a vector
// option entry whose index is beyond the reference vector's length is reported
// dirty even when it duplicates an existing value. On main these duplicates
// were NOT flagged. See the comment on add_correct_opts_to_diff() in src/libslic3r/Preset.cpp.
TEST_CASE("deep_diff flags new vector entries that duplicate values[0]", "[PresetDiff][Config]")
{
// reference: single-extruder vector (one entry)
Preset reference(Preset::TYPE_PRINTER, "ref");
reference.config.set_key_value("nozzle_diameter", new ConfigOptionFloats{0.4});
// edited: a second extruder entry was added whose value duplicates the first
Preset edited(Preset::TYPE_PRINTER, "edited");
edited.config.set_key_value("nozzle_diameter", new ConfigOptionFloats{0.4, 0.4});
// deep_compare = true routes through deep_diff() -> add_correct_opts_to_diff()
std::vector<std::string> diff =
PresetCollection::dirty_options(&edited, &reference, /*deep_compare=*/true);
// The new index #1 is reported dirty even though 0.4 == values[0] (0.4).
REQUIRE(std::find(diff.begin(), diff.end(), "nozzle_diameter#1") != diff.end());
// Sanity: the unchanged existing index #0 is NOT reported, so the rule is
// specific to new indices rather than flagging the whole vector.
REQUIRE(std::find(diff.begin(), diff.end(), "nozzle_diameter#0") == diff.end());
}