Compare commits

...
Author SHA1 Message Date
Hanif Koh 64460d7f85 Let the CLI Resolve Presets on Installs That Ship Preset Caches Only
Release builds install each vendor as its preset cache alone. The
read-only preset load the CLI uses to resolve an inheriting user preset
passed allow_cache = false to keep caches from being written, which
also stopped them from being read, so every vendor fell back to JSONs
that are not installed and the CLI failed.

The flag now only gates writing: a read-only load reads caches and
writes none. The filament library is also read from its cache whenever
that is all that is installed, so a vendor updated over the air still
resolves against it.
2026-10-01 22:12:18 +08:00
3 changed files with 140 additions and 11 deletions
+12 -10
View File
@@ -588,13 +588,15 @@ const PresetBundle *PresetBundle::load_source_vendor(const boost::filesystem::pa
return it->second.get(); return it->second.get();
// The library loads with no base of its own, so the tree a vendor inherits from // 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. // is the same one that resolves the library's own presets. It is only a base, so
const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY); // it comes from its cache whenever that is all that is installed, even when the
const PresetBundle *library = nullptr; // vendor itself is parsed (a vendor updated over the air).
if (vendor_id != ORCA_FILAMENT_LIBRARY && const std::string library_file = std::string(ORCA_FILAMENT_LIBRARY);
(boost::filesystem::is_regular_file(root_dir / (library_file + ".json")) || const bool library_json = boost::filesystem::is_regular_file(root_dir / (library_file + ".json"));
(allow_cache && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"))))) { const bool library_cache_only = !library_json && boost::filesystem::is_regular_file(root_dir / (library_file + ".opc"));
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache); const PresetBundle *library = nullptr;
if (vendor_id != ORCA_FILAMENT_LIBRARY && (library_json || library_cache_only)) {
library = load_source_vendor(root_dir, ORCA_FILAMENT_LIBRARY, compatibility_rule, error, allow_cache || library_cache_only);
if (library == nullptr) { if (library == nullptr) {
error = "OrcaFilamentLibrary contains invalid presets"; error = "OrcaFilamentLibrary contains invalid presets";
return nullptr; return nullptr;
@@ -2527,7 +2529,7 @@ void PresetBundle::clear_printer_hold_aliases()
//BBS: add json related logic, load system presets from json //BBS: add json related logic, load system presets from json
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json( std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache) ForwardCompatibilitySubstitutionRule compatibility_rule, bool write_caches)
{ {
//BBS: add config related logs //BBS: add config related logs
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, compatibility_rule %1%")%compatibility_rule; BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, compatibility_rule %1%")%compatibility_rule;
@@ -2547,14 +2549,14 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
// The vendors below are loaded whole and against each other — the filament // The vendors below are loaded whole and against each other — the filament
// library first, then every other vendor with it as the base — so each parse // library first, then every other vendor with it as the base — so each parse
// is complete enough to be worth caching. // is complete enough to be worth caching.
m_generate_vendor_caches = allow_cache && (m_generate_vendor_caches || !validation_mode); m_generate_vendor_caches = write_caches && (m_generate_vendor_caches || !validation_mode);
// Sorted, so any duplicate-preset warning comes out in the same order on every run. // Sorted, so any duplicate-preset warning comes out in the same order on every run.
std::vector<VendorSource> vendors; std::vector<VendorSource> vendors;
for (const std::string& name : vendor_names_in(dir)) for (const std::string& name : vendor_names_in(dir))
if (name == ORCA_FILAMENT_LIBRARY || !(validation_mode && !vendor_to_validate.empty() && name != vendor_to_validate)) if (name == ORCA_FILAMENT_LIBRARY || !(validation_mode && !vendor_to_validate.empty() && name != vendor_to_validate))
vendors.push_back({ name, dir }); vendors.push_back({ name, dir });
auto result = this->load_vendors(vendors, compatibility_rule, allow_cache); auto result = this->load_vendors(vendors, compatibility_rule, true);
this->update_system_maps(); this->update_system_maps();
+3 -1
View File
@@ -839,7 +839,9 @@ private:
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule); //std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
//BBS: add json related logic //BBS: add json related logic
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache = true); // Reads each vendor from its preset cache where one covers the profile, as every load does.
// write_caches = false keeps a read-only load from writing caches into the data directory.
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule, bool write_caches = true);
// Update the multicolor information for filaments. // Update the multicolor information for filaments.
void update_filament_multi_color(); void update_filament_multi_color();
// Update renamed_from and alias maps of system profiles. // Update renamed_from and alias maps of system profiles.
@@ -5788,6 +5788,131 @@ TEST_CASE("A system preset no vendor lists is not resolved", "[Preset][Bundle]")
namespace { namespace {
// Writes each vendor's preset cache into dir, then deletes its profile JSONs: what a release build installs.
void reduce_vendors_to_caches(const fs::path &dir, const std::vector<std::string> &vendor_ids)
{
const std::string lib(PresetBundle::ORCA_FILAMENT_LIBRARY);
PresetBundle library;
if (fs::exists(dir / (lib + ".json"))) {
library.set_generate_vendor_caches(true);
library.load_vendor_configs_from_json(dir.string(), lib, PresetBundle::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent);
}
for (const std::string &vendor_id : vendor_ids) {
if (vendor_id == lib)
continue;
PresetBundle writer;
writer.set_generate_vendor_caches(true);
writer.load_vendor_configs_from_json(dir.string(), vendor_id, PresetBundle::LoadSystem,
ForwardCompatibilitySubstitutionRule::EnableSilent, &library);
}
for (const std::string &vendor_id : vendor_ids) {
REQUIRE(fs::exists(dir / (vendor_id + ".opc")));
fs::remove(dir / (vendor_id + ".json"));
fs::remove_all(dir / vendor_id);
}
}
// The filament library with one abstract base filament, and an "Acme" vendor whose one filament inherits it.
void write_library_and_acme_filament(const fs::path &root)
{
const std::string lib(PresetBundle::ORCA_FILAMENT_LIBRARY);
fs::create_directories(root / lib / "filament");
std::ofstream((root / (lib + ".json")).string())
<< R"({"version":"1.0.0","name":")" << lib << R"(",)"
<< R"("filament_list":[{"name":"Generic PLA","sub_path":"filament/generic_pla.json"}]})";
std::ofstream((root / lib / "filament" / "generic_pla.json").string())
<< R"({"type":"filament","name":"Generic PLA","from":"system","instantiation":"false","filament_id":"GFL99","filament_cost":"27"})";
fs::create_directories(root / "Acme" / "filament");
std::ofstream((root / "Acme.json").string())
<< R"({"version":"1.0.0","name":"Acme","filament_list":[{"name":"Acme PLA","sub_path":"filament/pla.json"}]})";
std::ofstream((root / "Acme" / "filament" / "pla.json").string())
<< R"({"type":"filament","name":"Acme PLA","from":"system","instantiation":"true","inherits":"Generic PLA"})";
}
} // namespace
TEST_CASE("A read-only load resolves a user preset against vendors installed as their cache alone", "[Preset][Bundle][Regression]")
{
ScopedTemporaryDir temp_dir;
const fs::path data = temp_dir.path() / "data";
const fs::path system = data / PRESET_SYSTEM_DIR;
ScopedDataDir scoped_data(data);
ScopedResourcesDir scoped_resources(temp_dir.path() / "resources");
write_acme_printer_vendor(system, 33.);
reduce_vendors_to_caches(system, {"Acme"});
fs::create_directories(data / PRESET_USER_DIR / DEFAULT_USER_FOLDER_NAME / PRESET_PRINTER_NAME);
std::ofstream((data / PRESET_USER_DIR / DEFAULT_USER_FOLDER_NAME / PRESET_PRINTER_NAME / "My Acme.json").string())
<< R"({"type":"machine","name":"My Acme","from":"User","version":"2.3.0.0","inherits":"Acme Printer","printable_height":"123"})";
AppConfig app_config;
PresetBundle bundle;
std::string errors;
bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, PresetBundle::PresetPreferences(),
&errors, true);
CHECK(errors.empty());
const Preset *preset = bundle.printers.find_preset("My Acme");
REQUIRE(preset != nullptr);
CHECK_THAT(preset->config.opt_float("printable_height"), Catch::Matchers::WithinAbs(123., 1e-6));
CHECK_THAT(preset->config.opt_float("extruder_clearance_dist_to_rod"), Catch::Matchers::WithinAbs(33., 1e-6));
}
TEST_CASE("A read-only load writes no preset cache", "[Preset][Bundle][Regression]")
{
ScopedTemporaryDir temp_dir;
const fs::path system = temp_dir.path() / "data" / PRESET_SYSTEM_DIR;
ScopedDataDir scoped_data(temp_dir.path() / "data");
ScopedResourcesDir scoped_resources(temp_dir.path() / "resources");
write_acme_printer_vendor(system, 33.);
AppConfig app_config;
PresetBundle bundle;
std::string errors;
bundle.load_presets(app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, PresetBundle::PresetPreferences(),
&errors, true);
CHECK(errors.empty());
CHECK(bundle.printers.find_preset("Acme Printer") != nullptr);
CHECK_FALSE(fs::exists(system / "Acme.opc"));
}
TEST_CASE("A vendor updated over the air resolves against the library installed as its cache alone", "[Preset][Bundle][Regression]")
{
ScopedTemporaryDir temp_dir;
const fs::path system = temp_dir.path() / "data" / PRESET_SYSTEM_DIR;
ScopedDataDir scoped_data(temp_dir.path() / "data");
ScopedResourcesDir scoped_resources(temp_dir.path() / "resources");
// System presets are found by name through the bundled profiles.
write_library_and_acme_filament(temp_dir.path() / "resources" / PRESET_PROFILES_DIR);
// The release install, then an update that brings Acme back as JSONs while the library stays a cache.
write_library_and_acme_filament(system);
reduce_vendors_to_caches(system, {PresetBundle::ORCA_FILAMENT_LIBRARY, "Acme"});
write_library_and_acme_filament(temp_dir.path() / "update");
fs::copy_file(temp_dir.path() / "update" / "Acme.json", system / "Acme.json");
fs::create_directories(system / "Acme" / "filament");
fs::copy_file(temp_dir.path() / "update" / "Acme" / "filament" / "pla.json", system / "Acme" / "filament" / "pla.json");
SECTION("by name") {
PresetBundle bundle;
DynamicPrintConfig config;
std::string error;
REQUIRE(bundle.resolve_system_preset(config, Preset::TYPE_FILAMENT, "Acme PLA",
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_THAT(config.opt<ConfigOptionFloats>("filament_cost")->values.front(), Catch::Matchers::WithinAbs(27., 1e-6));
}
SECTION("by its source file") {
PresetBundle bundle;
DynamicPrintConfig config;
config.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "Generic PLA";
std::string error;
REQUIRE(bundle.resolve_preset_config(config, Preset::TYPE_FILAMENT, (system / "Acme" / "filament" / "pla.json").string(),
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
CHECK_THAT(config.opt<ConfigOptionFloats>("filament_cost")->values.front(), Catch::Matchers::WithinAbs(27., 1e-6));
}
}
namespace {
// A default preset config for type, built the way PresetBundle builds its default presets. // A default preset config for type, built the way PresetBundle builds its default presets.
DynamicPrintConfig external_default_config(Preset::Type type) DynamicPrintConfig external_default_config(Preset::Type type)
{ {