mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-16 13:32:44 +00:00
Load Each Vendor Tree Once When the CLI Resolves System Presets (#15693)
# Description <!-- > Please provide a summary of the changes made in this PR. Include details such as: > * What issue does this PR address or fix? > * What new features or enhancements does this PR introduce? > * Are there any breaking changes or dependencies that need to be considered? --> Since #15438, every CLI run that loads a system preset spends about a second per preset file re-parsing that vendor's entire profile tree. A slice with a machine, process and filament preset got roughly 2.5 s slower, and a four-filament slice roughly 4 s slower. This PR loads each vendor tree once per run instead. Resolved presets and G-code are unchanged. The GUI never takes this path, and no release contains #15438, so the regression only affects CLI runs on current dev and nightly builds. That includes print farms, slicing services and plugins that call `orca-slicer --slice`, and CI suites. ## Changes ### Why it was slow `PresetBundle::resolve_preset_config` resolves a system preset through its vendor manifest by loading the whole OrcaFilamentLibrary bundle and the whole vendor tree from JSON, then picking the one preset out. The CLI did that separately for every `--load-settings` and `--load-filaments` file, on a fresh `PresetBundle` each time. With BBL presets, a machine + process + filament run opened `BBL.json` three times and read BBL's 2,879 profile files and the library's 512 three times over. ### Load each vendor tree once - `PresetBundle` keeps every vendor bundle its manifest path loads, keyed by source root, vendor and substitution rule, and reuses them for later resolutions on the same bundle. - OrcaFilamentLibrary is cached the same way, so vendors under one root share a single library load and the library's own presets resolve from that same instance. A vendor bundle only reads from its base while loading, so sharing it is safe. - A failed or throwing load is not kept, so error reporting is unchanged. - The key includes the source root, so presets from two different profile roots still resolve separately. - The CLI resolves every system preset through one `PresetBundle` for the whole run, instead of creating one per file. The resolved configurations still come from the same canonical vendor loader, so what a preset resolves to does not change. Only the CLI calls `resolve_preset_config`, so a long-lived GUI bundle cannot end up holding profile trees that later change on disk. # Screenshots/Recordings/Graphs <!-- > Please attach relevant screenshots to showcase the UI changes. > Please attach images that can help explain the changes. --> CLI slice of a 20 mm cube with X1 Carbon system presets. Both builds get the same datadir, best of 3, Linux. "Before" is this PR's base from CI. | System presets loaded | Before | After | |---|---|---| | machine | 0.95 s | 0.87 s | | machine + process | 1.67 s | 0.92 s | | machine + process + 1 filament | 2.51 s | 0.97 s | | machine + process + 4 filaments | 5.00 s | 0.99 s | Files opened during the machine + process + filament run (`strace -e openat`): | | Before | After | |---|---|---| | `BBL.json` | 3 | 1 | | `OrcaFilamentLibrary.json` | 3 | 1 | | `system/BBL/**/*.json` | 8,634 | 2,880 | | `system/OrcaFilamentLibrary/**/*.json` | 1,536 | 512 | Peak memory did not rise: max RSS 306 MB → 286 MB for the three-preset run, and 305 MB → 285 MB for four filaments. The "before" figure is an AppImage, so part of that gap is probably packaging. ## Tests <!-- > Please describe the tests that you have conducted to verify the changes made in this PR. --> - New test "Manifest-backed resolution reuses the vendor tree it already loaded" in `tests/libslic3r/test_preset_bundle_loading.cpp`. It resolves one preset, changes the parent profile on disk, then resolves a sibling. The same bundle returns the value it already loaded, and a fresh bundle picks up the change. - New test "Manifest-backed resolution shares the library between vendors under one root". It resolves through one vendor, changes a library profile on disk, then resolves through a second vendor and a library preset on the same bundle. Both return the value already loaded, and a fresh bundle picks up the change. - All `[Preset][Bundle]` tests pass (87 test cases, 1,069 assertions), including the existing manifest-backed resolution cases for source-root scoping, malformed vendor loads, missing parents and type mismatches. - G-code of the three-preset slice is identical before and after, header lines excluded. - The external CLI regression suite passes. Two cases report as unexpectedly passing because #15639 fixed their bug. They pass the same way on this PR's base without the change. - A GUI-vs-CLI parity run over 10 fixtures shows no new differences. - Builds clean on Linux (Release, with tests). <!-- > A guide for users on how to download the artifacts from this PR. --> [How to Download Pull Requests Artifacts for Testing](https://www.orcaslicer.com/wiki/how_to_download_pr_artifacts)
This commit is contained in:
+6
-4
@@ -2029,19 +2029,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<PresetBundle> 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,
|
std::string &config_type, const std::string &config_from,
|
||||||
bool probe_type, std::string &error) {
|
bool probe_type, std::string &error) {
|
||||||
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
const auto *inherits = config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
||||||
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
std::unique_ptr<PresetBundle> source_bundle;
|
|
||||||
PresetBundle *bundle = nullptr;
|
PresetBundle *bundle = nullptr;
|
||||||
bool allow_source_manifest = false;
|
bool allow_source_manifest = false;
|
||||||
if (config_from == "system") {
|
if (config_from == "system") {
|
||||||
source_bundle = std::make_unique<PresetBundle>();
|
if (!system_preset_resolver)
|
||||||
bundle = source_bundle.get();
|
system_preset_resolver = std::make_unique<PresetBundle>();
|
||||||
|
bundle = system_preset_resolver.get();
|
||||||
allow_source_manifest = true;
|
allow_source_manifest = true;
|
||||||
} else {
|
} else {
|
||||||
bundle = ensure_cli_preset_bundle(error);
|
bundle = ensure_cli_preset_bundle(error);
|
||||||
|
|||||||
@@ -484,7 +484,7 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
|
|||||||
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
|
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
|
||||||
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
|
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) {
|
switch (preset_type) {
|
||||||
case Preset::TYPE_PRINT: return &bundle.prints;
|
case Preset::TYPE_PRINT: return &bundle.prints;
|
||||||
case Preset::TYPE_FILAMENT: return &bundle.filaments;
|
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) {
|
if (collection == nullptr) {
|
||||||
error = "Unsupported preset type";
|
error = "Unsupported preset type";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const boost::filesystem::path source_path = boost::filesystem::absolute(source_file).lexically_normal();
|
const boost::filesystem::path source_path = boost::filesystem::absolute(source_file).lexically_normal();
|
||||||
auto find_loaded = [&](PresetBundle &bundle) -> const Preset * {
|
auto find_loaded = [&](const PresetBundle &bundle) -> const Preset * {
|
||||||
PresetCollection *loaded_collection = collection_for_type(bundle, type);
|
const PresetCollection *loaded_collection = collection_for_type(bundle, type);
|
||||||
const Preset *resolved = nullptr;
|
const Preset *resolved = nullptr;
|
||||||
for (const Preset &preset : loaded_collection->get_presets()) {
|
for (const Preset &preset : loaded_collection->get_presets()) {
|
||||||
if (preset.file.empty())
|
if (preset.file.empty())
|
||||||
@@ -549,30 +549,11 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
PresetBundle library_bundle;
|
const PresetBundle *loaded = load_source_vendor(root_dir, vendor_id, compatibility_rule, error);
|
||||||
const PresetBundle *base_bundle = nullptr;
|
if (loaded == 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";
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
const Preset *resolved = find_loaded(source_bundle);
|
const Preset *resolved = find_loaded(*loaded);
|
||||||
if (resolved == nullptr) {
|
if (resolved == nullptr) {
|
||||||
if (error.empty())
|
if (error.empty())
|
||||||
error = "Source file is not an instantiated preset in its vendor manifest";
|
error = "Source file is not an instantiated preset in its vendor manifest";
|
||||||
@@ -591,6 +572,37 @@ bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Typ
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, compatibility_rule);
|
||||||
|
if (auto it = m_source_vendor_bundles.find(key); it != m_source_vendor_bundles.end())
|
||||||
|
return it->second.get();
|
||||||
|
|
||||||
|
// 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"))) {
|
||||||
|
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error);
|
||||||
|
if (library == nullptr) {
|
||||||
|
error = "OrcaFilamentLibrary contains invalid presets";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bundle = std::make_unique<PresetBundle>();
|
||||||
|
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_vendor_bundles.emplace(std::move(key), std::move(bundle)).first->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type,
|
bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type,
|
||||||
const std::string &source_file,
|
const std::string &source_file,
|
||||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <shared_mutex>
|
#include <shared_mutex>
|
||||||
|
#include <tuple>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -652,6 +653,17 @@ private:
|
|||||||
bool m_generate_vendor_caches { false };
|
bool m_generate_vendor_caches { false };
|
||||||
bool m_preserve_vendor_source_paths { 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. The
|
||||||
|
// filament library is one such tree, shared by every vendor under its root.
|
||||||
|
std::map<std::tuple<std::string, std::string, ForwardCompatibilitySubstitutionRule>, std::unique_ptr<PresetBundle>>
|
||||||
|
m_source_vendor_bundles;
|
||||||
|
|
||||||
|
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
|
// Orca: validation only - flag any printer with two or more compatible
|
||||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||||
bool check_duplicate_filament_subtypes() const;
|
bool check_duplicate_filament_subtypes() const;
|
||||||
|
|||||||
@@ -987,6 +987,193 @@ TEST_CASE("Resolution terminates when no vendor manifest exists", "[Preset][Bund
|
|||||||
CHECK(error == "Preset was not found in the loaded bundle");
|
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<ConfigOptionString>(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<ConfigOptionFloats>("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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ConfigOptionString>(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<ConfigOptionFloats>("filament_density")->values.front();
|
||||||
|
};
|
||||||
|
|
||||||
|
PresetBundle bundle;
|
||||||
|
DynamicPrintConfig first;
|
||||||
|
first.option<ConfigOptionString>(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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<ConfigOptionString>(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<ConfigOptionFloats>("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
|
// 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
|
// 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.
|
// with that printer and the plater combo box lists the shared alias twice.
|
||||||
|
|||||||
Reference in New Issue
Block a user