From e39be23d6dbd5d79942a1b42fc3e039096528edc Mon Sep 17 00:00:00 2001 From: SoftFever Date: Wed, 1 Jul 2026 15:25:43 +0800 Subject: [PATCH] fix compatible_prints/printers on load instead --- src/libslic3r/PresetBundle.cpp | 55 +++++++++++++ src/libslic3r/PresetBundle.hpp | 5 ++ .../libslic3r/test_preset_bundle_loading.cpp | 79 +++++++++++++++++++ 3 files changed, 139 insertions(+) diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index bb3920bde8..e9ff279ded 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -532,6 +532,9 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward load_user_presets(dir_user_presets, substitution_rule); } + // Rewrite renamed compatible_printers / compatible_prints references before selection. + this->normalize_compatible_presets(); + this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); @@ -752,6 +755,8 @@ PresetsConfigSubstitutions PresetBundle::load_project_embedded_presets(std::vect //this->update_multi_material_filament_presets(); //this->update_compatible(PresetSelectCompatibleType::Never); + // Rewrite renamed compatible references before the caller (Plater) selects the project presets. + this->normalize_compatible_presets(); if (! errors_cummulative.empty()) throw Slic3r::RuntimeError(errors_cummulative); @@ -1122,6 +1127,9 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig & this->printers.update_after_user_presets_loaded(); }*/ + // Rewrite renamed compatible references in synced user presets before compatibility is evaluated. + this->normalize_compatible_presets(); + this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); //this->load_selections(config, PresetPreferences()); @@ -1807,6 +1815,9 @@ PresetsConfigSubstitutions PresetBundle::update_subscribed_presets( BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed to save bundle metadata to: " << metadata_save_path.string(); } + // Rewrite renamed compatible references in synced user presets before compatibility is evaluated. + this->normalize_compatible_presets(); + this->update_multi_material_filament_presets(); this->update_compatible(PresetSelectCompatibleType::Never); @@ -5240,6 +5251,50 @@ void PresetBundle::update_multi_material_filament_presets(size_t to_delete_filam } } +// Rewrite a preset-name-list field (compatible_printers / compatible_prints) so references to a +// renamed system preset point at the current name. Sibling-collection analog of +// Preset::normalize_inherits: target.find_preset(name, false) resolves "renamed_from" recursively +// and returns nullptr for unknown names, so we rewrite only on a positive, changed match and leave +// user/deleted names untouched. +static void normalize_compatible_field(Preset &preset, const char *field_key, PresetCollection &target) +{ + auto *opt = preset.config.option(field_key); + if (opt == nullptr) + return; + for (std::string &name : opt->values) { + if (name.empty()) + continue; + if (const Preset *resolved = target.find_preset(name, false); resolved != nullptr && resolved->name != name) + name = resolved->name; + } +} + +// Resolve compatible_printers / compatible_prints references that point at a renamed system preset +// to the current name, mirroring Preset::normalize_inherits for the "inherits" field. Because these +// fields reference presets in sibling collections (printers / prints / sla_prints), the resolution +// cannot happen inside a single collection's load_presets() and runs here, after update_system_maps() +// has built every collection's rename map. Must be called before selection so the rewritten stored +// preset is copied into the edited preset by select_preset (no spurious "modified" flag). +void PresetBundle::normalize_compatible_presets() +{ + // compatible_printers references a printer preset; compatible_prints (filaments / SLA materials) + // references a process preset. Skip system presets: they are the targets of renames, not holders + // of stale references, and their vendor data must not be mutated. (begin()/end() skip defaults.) + auto normalize = [](PresetCollection &holders, PresetCollection &printers, PresetCollection *processes) { + for (Preset &p : holders) { + if (p.is_system) + continue; + normalize_compatible_field(p, "compatible_printers", printers); + if (processes != nullptr) + normalize_compatible_field(p, "compatible_prints", *processes); + } + }; + normalize(this->prints, this->printers, nullptr); + // normalize(this->sla_prints, this->printers, nullptr); + normalize(this->filaments, this->printers, &this->prints); + // normalize(this->sla_materials, this->printers, &this->sla_prints); +} + void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible) { const Preset &printer_preset = this->printers.get_edited_preset(); diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index a1fd1a6430..3acf9ae4c6 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -448,6 +448,11 @@ public: void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible); void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); } + // Rewrite compatible_printers / compatible_prints references that point at a renamed system + // preset to the current name, mirroring Preset::normalize_inherits for the "inherits" field. + // Call after loading presets and before selection; requires update_system_maps() to have run. + void normalize_compatible_presets(); + // Set the is_visible flag for printer vendors, printer models and printer variants // based on the user configuration. // If the "vendor" section is missing, enable all models and variants of the particular vendor. diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index a543b64e7e..4e2ddb480f 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -3,6 +3,7 @@ #include #include "libslic3r/PresetBundle.hpp" +#include "libslic3r/AppConfig.hpp" using namespace Slic3r; @@ -298,3 +299,81 @@ TEST_CASE("Removed Generic parent is normalized into a loaded filament's inherit CHECK(bundle.filaments.get_preset_parent(*child)->name == "Generic PLA @System"); } +namespace { + +// A live reference to a preset's compatible_printers / compatible_prints list. Fetches the *stored* +// preset (real=true) so writes and reads hit the same object; creates the option if absent. +std::vector &compatible_list(PresetCollection &coll, const std::string &preset_name, const char *field_key) +{ + Preset *preset = coll.find_preset(preset_name, /*first_visible_if_not_found=*/false, /*real=*/true); + REQUIRE(preset != nullptr); + return preset->config.option(field_key, true)->values; +} + +} // namespace + +TEST_CASE("Renamed printer/process names are normalized into compatible lists on load", "[Preset][Rename]") +{ + PresetBundle bundle; + + // Current printer + process, each renamed from an older name. + add_inmemory_preset(bundle.printers, "New Printer"); + set_renamed_from(bundle.printers, "New Printer", { "Old Printer" }); + add_inmemory_preset(bundle.prints, "New Process"); + set_renamed_from(bundle.prints, "New Process", { "Old Process" }); + + // A user process still compatible with the OLD printer name. + add_inmemory_preset(bundle.prints, "My Process"); + compatible_list(bundle.prints, "My Process", "compatible_printers") = { "Old Printer" }; + + // A user filament referencing the OLD printer AND OLD process names, plus an unknown printer. + add_inmemory_preset(bundle.filaments, "My Filament"); + compatible_list(bundle.filaments, "My Filament", "compatible_printers") = { "Old Printer", "Unknown Printer" }; + compatible_list(bundle.filaments, "My Filament", "compatible_prints") = { "Old Process" }; + + // Build the rename maps (done during system load in the real pipeline), then normalize. + AppConfig app_config; + bundle.load_installed_printers(app_config); // rebuilds every collection's rename map + bundle.normalize_compatible_presets(); + + // The stale printer name in a process' compatible_printers is rewritten to the current name. + CHECK(compatible_list(bundle.prints, "My Process", "compatible_printers") == std::vector{ "New Printer" }); + + // The stale process name in a filament's compatible_prints is rewritten (this field has no + // runtime rename fallback, so load-time normalization is the only fix). + CHECK(compatible_list(bundle.filaments, "My Filament", "compatible_prints") == std::vector{ "New Process" }); + + // The renamed printer is rewritten while the unknown/deleted name is preserved as-is. + CHECK(compatible_list(bundle.filaments, "My Filament", "compatible_printers") == + (std::vector{ "New Printer", "Unknown Printer" })); + + // Normalizing rewrites config in place without flagging the preset dirty. + CHECK_FALSE(bundle.prints.find_preset("My Process", false, true)->is_dirty); + + // A system preset that already references the current name is left untouched (idempotent no-op). + bundle.normalize_compatible_presets(); + CHECK(compatible_list(bundle.prints, "My Process", "compatible_printers") == std::vector{ "New Printer" }); +} + +TEST_CASE("compatible_prints on SLA materials resolves against sla_prints, not prints", "[Preset][Rename]") +{ + PresetBundle bundle; + + // A renamed SLA process, and a same-named FFF process that must NOT be picked up: resolving the + // SLA material's compatible_prints against `prints` would wrongly rewrite to "Wrong FFF Process". + add_inmemory_preset(bundle.sla_prints, "New SLA Process"); + set_renamed_from(bundle.sla_prints, "New SLA Process", { "Old SLA Process" }); + add_inmemory_preset(bundle.prints, "Wrong FFF Process"); + set_renamed_from(bundle.prints, "Wrong FFF Process", { "Old SLA Process" }); + + add_inmemory_preset(bundle.sla_materials, "My SLA Material"); + compatible_list(bundle.sla_materials, "My SLA Material", "compatible_prints") = { "Old SLA Process" }; + + AppConfig app_config; + bundle.load_installed_printers(app_config); + bundle.normalize_compatible_presets(); + + CHECK(compatible_list(bundle.sla_materials, "My SLA Material", "compatible_prints") == + std::vector{ "New SLA Process" }); +} +