mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-08 17:57:44 +00:00
fix(cli): resolve inherited presets through vendor manifests (#15438)
* fix(cli): resolve inherited presets through vendor manifests * fix(cli): resolve typeless inherited presets Probe the configured preset collections when a preset JSON omits its type. Reject missing, cross-type, and duplicate identities instead of silently selecting a candidate. * fix(cli): allow missing app config during preset resolution * fix(cli): tolerate malformed app config during preset resolution
This commit is contained in:
committed by
GitHub
parent
c61d2fe5d1
commit
bcb4f17d9a
@@ -1974,7 +1974,79 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
}
|
||||
|
||||
auto load_config_file = [](const std::string& file, DynamicPrintConfig& config, std::string& config_type,
|
||||
std::unique_ptr<PresetBundle> cli_preset_bundle;
|
||||
auto ensure_cli_preset_bundle = [&cli_preset_bundle, config_substitution_rule](std::string &error) -> PresetBundle * {
|
||||
if (cli_preset_bundle)
|
||||
return cli_preset_bundle.get();
|
||||
try {
|
||||
AppConfig app_config;
|
||||
const std::string app_config_error = app_config.load_if_exists();
|
||||
if (!app_config_error.empty()) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Ignoring invalid app config during CLI preset resolution: " << app_config_error;
|
||||
app_config.reset();
|
||||
}
|
||||
|
||||
auto bundle = std::make_unique<PresetBundle>();
|
||||
std::string load_error;
|
||||
bundle->load_presets(app_config, config_substitution_rule,
|
||||
PresetBundle::PresetPreferences(), &load_error, true);
|
||||
if (!load_error.empty()) {
|
||||
error = "Failed to load presets for inheritance resolution: " + load_error;
|
||||
return nullptr;
|
||||
}
|
||||
cli_preset_bundle = std::move(bundle);
|
||||
return cli_preset_bundle.get();
|
||||
} catch (const std::exception &ex) {
|
||||
error = ex.what();
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
auto resolve_preset = [&ensure_cli_preset_bundle, config_substitution_rule](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<ConfigOptionString>(BBL_JSON_KEY_INHERITS);
|
||||
if (!probe_type && (inherits == nullptr || inherits->value.empty()))
|
||||
return true;
|
||||
|
||||
std::unique_ptr<PresetBundle> source_bundle;
|
||||
PresetBundle *bundle = nullptr;
|
||||
bool allow_source_manifest = false;
|
||||
if (config_from == "system") {
|
||||
source_bundle = std::make_unique<PresetBundle>();
|
||||
bundle = source_bundle.get();
|
||||
allow_source_manifest = true;
|
||||
} else {
|
||||
bundle = ensure_cli_preset_bundle(error);
|
||||
if (bundle == nullptr)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (probe_type) {
|
||||
Preset::Type preset_type;
|
||||
if (!bundle->resolve_preset_config_type(config, preset_type, file, config_substitution_rule,
|
||||
error, allow_source_manifest))
|
||||
return false;
|
||||
config_type = Preset::get_type_string(preset_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
Preset::Type preset_type;
|
||||
if (config_type == "process")
|
||||
preset_type = Preset::TYPE_PRINT;
|
||||
else if (config_type == "filament")
|
||||
preset_type = Preset::TYPE_FILAMENT;
|
||||
else if (config_type == "machine")
|
||||
preset_type = Preset::TYPE_PRINTER;
|
||||
else {
|
||||
error = "Unsupported preset type: " + config_type;
|
||||
return false;
|
||||
}
|
||||
return bundle->resolve_preset_config(config, preset_type, file, config_substitution_rule,
|
||||
error, allow_source_manifest);
|
||||
};
|
||||
|
||||
auto load_config_file = [config_substitution_rule, &resolve_preset](const std::string& file, DynamicPrintConfig& config, std::string& config_type,
|
||||
std::string& config_name, std::string& filament_id, std::string& config_from) {
|
||||
if (! boost::filesystem::exists(file)) {
|
||||
boost::nowide::cerr << __FUNCTION__<< ": can not find setting file: " << file << std::endl;
|
||||
@@ -2003,9 +2075,15 @@ int CLI::run(int argc, char **argv)
|
||||
}
|
||||
|
||||
auto type_iter = key_values.find(BBL_JSON_KEY_TYPE);
|
||||
if (type_iter != key_values.end()) {
|
||||
const bool probe_type = type_iter == key_values.end();
|
||||
if (!probe_type)
|
||||
config_type = type_iter->second;
|
||||
|
||||
if (!resolve_preset(file, config, config_type, config_from, probe_type, reason)) {
|
||||
boost::nowide::cerr << __FUNCTION__ << boost::format(": can not resolve preset %1%: %2%") % file % reason << std::endl;
|
||||
return CLI_CONFIG_FILE_ERROR;
|
||||
}
|
||||
|
||||
if (config_type == "machine") {
|
||||
//config.set("printer_settings_id", config_name, true);
|
||||
//printer_inherits = config.option<ConfigOptionString>("inherits", true)->value;
|
||||
|
||||
@@ -1823,4 +1823,9 @@ bool AppConfig::exists()
|
||||
return boost::filesystem::exists(config_path());
|
||||
}
|
||||
|
||||
std::string AppConfig::load_if_exists()
|
||||
{
|
||||
return boost::filesystem::exists(loading_path()) ? load() : std::string();
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
@@ -113,8 +113,10 @@ public:
|
||||
void set_defaults();
|
||||
|
||||
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
|
||||
// return error string or empty strinf
|
||||
// Return an error string, or an empty string on success.
|
||||
std::string load();
|
||||
// Treat a missing config as default state; otherwise load it normally.
|
||||
std::string load_if_exists();
|
||||
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
|
||||
void save();
|
||||
|
||||
|
||||
@@ -1653,7 +1653,7 @@ std::string PresetCollection::canonical_preset_name(const std::string &name, con
|
||||
void PresetCollection::load_presets(
|
||||
const std::string &dir_path, const std::string &subdir,
|
||||
PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
std::function<void(Preset&)> preset_loaded_fn, const PresetOrigin &load_origin)
|
||||
std::function<void(Preset&)> preset_loaded_fn, const PresetOrigin &load_origin, bool read_only)
|
||||
{
|
||||
// Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points,
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/732
|
||||
@@ -1662,7 +1662,7 @@ void PresetCollection::load_presets(
|
||||
|
||||
// Load custom roots first
|
||||
if (fs::exists(dir / "base")) {
|
||||
load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin);
|
||||
load_presets(dir.string(), "base", substitutions, substitution_rule, nullptr, resolved_origin, read_only);
|
||||
}
|
||||
|
||||
//BBS: add config related logs
|
||||
@@ -1670,7 +1670,8 @@ void PresetCollection::load_presets(
|
||||
//BBS do not parse folder if not exists
|
||||
m_dir_path = dir.string();
|
||||
if (!fs::exists(dir)) {
|
||||
fs::create_directory(dir);
|
||||
if (!read_only)
|
||||
fs::create_directory(dir);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1720,10 +1721,10 @@ void PresetCollection::load_presets(
|
||||
substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) });
|
||||
if (!reason.empty()) {
|
||||
fs::path file_path(preset.file);
|
||||
if (fs::exists(file_path))
|
||||
if (!read_only && fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
file_path.replace_extension(".info");
|
||||
if (fs::exists(file_path))
|
||||
if (!read_only && fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("parse config %1% failed")%preset.file;
|
||||
++m_errors;
|
||||
@@ -1794,7 +1795,8 @@ void PresetCollection::load_presets(
|
||||
size_t at_pos = name.find('@');
|
||||
if (at_pos != std::string::npos && at_pos + 1 < name.length()) {
|
||||
compatible_printers->values.push_back(name.substr(at_pos + 1));
|
||||
preset.save(nullptr);
|
||||
if (!read_only)
|
||||
preset.save(nullptr);
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " added compatible_printers for preset: " << name;
|
||||
}
|
||||
}
|
||||
@@ -1812,10 +1814,10 @@ void PresetCollection::load_presets(
|
||||
++m_errors;
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("The user-config cannot be loaded: %1%. Reason: %2%")%preset.file %err.what();
|
||||
fs::path file_path(preset.file);
|
||||
if (fs::exists(file_path))
|
||||
if (!read_only && fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
file_path.replace_extension(".info");
|
||||
if (fs::exists(file_path))
|
||||
if (!read_only && fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
//throw Slic3r::RuntimeError(std::string("The selected preset cannot be loaded: ") + preset.file + "\n\tReason: " + err.what());
|
||||
} catch (const std::runtime_error &err) {
|
||||
@@ -1823,10 +1825,10 @@ void PresetCollection::load_presets(
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Failed loading the user-config file: %1%. Reason: %2%")%preset.file %err.what();
|
||||
//throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + preset.file + "\n\tReason: " + err.what());
|
||||
fs::path file_path(preset.file);
|
||||
if (fs::exists(file_path))
|
||||
if (!read_only && fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
file_path.replace_extension(".info");
|
||||
if (fs::exists(file_path))
|
||||
if (!read_only && fs::exists(file_path))
|
||||
fs::remove(file_path);
|
||||
}
|
||||
|
||||
|
||||
@@ -558,7 +558,7 @@ public:
|
||||
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, std::function<void(Preset&)> preset_loaded_fn = nullptr, const PresetOrigin &load_origin = PresetOrigin());
|
||||
void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, std::function<void(Preset&)> preset_loaded_fn = nullptr, const PresetOrigin &load_origin = PresetOrigin(), bool read_only = false);
|
||||
|
||||
//BBS: update user presets directory
|
||||
void update_user_presets_directory(const std::string& dir_path, const std::string& type);
|
||||
|
||||
@@ -453,6 +453,158 @@ PresetBundle::PresetBundle()
|
||||
this->project_config.apply_only(FullPrintConfig::defaults(), s_project_options);
|
||||
}
|
||||
|
||||
bool PresetBundle::resolve_preset_config(DynamicPrintConfig &config, Preset::Type type,
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest)
|
||||
{
|
||||
if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
|
||||
compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
|
||||
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
|
||||
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
|
||||
|
||||
auto collection_for_type = [](PresetBundle &bundle, Preset::Type preset_type) -> PresetCollection * {
|
||||
switch (preset_type) {
|
||||
case Preset::TYPE_PRINT: return &bundle.prints;
|
||||
case Preset::TYPE_FILAMENT: return &bundle.filaments;
|
||||
case Preset::TYPE_PRINTER: return &bundle.printers;
|
||||
default: return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
const Preset *resolved = nullptr;
|
||||
for (const Preset &preset : loaded_collection->get_presets()) {
|
||||
if (preset.file.empty())
|
||||
continue;
|
||||
|
||||
boost::system::error_code ec;
|
||||
const bool same_file = boost::filesystem::equivalent(source_path, boost::filesystem::path(preset.file), ec);
|
||||
if (ec || !same_file)
|
||||
continue;
|
||||
if (resolved != nullptr) {
|
||||
error = "Preset identity is ambiguous";
|
||||
return nullptr;
|
||||
}
|
||||
resolved = &preset;
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
|
||||
if (const Preset *resolved = find_loaded(*this)) {
|
||||
config = resolved->config;
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
if (error == "Preset identity is ambiguous")
|
||||
return false;
|
||||
if (!allow_source_manifest) {
|
||||
error = "Preset was not found in the loaded bundle";
|
||||
return false;
|
||||
}
|
||||
|
||||
// A manifest-backed source file can be resolved without requiring the vendor
|
||||
// to have been copied into data_dir()/system. Find the nearest ancestor whose
|
||||
// sibling manifest names it, then let the canonical vendor loader flatten the
|
||||
// complete tree (including nested sub_path entries and library inheritance).
|
||||
for (boost::filesystem::path vendor_dir = source_path.parent_path(); !vendor_dir.empty(); vendor_dir = vendor_dir.parent_path()) {
|
||||
const std::string vendor_id = vendor_dir.filename().string();
|
||||
if (vendor_id.empty())
|
||||
continue;
|
||||
const boost::filesystem::path root_dir = vendor_dir.parent_path();
|
||||
const boost::filesystem::path manifest = root_dir / (vendor_id + ".json");
|
||||
if (!boost::filesystem::is_regular_file(manifest))
|
||||
continue;
|
||||
const boost::filesystem::path manifest_relative = source_path.lexically_relative(vendor_dir);
|
||||
if (manifest_relative.empty() || *manifest_relative.begin() == "..")
|
||||
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";
|
||||
return false;
|
||||
}
|
||||
|
||||
const Preset *resolved = find_loaded(source_bundle);
|
||||
if (resolved == nullptr) {
|
||||
if (error.empty())
|
||||
error = "Source file is not an instantiated preset in its vendor manifest";
|
||||
return false;
|
||||
}
|
||||
config = resolved->config;
|
||||
error.clear();
|
||||
return true;
|
||||
} catch (const std::exception &ex) {
|
||||
error = ex.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
error = "Preset was not found in the loaded bundle";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PresetBundle::resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type,
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest)
|
||||
{
|
||||
std::optional<std::pair<Preset::Type, DynamicPrintConfig>> resolved;
|
||||
for (Preset::Type candidate_type : types_list(ptFFF)) {
|
||||
DynamicPrintConfig candidate_config(config);
|
||||
std::string candidate_error;
|
||||
if (!resolve_preset_config(candidate_config, candidate_type, source_file, compatibility_rule,
|
||||
candidate_error, allow_source_manifest)) {
|
||||
if (candidate_error == "Preset identity is ambiguous") {
|
||||
error = std::move(candidate_error);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (resolved) {
|
||||
error = "Preset type is ambiguous";
|
||||
return false;
|
||||
}
|
||||
resolved.emplace(candidate_type, std::move(candidate_config));
|
||||
}
|
||||
|
||||
if (!resolved) {
|
||||
error = "Preset type could not be resolved";
|
||||
return false;
|
||||
}
|
||||
|
||||
type = resolved->first;
|
||||
config = std::move(resolved->second);
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
PresetBundle::PresetBundle(const PresetBundle &rhs)
|
||||
{
|
||||
*this = rhs;
|
||||
@@ -574,7 +726,8 @@ void PresetBundle::copy_files(const std::string& from)
|
||||
}
|
||||
|
||||
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule,
|
||||
const PresetPreferences& preferred_selection/* = PresetPreferences()*/)
|
||||
const PresetPreferences& preferred_selection/* = PresetPreferences()*/,
|
||||
std::string *errors, bool read_only)
|
||||
{
|
||||
// First load the vendor specific system presets.
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
@@ -585,16 +738,20 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
|
||||
const auto startup_t0 = std::chrono::steady_clock::now();
|
||||
|
||||
//BBS: change system config to json
|
||||
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule);
|
||||
std::tie(substitutions, errors_cummulative) = this->load_system_presets_from_json(substitution_rule, !read_only);
|
||||
if (errors != nullptr)
|
||||
*errors = errors_cummulative;
|
||||
|
||||
// BBS load preset from user's folder, load system default if
|
||||
// BBS: change directories by design
|
||||
std::string dir_user_presets = config.get("preset_folder");
|
||||
if (dir_user_presets.empty()) {
|
||||
load_user_presets(DEFAULT_USER_FOLDER_NAME, substitution_rule);
|
||||
load_user_presets(DEFAULT_USER_FOLDER_NAME, substitution_rule, read_only);
|
||||
} else {
|
||||
load_user_presets(dir_user_presets, substitution_rule);
|
||||
load_user_presets(dir_user_presets, substitution_rule, read_only);
|
||||
}
|
||||
if (errors != nullptr && errors->empty() && m_errors != 0)
|
||||
*errors = "Preset loading reported " + std::to_string(m_errors) + " error(s)";
|
||||
|
||||
// Rewrite renamed compatible_printers / compatible_prints references before selection. Skipped
|
||||
// in validation mode so the profile validator (has_errors -> check_preset_references) sees the
|
||||
@@ -1010,18 +1167,26 @@ std::string PresetBundle::get_hotend_model_for_printer_model(std::string model_n
|
||||
return out;
|
||||
}
|
||||
|
||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule substitution_rule)
|
||||
PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule substitution_rule, bool read_only)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << __LINE__ << " entry and user is: " << user;
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::string errors_cummulative;
|
||||
|
||||
fs::path user_folder(data_dir() + "/" + PRESET_USER_DIR);
|
||||
if (!fs::exists(user_folder)) fs::create_directory(user_folder);
|
||||
if (!fs::exists(user_folder)) {
|
||||
if (read_only)
|
||||
return substitutions;
|
||||
fs::create_directory(user_folder);
|
||||
}
|
||||
|
||||
std::string dir_user_presets = data_dir() + "/" + PRESET_USER_DIR + "/" + user;
|
||||
fs::path folder(user_folder / user);
|
||||
if (!fs::exists(folder)) fs::create_directory(folder);
|
||||
if (!fs::exists(folder)) {
|
||||
if (read_only)
|
||||
return substitutions;
|
||||
fs::create_directory(folder);
|
||||
}
|
||||
|
||||
bundles.WriteLock();
|
||||
bundles.m_bundles.clear();
|
||||
@@ -1049,13 +1214,13 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.filament_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.printer_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id));
|
||||
}, PresetOrigin(PresetOrigin::Kind::LocalBundle, metadata.id), read_only);
|
||||
metadata.bundle_type = BundleType::Local;
|
||||
metadata.path = metadata_file.string();
|
||||
|
||||
@@ -1085,13 +1250,13 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
|
||||
this->prints.load_presets(bundle_dir, PRESET_PRINT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.print_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
||||
this->filaments.load_presets(bundle_dir, PRESET_FILAMENT_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.filament_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
||||
this->printers.load_presets(bundle_dir, PRESET_PRINTER_NAME, substitutions, substitution_rule, [&](Preset& preset) {
|
||||
metadata.printer_presets.push_back(preset.name);
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id));
|
||||
}, PresetOrigin(PresetOrigin::Kind::SubscribedBundle, metadata.id), read_only);
|
||||
|
||||
metadata.bundle_type = BundleType::Subscribed;
|
||||
metadata.path = metadata_file.string();
|
||||
@@ -1110,17 +1275,20 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(std::string user, For
|
||||
const auto json_t0 = std::chrono::steady_clock::now();
|
||||
try {
|
||||
std::string sel = prints.get_selected_preset().name;
|
||||
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule);
|
||||
this->prints.load_presets(dir_user_presets, PRESET_PRINT_NAME, substitutions, substitution_rule,
|
||||
nullptr, PresetOrigin(), read_only);
|
||||
prints.select_preset_by_name(sel, false);
|
||||
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||
try {
|
||||
std::string sel = filaments.get_selected_preset().name;
|
||||
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule);
|
||||
this->filaments.load_presets(dir_user_presets, PRESET_FILAMENT_NAME, substitutions, substitution_rule,
|
||||
nullptr, PresetOrigin(), read_only);
|
||||
filaments.select_preset_by_name(sel, false);
|
||||
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||
try {
|
||||
std::string sel = printers.get_selected_preset().name;
|
||||
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule);
|
||||
this->printers.load_presets(dir_user_presets, PRESET_PRINTER_NAME, substitutions, substitution_rule,
|
||||
nullptr, PresetOrigin(), read_only);
|
||||
printers.select_preset_by_name(sel, false);
|
||||
} catch (const std::runtime_error& err) { errors_cummulative += err.what(); }
|
||||
if (!errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative);
|
||||
@@ -2266,7 +2434,8 @@ void PresetBundle::clear_printer_hold_aliases()
|
||||
}
|
||||
|
||||
//BBS: add json related logic, load system presets from json
|
||||
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule)
|
||||
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets_from_json(
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache)
|
||||
{
|
||||
//BBS: add config related logs
|
||||
BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(" enter, compatibility_rule %1%")%compatibility_rule;
|
||||
@@ -2288,7 +2457,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
// 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
|
||||
// is complete enough to be worth caching.
|
||||
m_generate_vendor_caches = m_generate_vendor_caches || ! validation_mode;
|
||||
m_generate_vendor_caches = allow_cache && (m_generate_vendor_caches || !validation_mode);
|
||||
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
std::string errors_cummulative;
|
||||
@@ -2318,7 +2487,8 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
// state into this load.
|
||||
this->clear_printer_hold_aliases();
|
||||
this->m_errors = 0;
|
||||
append(substitutions, this->load_vendor_configs_from_json(dir.string(), orca_lib_vendor, PresetBundle::LoadSystem, compatibility_rule).first);
|
||||
append(substitutions, this->load_vendor_configs_from_json(
|
||||
dir.string(), orca_lib_vendor, PresetBundle::LoadSystem, compatibility_rule, nullptr, allow_cache).first);
|
||||
first = false;
|
||||
} catch (const std::runtime_error &err) {
|
||||
if (validation_mode)
|
||||
@@ -2343,7 +2513,7 @@ std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_pre
|
||||
bundle->set_generate_vendor_caches(m_generate_vendor_caches);
|
||||
try {
|
||||
auto result = bundle->load_vendor_configs_from_json(
|
||||
dir.string(), other_vendors[i], PresetBundle::LoadSystem, compatibility_rule, this);
|
||||
dir.string(), other_vendors[i], PresetBundle::LoadSystem, compatibility_rule, this, allow_cache);
|
||||
parallel_substitutions[i] = std::move(result.first);
|
||||
parallel_bundles[i] = std::move(bundle);
|
||||
} catch (const std::runtime_error &err) {
|
||||
@@ -5280,9 +5450,11 @@ std::string PresetBundle::load_vendor_preset(
|
||||
return reason;
|
||||
}
|
||||
|
||||
auto file_path = (boost::filesystem::path(data_dir()) /PRESET_SYSTEM_DIR/ vendor_name / entry.sub_path).make_preferred();
|
||||
if(validation_mode)
|
||||
auto file_path = (boost::filesystem::path(data_dir()) / PRESET_SYSTEM_DIR / vendor_name / entry.sub_path).make_preferred();
|
||||
if (validation_mode)
|
||||
file_path = (boost::filesystem::path(data_dir()) / vendor_name / entry.sub_path).make_preferred();
|
||||
if (m_preserve_vendor_source_paths)
|
||||
file_path = (boost::filesystem::path(path) / vendor_name / entry.sub_path).make_preferred();
|
||||
|
||||
// Load the preset into the list of presets, save it to disk.
|
||||
Preset &loaded = presets_collection->load_preset(file_path.string(), preset_name, std::move(config), false);
|
||||
@@ -5348,7 +5520,8 @@ std::string PresetBundle::load_vendor_preset(
|
||||
|
||||
//BBS: Load a config bundle file from json
|
||||
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_from_json(
|
||||
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle)
|
||||
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle, bool allow_cache)
|
||||
{
|
||||
// Enable substitutions for user config bundle, throw an exception when loading a system profile.
|
||||
ConfigSubstitutionContext substitution_context { compatibility_rule };
|
||||
@@ -5366,7 +5539,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
// Orca: only a whole-vendor load has a cache — the vendor-only and filament-only
|
||||
// scans want a slice of one. Validation reads the JSONs whatever is cached.
|
||||
const boost::filesystem::path dir_path(dir);
|
||||
const bool cacheable = flags.has(LoadConfigBundleAttribute::LoadSystem) && ! flags.has(LoadConfigBundleAttribute::LoadFilamentOnly);
|
||||
const bool cacheable = allow_cache && flags.has(LoadConfigBundleAttribute::LoadSystem) && ! flags.has(LoadConfigBundleAttribute::LoadFilamentOnly);
|
||||
if (cacheable && ! validation_mode && this->load_vendor_cache(dir_path, vendor_name, base_bundle)) {
|
||||
size_t presets_loaded = 0;
|
||||
for (const PresetCollection* coll : std::initializer_list<const PresetCollection*>{
|
||||
|
||||
@@ -230,7 +230,22 @@ public:
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// select preferred presets, if any exist
|
||||
PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule,
|
||||
const PresetPreferences& preferred_selection = PresetPreferences());
|
||||
const PresetPreferences& preferred_selection = PresetPreferences(),
|
||||
std::string *errors = nullptr, bool read_only = false);
|
||||
|
||||
// Resolve an explicitly named source file through a canonical flattened
|
||||
// preset. Exact loaded-file identity is preferred; otherwise a manifest-
|
||||
// backed vendor tree is loaded from that source root without using caches.
|
||||
bool resolve_preset_config(DynamicPrintConfig &config, Preset::Type type,
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest = true);
|
||||
// Resolve a source file whose JSON omits `type`. Succeeds only when exactly
|
||||
// one FFF preset collection owns the file and returns that collection's type.
|
||||
bool resolve_preset_config_type(DynamicPrintConfig &config, Preset::Type &type,
|
||||
const std::string &source_file,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule,
|
||||
std::string &error, bool allow_source_manifest = true);
|
||||
|
||||
// Load selections (current print, current filaments, current printer) from config.ini
|
||||
// This is done just once on application start up.
|
||||
@@ -238,7 +253,7 @@ public:
|
||||
void load_selections(AppConfig &config, const PresetPreferences& preferred_selection = PresetPreferences());
|
||||
|
||||
// BBS Load user presets
|
||||
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule);
|
||||
PresetsConfigSubstitutions load_user_presets(std::string user, ForwardCompatibilitySubstitutionRule rule, bool read_only = false);
|
||||
PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map<std::string, std::map<std::string, std::string>>& my_presets, ForwardCompatibilitySubstitutionRule rule);
|
||||
// Orca: Import subscribed bundle presets (load and save to disk in one operation), handles one bundle at a time
|
||||
PresetsConfigSubstitutions update_subscribed_presets(AppConfig& config,
|
||||
@@ -481,10 +496,13 @@ public:
|
||||
//Orca: load config bundle from json, pass the base bundle to support cross vendor inheritance
|
||||
// Orca: `dir` is where the vendor is looked for — its own directory, whether or
|
||||
// not the profile JSONs are still there. A whole-vendor load comes from the
|
||||
// vendor's preset cache whenever one covers the profile on disk, and is parsed
|
||||
// from the JSONs in `dir` only when none does. Nothing here reads resources.
|
||||
// vendor's preset cache whenever one covers the profile on disk and allow_cache
|
||||
// is true, and is parsed from the JSONs in `dir` otherwise. Nothing here reads
|
||||
// resources implicitly.
|
||||
std::pair<PresetsConfigSubstitutions, size_t> load_vendor_configs_from_json(
|
||||
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle = nullptr);
|
||||
const std::string &dir, const std::string &vendor_name, LoadConfigBundleAttributes flags,
|
||||
ForwardCompatibilitySubstitutionRule compatibility_rule, const PresetBundle* base_bundle = nullptr,
|
||||
bool allow_cache = true);
|
||||
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
//void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
|
||||
@@ -606,6 +624,7 @@ private:
|
||||
|
||||
// Whether to (re)write a per-vendor cache after a JSON parse.
|
||||
bool m_generate_vendor_caches { false };
|
||||
bool m_preserve_vendor_source_paths { false };
|
||||
|
||||
// Orca: validation only - flag any printer with two or more compatible
|
||||
// filament presets sharing one filament_id (ambiguous AMS subtype match).
|
||||
@@ -613,7 +632,7 @@ private:
|
||||
|
||||
//std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
//BBS: add json related logic
|
||||
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule);
|
||||
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets_from_json(ForwardCompatibilitySubstitutionRule compatibility_rule, bool allow_cache = true);
|
||||
// Update the multicolor information for filaments.
|
||||
void update_filament_multi_color();
|
||||
// Update renamed_from and alias maps of system profiles.
|
||||
|
||||
@@ -554,6 +554,401 @@ struct LibraryFilamentTestCollection : public PresetCollection
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Missing app config is accepted as default CLI state", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
AppConfig app_config;
|
||||
app_config.set_loading_path((dir.path() / "missing.conf").string());
|
||||
CHECK(app_config.load_if_exists().empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Read-only user preset loading does not create or delete files", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
PresetBundle bundle;
|
||||
PresetsConfigSubstitutions substitutions;
|
||||
|
||||
const fs::path missing_root = dir.path() / "missing-user";
|
||||
bundle.prints.load_presets(missing_root.string(), PRESET_PRINT_NAME, substitutions,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, nullptr,
|
||||
PresetOrigin(), true);
|
||||
CHECK_FALSE(fs::exists(missing_root / PRESET_PRINT_NAME));
|
||||
|
||||
const fs::path malformed = dir.path() / "existing-user" / PRESET_PRINT_NAME / "malformed.json";
|
||||
fs::create_directories(malformed.parent_path());
|
||||
std::ofstream(malformed.string()) << "{not-json";
|
||||
bundle.prints.load_presets((dir.path() / "existing-user").string(), PRESET_PRINT_NAME, substitutions,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, nullptr,
|
||||
PresetOrigin(), true);
|
||||
CHECK(fs::exists(malformed));
|
||||
}
|
||||
|
||||
TEST_CASE("Typeless preset resolution probes loaded FFF collections", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path source_file = dir.path() / "typeless-process.json";
|
||||
std::ofstream(source_file.string()) << R"({"name":"Typeless Process","from":"User"})";
|
||||
|
||||
PresetBundle bundle;
|
||||
Preset &process = add_inmemory_preset(bundle.prints, "Typeless Process");
|
||||
process.file = source_file.string();
|
||||
process.config.option<ConfigOptionFloats>("travel_speed", true)->values = {321.0};
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
Preset::Type resolved_type = Preset::TYPE_INVALID;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_preset_config_type(raw, resolved_type, source_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error, false));
|
||||
CHECK(error.empty());
|
||||
CHECK(resolved_type == Preset::TYPE_PRINT);
|
||||
REQUIRE(raw.option<ConfigOptionFloats>("travel_speed")->values.size() == 1);
|
||||
CHECK_THAT(raw.option<ConfigOptionFloats>("travel_speed")->values.front(), Catch::Matchers::WithinAbs(321.0, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Typeless preset resolution preserves duplicate identity ambiguity", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path source_file = dir.path() / "duplicate-process.json";
|
||||
std::ofstream(source_file.string()) << "{}";
|
||||
|
||||
PresetBundle bundle;
|
||||
add_inmemory_preset(bundle.prints, "First Process Identity").file = source_file.string();
|
||||
add_inmemory_preset(bundle.prints, "Second Process Identity").file = source_file.string();
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
Preset::Type resolved_type = Preset::TYPE_INVALID;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config_type(raw, resolved_type, source_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error, false));
|
||||
CHECK(error == "Preset identity is ambiguous");
|
||||
CHECK(resolved_type == Preset::TYPE_INVALID);
|
||||
}
|
||||
|
||||
TEST_CASE("Typeless preset resolution rejects cross-type ambiguity", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path source_file = dir.path() / "ambiguous.json";
|
||||
std::ofstream(source_file.string()) << "{}";
|
||||
|
||||
PresetBundle bundle;
|
||||
add_inmemory_preset(bundle.prints, "Process Identity").file = source_file.string();
|
||||
add_inmemory_preset(bundle.filaments, "Filament Identity").file = source_file.string();
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
Preset::Type resolved_type = Preset::TYPE_INVALID;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config_type(raw, resolved_type, source_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error, false));
|
||||
CHECK(error == "Preset type is ambiguous");
|
||||
CHECK(resolved_type == Preset::TYPE_INVALID);
|
||||
}
|
||||
|
||||
TEST_CASE("Typeless preset resolution rejects a missing type candidate", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path source_file = dir.path() / "unknown.json";
|
||||
std::ofstream(source_file.string()) << "{}";
|
||||
|
||||
PresetBundle bundle;
|
||||
DynamicPrintConfig raw;
|
||||
Preset::Type resolved_type = Preset::TYPE_INVALID;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config_type(raw, resolved_type, source_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error, false));
|
||||
CHECK(error == "Preset type could not be resolved");
|
||||
CHECK(resolved_type == Preset::TYPE_INVALID);
|
||||
}
|
||||
|
||||
TEST_CASE("Exact file resolution rejects multiple preset identities", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path source_file = dir.path() / "duplicate.json";
|
||||
std::ofstream(source_file.string()) << "{}";
|
||||
|
||||
PresetBundle bundle;
|
||||
Preset &first = add_inmemory_preset(bundle.prints, "First Identity");
|
||||
first.file = source_file.string();
|
||||
Preset &second = add_inmemory_preset(bundle.prints, "Second Identity");
|
||||
second.file = source_file.string();
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "Parent";
|
||||
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, source_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error, false));
|
||||
CHECK(error == "Preset identity is ambiguous");
|
||||
}
|
||||
|
||||
TEST_CASE("System preset resolution returns the canonical vendor configuration", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir source_dir;
|
||||
PresetBundle bundle;
|
||||
|
||||
VendorProfile vendor("VendorB");
|
||||
vendor.name = "Vendor B";
|
||||
auto [vendor_it, inserted] = bundle.vendors.emplace(vendor.id, std::move(vendor));
|
||||
REQUIRE(inserted);
|
||||
|
||||
Preset &resolved = add_inmemory_preset(bundle.prints, "Vendor B Process", "fdm_process_common");
|
||||
resolved.is_system = true;
|
||||
resolved.vendor = &vendor_it->second;
|
||||
resolved.file = (source_dir.path() / "vendor-b-process.json").string();
|
||||
std::ofstream(resolved.file) << "{}";
|
||||
resolved.config.option<ConfigOptionFloats>("travel_speed", true)->values = {321.0};
|
||||
resolved.config.option<ConfigOptionInt>("wall_loops", true)->value = 2;
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common";
|
||||
raw.option<ConfigOptionInt>("wall_loops", true)->value = 5;
|
||||
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, resolved.file,
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error.empty());
|
||||
REQUIRE(raw.option<ConfigOptionFloats>("travel_speed")->values.size() == 1);
|
||||
CHECK_THAT(raw.option<ConfigOptionFloats>("travel_speed")->values.front(), Catch::Matchers::WithinAbs(321.0, 1e-6));
|
||||
CHECK(raw.option<ConfigOptionInt>("wall_loops")->value == 2);
|
||||
}
|
||||
|
||||
TEST_CASE("Manifest-backed preset resolution loads the source vendor tree", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path vendor_dir = dir.path() / "Acme";
|
||||
const fs::path child_file = vendor_dir / "process" / "nested" / "child.json";
|
||||
|
||||
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 Process","sub_path":"process/nested/child.json"}]})";
|
||||
fs::create_directories(child_file.parent_path());
|
||||
std::ofstream((vendor_dir / "process" / "base.json").string())
|
||||
<< R"({"type":"process","name":"fdm_process_common","from":"system",)"
|
||||
<< R"("instantiation":"false","travel_speed":["321"]})";
|
||||
std::ofstream(child_file.string())
|
||||
<< R"({"type":"process","name":"Acme Process","from":"system",)"
|
||||
<< R"("instantiation":"true","inherits":"fdm_process_common","wall_loops":"5"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common";
|
||||
raw.option<ConfigOptionInt>("wall_loops", true)->value = 5;
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, child_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error.empty());
|
||||
REQUIRE(raw.option<ConfigOptionFloats>("travel_speed")->values.size() == 1);
|
||||
CHECK_THAT(raw.option<ConfigOptionFloats>("travel_speed")->values.front(), Catch::Matchers::WithinAbs(321.0, 1e-6));
|
||||
CHECK(raw.option<ConfigOptionInt>("wall_loops")->value == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Manifest-backed resolution is scoped to the explicit source root", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
auto write_vendor = [&](const std::string &root_name, double travel_speed) {
|
||||
const fs::path root = dir.path() / root_name;
|
||||
const fs::path child_file = root / "Acme" / "process" / "child.json";
|
||||
fs::create_directories(child_file.parent_path());
|
||||
std::ofstream((root / "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 Process","sub_path":"process/child.json"}]})";
|
||||
std::ofstream((root / "Acme" / "process" / "base.json").string())
|
||||
<< R"({"type":"process","name":"fdm_process_common","from":"system",)"
|
||||
<< R"("instantiation":"false","travel_speed":[")" << travel_speed << R"("]})";
|
||||
std::ofstream(child_file.string())
|
||||
<< R"({"type":"process","name":"Acme Process","from":"system",)"
|
||||
<< R"("instantiation":"true","inherits":"fdm_process_common"})";
|
||||
return child_file;
|
||||
};
|
||||
|
||||
const fs::path source_a = write_vendor("root-a", 111.0);
|
||||
const fs::path source_b = write_vendor("root-b", 222.0);
|
||||
REQUIRE(fs::exists(source_a));
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "synthetic-parent-marker";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, source_b.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
REQUIRE(raw.option<ConfigOptionFloats>("travel_speed")->values.size() == 1);
|
||||
CHECK_THAT(raw.option<ConfigOptionFloats>("travel_speed")->values.front(), Catch::Matchers::WithinAbs(222.0, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Exact-only resolution rejects an unconfigured manifest-backed file", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path source_file = dir.path() / "Acme" / "process" / "child.json";
|
||||
fs::create_directories(source_file.parent_path());
|
||||
std::ofstream((dir.path() / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme","process_list":[)"
|
||||
<< R"({"name":"Acme Process","sub_path":"process/child.json"}]})";
|
||||
std::ofstream(source_file.string())
|
||||
<< R"({"type":"process","name":"Acme Process","from":"system",)"
|
||||
<< R"("instantiation":"true","layer_height":"0.2"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "Some Parent";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, source_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error, false));
|
||||
CHECK(error == "Preset was not found in the loaded bundle");
|
||||
}
|
||||
|
||||
TEST_CASE("Vendor filament resolution uses the shared Orca library base", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path library_dir = dir.path() / PresetBundle::ORCA_FILAMENT_LIBRARY;
|
||||
const fs::path vendor_dir = dir.path() / "Acme";
|
||||
const fs::path child_file = vendor_dir / "filament" / "nested" / "petg.json";
|
||||
|
||||
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_dir / "filament");
|
||||
std::ofstream((library_dir / "filament" / "pet.json").string())
|
||||
<< R"({"type":"filament","name":"fdm_filament_pet","from":"system",)"
|
||||
<< R"("filament_id":"GFL99","instantiation":"false",)"
|
||||
<< R"("filament_type":["PETG"],"filament_density":["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/nested/petg.json","filament_id":"GFA00"}]})";
|
||||
fs::create_directories(child_file.parent_path());
|
||||
std::ofstream(child_file.string())
|
||||
<< R"({"type":"filament","name":"Acme PETG","from":"system",)"
|
||||
<< R"("filament_id":"GFA00","instantiation":"true","inherits":"fdm_filament_pet"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "fdm_filament_pet";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
REQUIRE(bundle.resolve_preset_config(raw, Preset::TYPE_FILAMENT, child_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error.empty());
|
||||
CHECK(raw.opt_string("filament_type", 0u) == "PETG");
|
||||
REQUIRE(raw.option<ConfigOptionFloats>("filament_density")->values.size() == 1);
|
||||
CHECK_THAT(raw.option<ConfigOptionFloats>("filament_density")->values.front(), Catch::Matchers::WithinAbs(1.27, 1e-6));
|
||||
}
|
||||
|
||||
TEST_CASE("Manifest-backed resolution rejects a missing parent", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path child_file = dir.path() / "Acme" / "process" / "child.json";
|
||||
|
||||
std::ofstream((dir.path() / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme","process_list":[)"
|
||||
<< R"({"name":"Acme Process","sub_path":"process/child.json"}]})";
|
||||
fs::create_directories(child_file.parent_path());
|
||||
std::ofstream(child_file.string())
|
||||
<< R"({"type":"process","name":"Acme Process","from":"system",)"
|
||||
<< R"("instantiation":"true","inherits":"Missing Parent","layer_height":"0.2"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "Missing Parent";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, child_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_FALSE(error.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Manifest-backed resolution rejects a vendor load with malformed entries", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path child_file = dir.path() / "Acme" / "process" / "child.json";
|
||||
|
||||
std::ofstream((dir.path() / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme","process_list":[123,)"
|
||||
<< R"({"name":"Acme Process","sub_path":"process/child.json"}]})";
|
||||
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"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, child_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK_FALSE(error.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("Manifest-backed resolution rejects files absent from the vendor manifest", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path listed_file = dir.path() / "Acme" / "process" / "listed.json";
|
||||
const fs::path unlisted_file = dir.path() / "Acme" / "process" / "unlisted.json";
|
||||
|
||||
std::ofstream((dir.path() / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme","process_list":[)"
|
||||
<< R"({"name":"Listed Process","sub_path":"process/listed.json"}]})";
|
||||
fs::create_directories(listed_file.parent_path());
|
||||
std::ofstream(listed_file.string())
|
||||
<< R"({"type":"process","name":"Listed Process","from":"system",)"
|
||||
<< R"("instantiation":"true","layer_height":"0.2"})";
|
||||
std::ofstream(unlisted_file.string())
|
||||
<< R"({"type":"process","name":"Unlisted Process","from":"system",)"
|
||||
<< R"("instantiation":"true","inherits":"fdm_process_common"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "fdm_process_common";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, unlisted_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error == "Source file is not an instantiated preset in its vendor manifest");
|
||||
}
|
||||
|
||||
TEST_CASE("Manifest-backed resolution rejects a mismatched preset type", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path process_file = dir.path() / "Acme" / "process" / "child.json";
|
||||
|
||||
std::ofstream((dir.path() / "Acme.json").string())
|
||||
<< R"({"version":"1.0.0","name":"Acme","process_list":[)"
|
||||
<< R"({"name":"Acme Process","sub_path":"process/child.json"}]})";
|
||||
fs::create_directories(process_file.parent_path());
|
||||
std::ofstream(process_file.string())
|
||||
<< R"({"type":"process","name":"Acme Process","from":"system",)"
|
||||
<< R"("instantiation":"true","layer_height":"0.2"})";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "fdm_filament_common";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_FILAMENT, process_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error == "Source file is not an instantiated preset in its vendor manifest");
|
||||
}
|
||||
|
||||
TEST_CASE("Resolution terminates when no vendor manifest exists", "[Preset][Bundle][Regression]")
|
||||
{
|
||||
ScopedTemporaryDir dir;
|
||||
const fs::path detached_file = dir.path() / "detached.json";
|
||||
std::ofstream(detached_file.string()) << "{}";
|
||||
|
||||
DynamicPrintConfig raw;
|
||||
raw.option<ConfigOptionString>(BBL_JSON_KEY_INHERITS, true)->value = "Missing Parent";
|
||||
|
||||
PresetBundle bundle;
|
||||
std::string error;
|
||||
CHECK_FALSE(bundle.resolve_preset_config(raw, Preset::TYPE_PRINT, detached_file.string(),
|
||||
ForwardCompatibilitySubstitutionRule::EnableSilent, error));
|
||||
CHECK(error == "Preset was not found in the loaded bundle");
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
||||
Reference in New Issue
Block a user