From 5f01f21661d5bd4002a6b261464ec4cd13cb3c7d Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Mon, 14 Sep 2026 17:44:17 +0800 Subject: [PATCH 1/3] Load Each Vendor Tree Once When the CLI Resolves System Presets Resolving a system preset through its vendor manifest loaded the whole vendor tree and the filament library from JSON, and the CLI did that separately for every --load-settings and --load-filaments file. A run with machine, process and filament presets parsed BBL's 2,879 profile files and the library's 512 three times over, about a second each. Keep the library and vendor bundles loaded by the manifest path on the PresetBundle that resolved them, keyed by source root, vendor and substitution rule, and have the CLI resolve every system preset through one bundle for the whole run. A failed load is not kept, so errors are reported as before. On a cube slice with X1C machine, process and PLA presets: 2.42 s -> 0.93 s, BBL.json opened once instead of three times, identical G-code. --- src/OrcaSlicer.cpp | 10 ++-- src/libslic3r/PresetBundle.cpp | 58 ++++++++++++------- src/libslic3r/PresetBundle.hpp | 14 +++++ .../libslic3r/test_preset_bundle_loading.cpp | 44 ++++++++++++++ 4 files changed, 100 insertions(+), 26 deletions(-) diff --git a/src/OrcaSlicer.cpp b/src/OrcaSlicer.cpp index b75c653eda..f2ce73e1f4 100644 --- a/src/OrcaSlicer.cpp +++ b/src/OrcaSlicer.cpp @@ -2010,19 +2010,21 @@ int CLI::run(int argc, char **argv) } }; - auto resolve_preset = [&ensure_cli_preset_bundle](const std::string &file, DynamicPrintConfig &config, + // One resolver for the whole run, so presets from the same vendor tree share its load. + std::unique_ptr system_preset_resolver; + auto resolve_preset = [&ensure_cli_preset_bundle, &system_preset_resolver](const std::string &file, DynamicPrintConfig &config, std::string &config_type, const std::string &config_from, bool probe_type, std::string &error) { const auto *inherits = config.option(BBL_JSON_KEY_INHERITS); if (!probe_type && (inherits == nullptr || inherits->value.empty())) return true; - std::unique_ptr source_bundle; PresetBundle *bundle = nullptr; bool allow_source_manifest = false; if (config_from == "system") { - source_bundle = std::make_unique(); - bundle = source_bundle.get(); + if (!system_preset_resolver) + system_preset_resolver = std::make_unique(); + bundle = system_preset_resolver.get(); allow_source_manifest = true; } else { bundle = ensure_cli_preset_bundle(error); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 4b8fb03a02..9cef965490 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -549,30 +549,11 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ continue; try { - PresetBundle library_bundle; - const PresetBundle *base_bundle = nullptr; - if (vendor_id != ORCA_FILAMENT_LIBRARY && - boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { - library_bundle.m_preserve_vendor_source_paths = true; - library_bundle.load_vendor_configs_from_json(root_dir.string(), ORCA_FILAMENT_LIBRARY, LoadSystem, - compatibility_rule, nullptr, false); - if (library_bundle.error_count() != 0) { - error = "OrcaFilamentLibrary contains invalid presets"; - return false; - } - base_bundle = &library_bundle; - } - - PresetBundle source_bundle; - source_bundle.m_preserve_vendor_source_paths = true; - source_bundle.load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, - compatibility_rule, base_bundle, false); - if (source_bundle.error_count() != 0) { - error = "Vendor bundle contains invalid presets"; + const SourceManifestBundles *loaded = load_source_manifest(root_dir, vendor_id, compatibility_rule, error); + if (loaded == nullptr) return false; - } - const Preset *resolved = find_loaded(source_bundle); + const Preset *resolved = find_loaded(*loaded->vendor); if (resolved == nullptr) { if (error.empty()) error = "Source file is not an instantiated preset in its vendor manifest"; @@ -591,6 +572,39 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ return false; } +const PresetBundle::SourceManifestBundles *PresetBundle::load_source_manifest(const boost::filesystem::path &root_dir, + const std::string &vendor_id, + ForwardCompatibilitySubstitutionRule compatibility_rule, + std::string &error) +{ + auto key = std::make_tuple(root_dir.string(), vendor_id, static_cast(compatibility_rule)); + if (auto it = m_source_manifest_bundles.find(key); it != m_source_manifest_bundles.end()) + return &it->second; + + SourceManifestBundles loaded; + if (vendor_id != ORCA_FILAMENT_LIBRARY && + boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { + loaded.library = std::make_unique(); + loaded.library->m_preserve_vendor_source_paths = true; + loaded.library->load_vendor_configs_from_json(root_dir.string(), ORCA_FILAMENT_LIBRARY, LoadSystem, + compatibility_rule, nullptr, false); + if (loaded.library->error_count() != 0) { + error = "OrcaFilamentLibrary contains invalid presets"; + return nullptr; + } + } + + loaded.vendor = std::make_unique(); + loaded.vendor->m_preserve_vendor_source_paths = true; + loaded.vendor->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, + compatibility_rule, loaded.library.get(), false); + if (loaded.vendor->error_count() != 0) { + error = "Vendor bundle contains invalid presets"; + return nullptr; + } + return &m_source_manifest_bundles.emplace(std::move(key), std::move(loaded)).first->second; +} + bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type, const std::string &source_file, ForwardCompatibilitySubstitutionRule compatibility_rule, diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 353b6dc07d..a0fceb332b 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -652,6 +653,19 @@ private: bool m_generate_vendor_caches { false }; bool m_preserve_vendor_source_paths { false }; + // Vendor trees loaded by resolve_preset_config's manifest path, so every preset + // resolved through this bundle shares one load per source root and vendor. + struct SourceManifestBundles { + std::unique_ptr library; + std::unique_ptr vendor; + }; + std::map, SourceManifestBundles> m_source_manifest_bundles; + + const SourceManifestBundles *load_source_manifest(const boost::filesystem::path &root_dir, + const std::string &vendor_id, + ForwardCompatibilitySubstitutionRule compatibility_rule, + std::string &error); + // Orca: validation only - flag any printer with two or more compatible // filament presets sharing one filament_id (ambiguous AMS subtype match). bool check_duplicate_filament_subtypes() const; diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index ecdede7053..5341e6c621 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -987,6 +987,50 @@ TEST_CASE("Resolution terminates when no vendor manifest exists", "[Preset][Bund CHECK(error == "Preset was not found in the loaded bundle"); } +TEST_CASE("Manifest-backed resolution reuses the vendor tree it already loaded", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path process_dir = dir.path() / "Acme" / "process"; + fs::create_directories(process_dir); + std::ofstream((dir.path() / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme","process_list":[)" + << R"({"name":"fdm_process_common","sub_path":"process/base.json"},)" + << R"({"name":"Acme First","sub_path":"process/first.json"},)" + << R"({"name":"Acme Second","sub_path":"process/second.json"}]})"; + auto write_base = [&](double travel_speed) { + std::ofstream((process_dir / "base.json").string()) + << R"({"type":"process","name":"fdm_process_common","from":"system",)" + << R"("instantiation":"false","travel_speed":[")" << travel_speed << R"("]})"; + }; + auto write_child = [&](const std::string &file, const std::string &name) { + std::ofstream((process_dir / file).string()) + << R"({"type":"process","name":")" << name << R"(","from":"system",)" + << R"("instantiation":"true","inherits":"fdm_process_common"})"; + }; + write_base(111.0); + write_child("first.json", "Acme First"); + write_child("second.json", "Acme Second"); + + auto travel_speed = [&](PresetBundle &bundle, const std::string &file) { + DynamicPrintConfig raw; + raw.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common"; + std::string error; + REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, (process_dir / file).string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + return raw.option("travel_speed")->values.front(); + }; + + PresetBundle bundle; + CHECK_THAT(travel_speed(bundle, "first.json"), Catch::Matchers::WithinAbs(111.0, 1e-6)); + + // Only a reload would see this change. + write_base(222.0); + CHECK_THAT(travel_speed(bundle, "second.json"), Catch::Matchers::WithinAbs(111.0, 1e-6)); + + PresetBundle fresh; + CHECK_THAT(travel_speed(fresh, "second.json"), Catch::Matchers::WithinAbs(222.0, 1e-6)); +} + // Orca: a filament in the Orca Filament Library that names its compatible printers has to hide the generic // library filament sharing its alias, the same way a vendor owned filament does. Otherwise both are compatible // with that printer and the plater combo box lists the shared alias twice. From d4840901fc2476e6d141ab46da51a8e361705516 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Mon, 14 Sep 2026 18:51:48 +0800 Subject: [PATCH 2/3] Test That Failed Vendor Loads Are Not Kept and the Library Base Is Reused Cover the two cache paths the first test left open: a vendor tree that fails to load is retried on the next resolution instead of being served from the cache, and a type-probed filament resolved through resolve_preset_config_type reuses the OrcaFilamentLibrary base already loaded for a sibling. --- .../libslic3r/test_preset_bundle_loading.cpp | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index 5341e6c621..29c38395ac 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -1031,6 +1031,93 @@ TEST_CASE("Manifest-backed resolution reuses the vendor tree it already loaded", CHECK_THAT(travel_speed(fresh, "second.json"), Catch::Matchers::WithinAbs(222.0, 1e-6)); } +TEST_CASE("Manifest-backed resolution does not keep a vendor tree that failed to load", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path child_file = dir.path() / "Acme" / "process" / "child.json"; + auto write_manifest = [&](const std::string &leading_entry) { + std::ofstream((dir.path() / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme","process_list":[)" << leading_entry + << R"({"name":"Acme Process","sub_path":"process/child.json"}]})"; + }; + write_manifest("123,"); + fs::create_directories(child_file.parent_path()); + std::ofstream(child_file.string()) + << R"({"type":"process","name":"Acme Process","from":"system",)" + << R"("instantiation":"true","layer_height":"0.2"})"; + + PresetBundle bundle; + auto resolve = [&](std::string &error) { + DynamicPrintConfig raw; + raw.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common"; + return bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, child_file.string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error); + }; + + std::string error; + CHECK_FALSE(resolve(error)); + CHECK_FALSE(error.empty()); + + write_manifest(""); + error.clear(); + CHECK(resolve(error)); + CHECK(error.empty()); +} + +TEST_CASE("Manifest-backed resolution reuses the library base for type-probed files", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path library_pet = dir.path() / PresetBundle::ORCA_FILAMENT_LIBRARY / "filament" / "pet.json"; + const fs::path filament_dir = dir.path() / "Acme" / "filament"; + + std::ofstream((dir.path() / (std::string(PresetBundle::ORCA_FILAMENT_LIBRARY) + ".json")).string()) + << R"({"version":"1.0.0","name":"OrcaFilamentLibrary","filament_list":[)" + << R"({"name":"fdm_filament_pet","sub_path":"filament/pet.json","filament_id":"GFL99"}]})"; + fs::create_directories(library_pet.parent_path()); + auto write_library_pet = [&](double density) { + std::ofstream(library_pet.string()) + << R"({"type":"filament","name":"fdm_filament_pet","from":"system",)" + << R"("filament_id":"GFL99","instantiation":"false",)" + << R"("filament_type":["PETG"],"filament_density":[")" << density << R"("]})"; + }; + write_library_pet(1.27); + + std::ofstream((dir.path() / "Acme.json").string()) + << R"({"version":"1.0.0","name":"Acme","filament_list":[)" + << R"({"name":"Acme PETG","sub_path":"filament/petg.json","filament_id":"GFA00"},)" + << R"({"name":"Acme PETG Matte","sub_path":"filament/petg_matte.json","filament_id":"GFA01"}]})"; + fs::create_directories(filament_dir); + auto write_child = [&](const std::string &file, const std::string &name, const std::string &filament_id) { + std::ofstream((filament_dir / file).string()) + << R"({"type":"filament","name":")" << name << R"(","from":"system",)" + << R"("filament_id":")" << filament_id << R"(","instantiation":"true","inherits":"fdm_filament_pet"})"; + }; + write_child("petg.json", "Acme PETG", "GFA00"); + write_child("petg_matte.json", "Acme PETG Matte", "GFA01"); + + auto density = [](const DynamicPrintConfig &config) { + return config.option("filament_density")->values.front(); + }; + + PresetBundle bundle; + DynamicPrintConfig first; + first.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_filament_pet"; + std::string error; + REQUIRE(bundle.resolve_preset_config(first, Preset::TYPE_FILAMENT, (filament_dir / "petg.json").string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK_THAT(density(first), Catch::Matchers::WithinAbs(1.27, 1e-6)); + + // Only a reload would see this change. + write_library_pet(1.5); + + DynamicPrintConfig second; + Preset::Type type = Preset::TYPE_INVALID; + REQUIRE(bundle.resolve_preset_config_type(second, type, (filament_dir / "petg_matte.json").string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + CHECK(type == Preset::TYPE_FILAMENT); + CHECK_THAT(density(second), Catch::Matchers::WithinAbs(1.27, 1e-6)); +} + // Orca: a filament in the Orca Filament Library that names its compatible printers has to hide the generic // library filament sharing its alias, the same way a vendor owned filament does. Otherwise both are compatible // with that printer and the plater combo box lists the shared alias twice. From d5cf1502c442b0b4860dedfa0b6d791d243f4299 Mon Sep 17 00:00:00 2001 From: Hanif Koh Date: Tue, 15 Sep 2026 13:31:30 +0800 Subject: [PATCH 3/3] Share One Library Load Between Vendors in the CLI Preset Resolver The manifest resolver loaded OrcaFilamentLibrary once per vendor it resolved through, so a run that mixes vendors parsed the library tree again for each of them. The library is now cached like any other vendor tree, keyed on its root and substitution rule, and doubles as the base every vendor under that root loads against. A vendor bundle only reads from its base while loading, so sharing the instance is safe. The cache key carries the substitution rule as its enum, and the lookup lambdas take a const bundle since they only read. --- src/libslic3r/PresetBundle.cpp | 48 ++++++++-------- src/libslic3r/PresetBundle.hpp | 18 +++--- .../libslic3r/test_preset_bundle_loading.cpp | 56 +++++++++++++++++++ 3 files changed, 87 insertions(+), 35 deletions(-) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 9cef965490..54e5db27e4 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -484,7 +484,7 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem) compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable; - auto collection_for_type = [](PresetBundle &bundle, Preset::Type preset_type) -> PresetCollection * { + auto collection_for_type = [](const PresetBundle &bundle, Preset::Type preset_type) -> const PresetCollection * { switch (preset_type) { case Preset::TYPE_PRINT: return &bundle.prints; case Preset::TYPE_FILAMENT: return &bundle.filaments; @@ -493,15 +493,15 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ } }; - PresetCollection *collection = collection_for_type(*this, type); + const PresetCollection *collection = collection_for_type(*this, type); if (collection == nullptr) { error = "Unsupported preset type"; return false; } const boost::filesystem::path source_path = boost::filesystem::absolute(source_file).lexically_normal(); - auto find_loaded = [&](PresetBundle &bundle) -> const Preset * { - PresetCollection *loaded_collection = collection_for_type(bundle, type); + auto find_loaded = [&](const PresetBundle &bundle) -> const Preset * { + const PresetCollection *loaded_collection = collection_for_type(bundle, type); const Preset *resolved = nullptr; for (const Preset &preset : loaded_collection->get_presets()) { if (preset.file.empty()) @@ -549,11 +549,11 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ continue; try { - const SourceManifestBundles *loaded = load_source_manifest(root_dir, vendor_id, compatibility_rule, error); + const PresetBundle *loaded = load_source_vendor(root_dir, vendor_id, compatibility_rule, error); if (loaded == nullptr) return false; - const Preset *resolved = find_loaded(*loaded->vendor); + const Preset *resolved = find_loaded(*loaded); if (resolved == nullptr) { if (error.empty()) error = "Source file is not an instantiated preset in its vendor manifest"; @@ -572,37 +572,35 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ return false; } -const PresetBundle::SourceManifestBundles *PresetBundle::load_source_manifest(const boost::filesystem::path &root_dir, - const std::string &vendor_id, - ForwardCompatibilitySubstitutionRule compatibility_rule, - std::string &error) +const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::path &root_dir, + const std::string &vendor_id, + ForwardCompatibilitySubstitutionRule compatibility_rule, + std::string &error) { - auto key = std::make_tuple(root_dir.string(), vendor_id, static_cast(compatibility_rule)); - if (auto it = m_source_manifest_bundles.find(key); it != m_source_manifest_bundles.end()) - return &it->second; + auto key = std::make_tuple(root_dir.string(), vendor_id, compatibility_rule); + if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end()) + return it->second.get(); - SourceManifestBundles loaded; + // The library loads with no base of its own, so the tree a vendor inherits from + // is the same one that resolves the library's own presets. + const PresetBundle *library = nullptr; if (vendor_id != ORCA_FILAMENT_LIBRARY && boost::filesystem::is_regular_file(root_dir / (std::string(ORCA_FILAMENT_LIBRARY) + ".json"))) { - loaded.library = std::make_unique(); - loaded.library->m_preserve_vendor_source_paths = true; - loaded.library->load_vendor_configs_from_json(root_dir.string(), ORCA_FILAMENT_LIBRARY, LoadSystem, - compatibility_rule, nullptr, false); - if (loaded.library->error_count() != 0) { + library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error); + if (library == nullptr) { error = "OrcaFilamentLibrary contains invalid presets"; return nullptr; } } - loaded.vendor = std::make_unique(); - loaded.vendor->m_preserve_vendor_source_paths = true; - loaded.vendor->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, - compatibility_rule, loaded.library.get(), false); - if (loaded.vendor->error_count() != 0) { + auto bundle = std::make_unique(); + bundle->m_preserve_vendor_source_paths = true; + bundle->load_vendor_configs_from_json(root_dir.string(), vendor_id, LoadSystem, compatibility_rule, library, false); + if (bundle->error_count() != 0) { error = "Vendor bundle contains invalid presets"; return nullptr; } - return &m_source_manifest_bundles.emplace(std::move(key), std::move(loaded)).first->second; + return m_source_vendor_bundles.emplace(std::move(key), std::move(bundle)).first->second.get(); } bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type, diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index a0fceb332b..88455fabf3 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -654,17 +654,15 @@ private: bool m_preserve_vendor_source_paths { false }; // Vendor trees loaded by resolve_preset_config's manifest path, so every preset - // resolved through this bundle shares one load per source root and vendor. - struct SourceManifestBundles { - std::unique_ptr library; - std::unique_ptr vendor; - }; - std::map, SourceManifestBundles> m_source_manifest_bundles; + // resolved through this bundle shares one load per source root and vendor. The + // filament library is one such tree, shared by every vendor under its root. + std::map, std::unique_ptr> + m_source_vendor_bundles; - const SourceManifestBundles *load_source_manifest(const boost::filesystem::path &root_dir, - const std::string &vendor_id, - ForwardCompatibilitySubstitutionRule compatibility_rule, - std::string &error); + const PresetBundle *load_source_vendor(const boost::filesystem::path &root_dir, + const std::string &vendor_id, + ForwardCompatibilitySubstitutionRule compatibility_rule, + std::string &error); // Orca: validation only - flag any printer with two or more compatible // filament presets sharing one filament_id (ambiguous AMS subtype match). diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index 29c38395ac..73d244cf42 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -1118,6 +1118,62 @@ TEST_CASE("Manifest-backed resolution reuses the library base for type-probed fi CHECK_THAT(density(second), Catch::Matchers::WithinAbs(1.27, 1e-6)); } +TEST_CASE("Manifest-backed resolution shares the library between vendors under one root", "[Preset][Bundle][Regression]") +{ + ScopedTemporaryDir dir; + const fs::path library_dir = dir.path() / PresetBundle::ORCA_FILAMENT_LIBRARY / "filament"; + + std::ofstream((dir.path() / (std::string(PresetBundle::ORCA_FILAMENT_LIBRARY) + ".json")).string()) + << R"({"version":"1.0.0","name":"OrcaFilamentLibrary","filament_list":[)" + << R"({"name":"fdm_filament_pet","sub_path":"filament/pet.json","filament_id":"GFL99"},)" + << R"({"name":"Generic PETG","sub_path":"filament/generic_petg.json","filament_id":"GFL98"}]})"; + fs::create_directories(library_dir); + auto write_library_pet = [&](double density) { + std::ofstream((library_dir / "pet.json").string()) + << R"({"type":"filament","name":"fdm_filament_pet","from":"system",)" + << R"("filament_id":"GFL99","instantiation":"false",)" + << R"("filament_type":["PETG"],"filament_density":[")" << density << R"("]})"; + }; + write_library_pet(1.27); + std::ofstream((library_dir / "generic_petg.json").string()) + << R"({"type":"filament","name":"Generic PETG","from":"system",)" + << R"("filament_id":"GFL98","instantiation":"true","inherits":"fdm_filament_pet"})"; + + auto write_vendor = [&](const std::string &vendor, const std::string &filament_id) { + const fs::path filament_dir = dir.path() / vendor / "filament"; + fs::create_directories(filament_dir); + std::ofstream((dir.path() / (vendor + ".json")).string()) + << R"({"version":"1.0.0","name":")" << vendor << R"(","filament_list":[)" + << R"({"name":")" << vendor << R"( PETG","sub_path":"filament/petg.json","filament_id":")" << filament_id << R"("}]})"; + std::ofstream((filament_dir / "petg.json").string()) + << R"({"type":"filament","name":")" << vendor << R"( PETG","from":"system",)" + << R"("filament_id":")" << filament_id << R"(","instantiation":"true","inherits":"fdm_filament_pet"})"; + return filament_dir / "petg.json"; + }; + const fs::path acme_petg = write_vendor("Acme", "GFA00"); + const fs::path beta_petg = write_vendor("Beta", "GFB00"); + + auto density = [&](PresetBundle &bundle, const fs::path &file) { + DynamicPrintConfig raw; + raw.option(BBL_JSON_KEY_INHERITS, true)->value = "fdm_filament_pet"; + std::string error; + REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_FILAMENT, file.string(), + ForwardCompatibilitySubstitutionRule::EnableSilent, error)); + return raw.option("filament_density")->values.front(); + }; + + PresetBundle bundle; + CHECK_THAT(density(bundle, acme_petg), Catch::Matchers::WithinAbs(1.27, 1e-6)); + + // Only a reload would see this change. + write_library_pet(1.5); + CHECK_THAT(density(bundle, beta_petg), Catch::Matchers::WithinAbs(1.27, 1e-6)); + CHECK_THAT(density(bundle, library_dir / "generic_petg.json"), Catch::Matchers::WithinAbs(1.27, 1e-6)); + + PresetBundle fresh; + CHECK_THAT(density(fresh, beta_petg), Catch::Matchers::WithinAbs(1.5, 1e-6)); +} + // Orca: a filament in the Orca Filament Library that names its compatible printers has to hide the generic // library filament sharing its alias, the same way a vendor owned filament does. Otherwise both are compatible // with that printer and the plater combo box lists the shared alias twice.