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:
HanifKoh
2026-09-15 15:01:24 +08:00
committed by GitHub
4 changed files with 243 additions and 30 deletions
@@ -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");
}
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
// 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.