diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 41c78e2579b..6295da51a73 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1672,6 +1672,7 @@ void PresetCollection::load_presets( std::string inherits_value = option_str->value; // Orca: try to find if the parent preset has been renamed inherit_preset = this->find_preset2(inherits_value); + Preset::normalize_inherits(config, inherit_preset); } else { ; } @@ -1924,6 +1925,7 @@ void PresetCollection::load_project_embedded_presets(std::vector& proje option_str->value = inherits_value; }*/ inherit_preset = this->find_preset2(inherits_value, true); + Preset::normalize_inherits(config, inherit_preset); } const Preset& default_preset = this->default_preset_for(config); if (inherit_preset) { @@ -2260,6 +2262,7 @@ bool PresetCollection::load_user_preset(std::string name, std::map (inherits_config); std::string inherits_value = option_str->value; inherit_preset = this->find_preset2(inherits_value, true); + Preset::normalize_inherits(cloud_config, inherit_preset); } const Preset& default_preset = this->default_preset_for(cloud_config); if (inherit_preset) { @@ -2674,7 +2677,10 @@ std::pair PresetCollection::load_external_preset( preset.filament_id = filament_id; else { if (!inherits.empty()) { - Preset *parent = this->find_preset(inherits, false, true); + // Orca: resolve via find_preset2 so a renamed/removed-and-matched parent still + // yields its filament_id (external presets store a full config, so the dangling + // "inherits" itself is normalized on the next load_presets pass). + Preset *parent = this->find_preset2(inherits, true); if (parent) preset.filament_id = parent->filament_id; } @@ -2938,10 +2944,14 @@ void PresetCollection::save_current_preset(const std::string &new_name, bool det //BBS: only save difference for user preset Preset* parent_preset = nullptr; if (!final_inherits.empty()) { - parent_preset = this->find_preset(final_inherits, false, true); - if (parent_preset && this->get_selected_preset().base_id.empty()) { - this->get_selected_preset().base_id = parent_preset->setting_id; - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base_id: " << parent_preset->setting_id; + parent_preset = this->find_preset2(final_inherits, true); + if (parent_preset) { + // Orca: take the saved diff against the resolved parent (renamed / library-matched). + Preset::normalize_inherits(this->get_selected_preset().config, parent_preset); + if (this->get_selected_preset().base_id.empty()) { + this->get_selected_preset().base_id = parent_preset->setting_id; + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " base_id: " << parent_preset->setting_id; + } } } if (parent_preset) @@ -3053,14 +3063,8 @@ const Preset* PresetCollection::get_selected_preset_parent() const return nullptr; preset = &this->default_preset(m_type == Preset::Type::TYPE_PRINTER && edited_preset.printer_technology() == ptSLA ? 1 : 0); } else + // find_preset() already resolves "renamed_from" internally. preset = this->find_preset(inherits, false); - if (preset == nullptr) { - // Resolve the "renamed_from" field. - assert(! inherits.empty()); - auto it = this->find_preset_renamed(inherits); - if (it != m_presets.end()) - preset = &(*it); - } //BBS: add project embedded preset logic and refine is_external return (preset == nullptr/* || preset->is_default || preset->is_external*/) ? nullptr : preset; //return (preset == nullptr/* || preset->is_default*/ || preset->is_external) ? nullptr : preset; @@ -3072,12 +3076,8 @@ const Preset* PresetCollection::get_preset_parent(const Preset& child) const if (inherits.empty()) // return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; return nullptr; + // find_preset() already resolves "renamed_from" internally. const Preset* preset = this->find_preset(inherits, false); - if (preset == nullptr) { - auto it = this->find_preset_renamed(inherits); - if (it != m_presets.end()) - preset = &(*it); - } return // not found (preset == nullptr/* || preset->is_default */|| @@ -3176,6 +3176,14 @@ Preset* PresetCollection::find_preset(const std::string &name, bool first_visibl auto it = this->find_preset_internal(canonical, only_from_library); if (it != m_presets.end() && it->name == canonical) return &this->preset(it - m_presets.begin(), real); + // Resolve the "renamed_from" field: a system preset may have been renamed (e.g. by a + // vendor profile sync), which records its old name in "renamed_from". Try the rename + // map before the first-visible fallback so every caller - including those passing + // real/only_from_library or first_visible_if_not_found - resolves to the renamed preset + // rather than a mismatched one. Recursion follows multi-step renames (A->B->C) and + // terminates as soon as a name resolves or has no further rename entry. + if (const std::string* renamed = get_preset_name_renamed(name)) + return find_preset(*renamed, first_visible_if_not_found, real, only_from_library); return first_visible_if_not_found ? &this->first_visible() : nullptr; } @@ -3183,14 +3191,11 @@ Preset* PresetCollection::find_preset2(const std::string& name, bool auto_match/ { auto preset = find_preset(name, false, true); if (preset == nullptr) { - auto _name = get_preset_name_renamed(name); - if (_name != nullptr) - preset = find_preset(*_name, false, true); - if (auto_match && preset == nullptr) { + if (auto_match) { //Orca: one more try, find the most likely preset in OrcaFilamentLibrary if (name.find("Generic") != std::string::npos) { // The regex pattern matches an optional prefix ending in '_' then "Generic" followed by the material name. - std::regex re(R"(^(?:.*?\b(?:\w+_)?)(Generic)\b\s+([^@]+?)\s*(?:@.*)?$)"); + static const std::regex re(R"(^(?:.*?\b(?:\w+_)?)(Generic)\b\s+([^@]+?)\s*(?:@.*)?$)"); auto alter_name = std::regex_replace(name, re, "Generic $2 @System"); preset = find_preset2(alter_name, false); // print preset file name diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 06534a42ef5..bbbdc6c5ff0 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -320,6 +320,20 @@ public: std::string& inherits() { return Preset::inherits(this->config); } const std::string& inherits() const { return Preset::inherits(const_cast(this)->config); } + // Rewrite cfg's "inherits" to the resolved parent's canonical name. find_preset2 may + // resolve a renamed parent, or a removed vendor profile auto-matched to the + // OrcaFilamentLibrary; persisting the canonical name lets later plain find_preset() + // callers (e.g. get_preset_parent) walk the inheritance chain without the fuzzy match. + // No-op when the parent could not be resolved or the name is already canonical. + static void normalize_inherits(DynamicPrintConfig &cfg, const Preset *resolved_parent) + { + if (resolved_parent == nullptr) + return; + std::string &inherits = Preset::inherits(cfg); + if (inherits != resolved_parent->name) + inherits = resolved_parent->name; + } + // Returns the "compatible_prints_condition". static std::string& compatible_prints_condition(DynamicPrintConfig &cfg) { return cfg.option("compatible_prints_condition", true)->value; } std::string& compatible_prints_condition() { diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 1e2973964b6..d642a849f20 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -1498,6 +1498,9 @@ bool PresetBundle::import_json_presets(PresetsConfigSubstitutions & s ConfigOptionString *option_str = dynamic_cast(inherits_config); inherits_value = option_str->value; inherit_preset = collection->find_preset2(inherits_value, true); + Preset::normalize_inherits(config, inherit_preset); + if (inherit_preset) + inherits_value = inherit_preset->name; // keep the base_id redo below in sync } if (inherit_preset) { new_config = inherit_preset->config; @@ -1869,6 +1872,8 @@ bool PresetBundle::save_preset_to_bundle_dir(Preset& preset, PresetCollection* c if (!parent_preset) { BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " cannot find parent preset for " << preset.name << ", inherits " << inherits; } else { + // Orca: take the saved diff against the resolved parent (renamed / library-matched). + Preset::normalize_inherits(preset.config, parent_preset); if (preset.base_id.empty()) preset.base_id = parent_preset->setting_id; BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " saved preset " << preset.name diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index be2f7278ee0..9d2a6e99051 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -1876,12 +1876,6 @@ void ConfigWizard::priv::load_vendors() for (auto &bundle : bundles) { const PresetCollection &materials = bundle.second.preset_bundle->materials(technology); const Preset *preset = materials.find_preset(material_name); - if (preset == nullptr) { - // Not found. Maybe the material preset is there, bu it was was renamed? - const std::string *new_name = materials.get_preset_name_renamed(material_name); - if (new_name != nullptr) - preset = materials.find_preset(*new_name); - } if (preset != nullptr) { // Materal preset was found, mark it as installed. section_new[preset->name] = "true"; diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index e2bf9307676..a543b64e7e4 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -36,6 +36,47 @@ void write_print_preset(const DynamicPrintConfig &default_config, const fs::path config.save_to_json(file.string(), name, "User", "1.0.0"); } +// Write a preset json carrying a name and an "inherits" value, using the given collection's +// default config so it loads back into that collection. Works for any preset type. +void write_preset_with_inherits(const DynamicPrintConfig &default_config, const fs::path &file, + const std::string &name, const std::string &inherits) +{ + DynamicPrintConfig config(default_config); + config.option(BBL_JSON_KEY_INHERITS, true)->value = inherits; + + fs::create_directories(file.parent_path()); + config.save_to_json(file.string(), name, "User", "1.0.0"); +} + +// Add an in-memory preset (no file) with the given inherits value (empty => root preset). +Preset &add_inmemory_preset(PresetCollection &coll, const std::string &name, const std::string &inherits = {}) +{ + DynamicPrintConfig config(coll.default_preset().config); + config.option(BBL_JSON_KEY_INHERITS, true)->value = inherits; + return coll.load_preset(std::string(), name, config, /*select=*/false); +} + +// Mark an already-loaded preset as renamed from one or more former names. +void set_renamed_from(PresetCollection &coll, const std::string &preset_name, std::vector old_names) +{ + for (auto it = coll.begin(); it != coll.end(); ++it) + if (it->name == preset_name) + it->renamed_from = std::move(old_names); +} + +// A standalone print preset collection that exposes the protected rename-map builder, so a +// renamed_from scenario can be set up without the full system-profile load pipeline. +// (PresetCollection is non-copyable - it holds a mutex - so it is constructed directly with +// the same type/keys/defaults PresetBundle uses for its print collection.) +struct RenameTestCollection : public PresetCollection +{ + RenameTestCollection() + : PresetCollection(Preset::TYPE_PRINT, Preset::print_options(), + static_cast(FullPrintConfig::defaults())) + {} + using PresetCollection::update_map_system_profile_renamed; +}; + } // namespace TEST_CASE("Preset identity is canonicalized from load path", "[Preset][Identity]") @@ -130,3 +171,130 @@ TEST_CASE("Printer extruder count tolerates missing nozzle diameter", "[Preset][ CHECK(bundle.get_printer_extruder_count() == 2); } +TEST_CASE("find_preset resolves a system preset's renamed_from", "[Preset][Rename]") +{ + RenameTestCollection coll; + + // "New Process" is the current preset; it was renamed from "Old Process". + add_inmemory_preset(coll, "New Process"); + set_renamed_from(coll, "New Process", { "Old Process" }); + coll.update_map_system_profile_renamed(); + + // The rename map knows the old name... + const std::string *renamed = coll.get_preset_name_renamed("Old Process"); + REQUIRE(renamed != nullptr); + CHECK(*renamed == "New Process"); + + // ...and plain find_preset() now follows it (the core of this PR; previously this + // resolution lived only in find_preset2 and a few call sites). + const Preset *resolved = coll.find_preset("Old Process"); + REQUIRE(resolved != nullptr); + CHECK(resolved->name == "New Process"); + + // A genuinely unknown name still returns null (no spurious match). + CHECK(coll.find_preset("Totally Unknown") == nullptr); + + // A child that still inherits the OLD name resolves through the runtime walker, + // which uses plain find_preset(). + Preset &child = add_inmemory_preset(coll, "Child Process", "Old Process"); + const Preset *parent = coll.get_preset_parent(child); + REQUIRE(parent != nullptr); + CHECK(parent->name == "New Process"); +} + +TEST_CASE("find_preset resolves a preset renamed more than once", "[Preset][Rename]") +{ + RenameTestCollection coll; + + // "New Process" was renamed twice, so it carries both former names in renamed_from. + add_inmemory_preset(coll, "New Process"); + set_renamed_from(coll, "New Process", { "Original Process", "Old Process" }); + coll.update_map_system_profile_renamed(); + + // Each historical name resolves to the current preset. + for (const char *old_name : { "Original Process", "Old Process" }) { + INFO("resolving old name: " << old_name); + const std::string *renamed = coll.get_preset_name_renamed(old_name); + REQUIRE(renamed != nullptr); + CHECK(*renamed == "New Process"); + + const Preset *resolved = coll.find_preset(old_name); + REQUIRE(resolved != nullptr); + CHECK(resolved->name == "New Process"); + } + + // A child inheriting either former name resolves through the runtime walker. + Preset &child = add_inmemory_preset(coll, "Child Process", "Original Process"); + REQUIRE(coll.get_preset_parent(child) != nullptr); + CHECK(coll.get_preset_parent(child)->name == "New Process"); +} + +TEST_CASE("find_preset2 auto-matches removed Generic vendor profiles to the library", "[Preset][Rename]") +{ + PresetBundle bundle; + + // The OrcaFilamentLibrary replacement that removed empty " Generic" profiles map to. + add_inmemory_preset(bundle.filaments, "Generic PLA @System"); + + // Plain lookups do NOT fuzzy-match a removed vendor profile. + CHECK(bundle.filaments.find_preset("Voron Generic PLA") == nullptr); + CHECK(bundle.filaments.find_preset2("Voron Generic PLA", /*auto_match=*/false) == nullptr); + + // With auto_match, the removed "Voron Generic PLA" resolves to "Generic PLA @System". + const Preset *matched = bundle.filaments.find_preset2("Voron Generic PLA", /*auto_match=*/true); + REQUIRE(matched != nullptr); + CHECK(matched->name == "Generic PLA @System"); + + // No library preset exists for an unrelated material => still no match. + CHECK(bundle.filaments.find_preset2("BrandX Generic PETG", /*auto_match=*/true) == nullptr); +} + +TEST_CASE("Renamed parent is normalized into a loaded preset's inherits", "[Preset][Rename]") +{ + TempPresetDir temp_dir; + RenameTestCollection coll; + + // Current parent, renamed from "Old Process". + add_inmemory_preset(coll, "New Process"); + set_renamed_from(coll, "New Process", { "Old Process" }); + coll.update_map_system_profile_renamed(); + + // A user preset on disk that still inherits the OLD name. + write_preset_with_inherits(coll.default_preset().config, + temp_dir.path / PRESET_PRINT_NAME / "Child.json", "Child", "Old Process"); + + PresetsConfigSubstitutions substitutions; + coll.load_presets(temp_dir.path.string(), PRESET_PRINT_NAME, substitutions, + ForwardCompatibilitySubstitutionRule::Disable); + + const Preset *child = coll.find_preset("Child"); + REQUIRE(child != nullptr); + // The dangling "Old Process" was rewritten to the resolved parent name at load time, + // so the runtime walker (plain find_preset) can resolve the chain. + CHECK(child->inherits() == "New Process"); + REQUIRE(coll.get_preset_parent(*child) != nullptr); + CHECK(coll.get_preset_parent(*child)->name == "New Process"); +} + +TEST_CASE("Removed Generic parent is normalized into a loaded filament's inherits", "[Preset][Rename]") +{ + TempPresetDir temp_dir; + PresetBundle bundle; + + add_inmemory_preset(bundle.filaments, "Generic PLA @System"); + + // A user filament that still inherits a removed " Generic PLA" profile. + write_preset_with_inherits(bundle.filaments.default_preset().config, + temp_dir.path / PRESET_FILAMENT_NAME / "MyPLA.json", "MyPLA", "Voron Generic PLA"); + + PresetsConfigSubstitutions substitutions; + bundle.filaments.load_presets(temp_dir.path.string(), PRESET_FILAMENT_NAME, substitutions, + ForwardCompatibilitySubstitutionRule::Disable); + + const Preset *child = bundle.filaments.find_preset("MyPLA"); + REQUIRE(child != nullptr); + CHECK(child->inherits() == "Generic PLA @System"); + REQUIRE(bundle.filaments.get_preset_parent(*child) != nullptr); + CHECK(bundle.filaments.get_preset_parent(*child)->name == "Generic PLA @System"); +} +