diff --git a/localization/i18n/list.txt b/localization/i18n/list.txt index 9954d85d3e..827686393e 100644 --- a/localization/i18n/list.txt +++ b/localization/i18n/list.txt @@ -309,7 +309,6 @@ src/slic3r/GUI/BBLStatusBarPrint.cpp src/slic3r/GUI/BBLStatusBarSend.cpp src/slic3r/GUI/BaseTransparentDPIFrame.cpp src/slic3r/GUI/CloneDialog.cpp -src/slic3r/GUI/LegacyPrinterMigrationDialog.cpp src/slic3r/GUI/ColorDecomposeDialog.cpp src/slic3r/GUI/ColorDecomposeSupport.cpp src/slic3r/GUI/ConfigWizard.cpp diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index fbbc02765d..58df91ce77 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2729,91 +2729,122 @@ void PresetCollection::update_after_user_presets_loaded() return; } -const Preset *PresetCollection::system_parent_with_inherited_printers(const Preset &preset) const +// A user preset file as written: it holds only the keys the preset sets itself, and an inherits that names a +// renamed parent by the old name the preset was made against. Throws if the file can not be read or parsed. +static json read_user_preset_file(const std::string &file) { - if (!preset.is_user() || preset.inherits().empty()) - return nullptr; - const Preset *parent = get_preset_parent(preset); - if (parent == nullptr || !parent->is_system) - return nullptr; - // A user preset that sets its own compatible_printers keeps exactly those. - const auto *own = preset.config.option("compatible_printers"); - const auto *inherited = parent->config.option("compatible_printers"); - return own != nullptr && inherited != nullptr && own->values == inherited->values ? parent : nullptr; + boost::nowide::ifstream in(file); + return json::parse(in); } -std::vector PresetCollection::legacy_printer_migrations(const std::map> &printers_by_parent) const +std::vector PresetCollection::legacy_user_filament_presets(const std::map> &printers_by_parent) { - std::vector migrations; + auto has_entry = [&printers_by_parent](const std::string &name) { return printers_by_parent.count(name) > 0; }; + std::vector legacy_presets; + // The preset sync thread can add or delete presets meanwhile. + lock(); for (const Preset &preset : m_presets) { - const Preset *parent = system_parent_with_inherited_printers(preset); - if (parent == nullptr) + if (!preset.is_user() || preset.inherits().empty()) + continue; + const Preset *parent = get_preset_parent(preset); + if (parent == nullptr || !parent->is_system) continue; // An empty list already covers every printer. const std::vector &own = preset.config.option("compatible_printers")->values; if (own.empty()) continue; - // The map names the parent as it was when the map was written; a later rename keeps that name in renamed_from. - auto it = printers_by_parent.find(parent->name); - for (auto old_name = parent->renamed_from.begin(); it == printers_by_parent.end() && old_name != parent->renamed_from.end(); ++old_name) - it = printers_by_parent.find(*old_name); + // The parent's name in the older release is its current name or one in its renamed_from, so only the + // files of presets whose parent has an entry under one of those are read. + if (!has_entry(parent->name) && std::none_of(parent->renamed_from.begin(), parent->renamed_from.end(), has_entry)) + continue; + LegacyUserFilamentPreset legacy{preset.name, parent->name, {}, {}}; + try { + const json j = read_user_preset_file(preset.file); + // A preset whose file sets compatible_printers, even to an empty list, was given its printers on + // purpose and keeps exactly those. + if (j.contains("compatible_printers")) + continue; + legacy.inherits = j.value(BBL_JSON_KEY_INHERITS, std::string()); + } catch (const std::exception &err) { + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": can not read " << preset.file << ": " << err.what(); + continue; + } + auto it = printers_by_parent.find(legacy.inherits); if (it == printers_by_parent.end()) continue; - LegacyPrinterMigration migration{preset.name, parent->name, {}}; for (const std::string &printer : it->second) if (std::find(own.begin(), own.end(), printer) == own.end()) - migration.printers.push_back(printer); - if (!migration.printers.empty()) - migrations.push_back(std::move(migration)); + legacy.printers.push_back(printer); + if (!legacy.printers.empty()) + legacy_presets.push_back(std::move(legacy)); } - return migrations; + unlock(); + return legacy_presets; } -bool PresetCollection::apply_legacy_printer_migration(const LegacyPrinterMigration &migration) +bool PresetCollection::migrate_legacy_user_filament_preset(const LegacyUserFilamentPreset &legacy) { + // Copied out under the lock: the preset sync thread can delete presets, which invalidates references into + // m_presets, while the file is written. lock(); - auto it = this->find_preset_internal(migration.preset); - if (it == m_presets.end() || it->name != migration.preset || system_parent_with_inherited_printers(*it) == nullptr) { + auto it = this->find_preset_internal(legacy.preset); + if (it == m_presets.end() || it->name != legacy.preset || !it->is_user()) { unlock(); return false; } - Preset &preset = *it; - std::vector printers = preset.config.option("compatible_printers")->values; - for (const std::string &printer : migration.printers) + const std::string file = it->file; + std::vector printers = it->config.option("compatible_printers")->values; + unlock(); + for (const std::string &printer : legacy.printers) if (std::find(printers.begin(), printers.end(), printer) == printers.end()) printers.push_back(printer); - const bool selected = m_idx_selected == static_cast(it - m_presets.begin()); - unlock(); // Only compatible_printers changes in the file. Saving the whole preset would also rewrite an inherits that // names a renamed parent by its old name, the only name older releases know it by. { - InstanceLock instance_lock(user_presets_lock_path()); + const std::string lock_path = user_presets_lock_path(); + InstanceLock instance_lock(lock_path); + // Without the lock another instance could save between the read and the write. Migrating can wait. + if (!lock_path.empty() && !instance_lock.locked()) + return false; try { - boost::nowide::ifstream in(preset.file); - json j = json::parse(in); - j["compatible_printers"] = printers; - if (const std::error_code ec = write_file_atomically(preset.file, j.dump(1, '\t'))) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to save " << preset.file << ": " << ec.message(); + json j = read_user_preset_file(file); + // Changed by hand or by another instance since the preset was found: a list of its own stands, and + // another parent would need other printers. + if (j.contains("compatible_printers") || j.value(BBL_JSON_KEY_INHERITS, std::string()) != legacy.inherits) + return false; + j["compatible_printers"] = printers; + if (const std::error_code ec = write_file_atomically(file, j.dump(1, '\t') + "\n")) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to save " << file << ": " << ec.message(); return false; } } catch (const std::exception &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not migrate " << preset.file << ": " << err.what(); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not migrate " << file << ": " << err.what(); return false; } } lock(); - preset.config.option("compatible_printers")->values = printers; - if (selected) - m_edited_preset.config.option("compatible_printers", true)->values = printers; - unlock(); - // Push the change to the cloud copy, as an edit in the settings tab would. - if (!preset.setting_id.empty()) { - preset.sync_info = "update"; - preset.save_info(); + it = this->find_preset_internal(legacy.preset); + if (it != m_presets.end() && it->name == legacy.preset) { + std::vector &saved = it->config.option("compatible_printers", true)->values; + std::vector &edited = m_edited_preset.config.option("compatible_printers", true)->values; + // A change to the list pending in the settings tab stays pending. Without one, the edited copy and the + // snapshot unsaved changes are measured against take the written list, so nothing reads as modified. + if (m_idx_selected == static_cast(it - m_presets.begin()) && edited == saved) { + edited = printers; + if (m_saved_preset.name == it->name) + m_saved_preset.config.option("compatible_printers", true)->values = printers; + } + saved = printers; + // Push the change to the cloud copy, as an edit in the settings tab would. + if (!it->setting_id.empty()) { + it->sync_info = "update"; + it->save_info(); + } } - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": user preset " << preset.name << " gets " << migration.printers.size() + unlock(); + BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": user preset " << legacy.preset << " gets " << legacy.printers.size() << " printer(s) its parent listed in an older release"; return true; } diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 80882ea596..cdbb5fc7d1 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -551,11 +551,13 @@ struct PresetConfigSubstitutions { // PrusaSlicer and reading the user Print / Filament / Printer profiles. using PresetsConfigSubstitutions = std::vector; -// A user filament preset that no longer shows on printers its system parent listed in an older release, -// and those printers. Migrating it writes them into the preset's own compatible_printers. -struct LegacyPrinterMigration { +// A user filament preset made against an older release that no longer shows on printers its system parent +// listed then, and those printers. Migrating it writes them into the preset's own compatible_printers. +struct LegacyUserFilamentPreset { std::string preset; std::string parent; + // The parent's name as the preset file states it: the name the older release knew it by. + std::string inherits; std::vector printers; }; @@ -610,13 +612,15 @@ public: bool load_user_preset(std::string name, std::map preset_values, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule, const PresetOrigin &load_origin = PresetOrigin(PresetOrigin::Kind::User)); void update_after_user_presets_loaded(); // User presets store only their diff, so a user preset inherits its system parent's compatible_printers - // and disappears from a printer once a profile update narrows that list. Lists the user presets that - // inherit the list and the printers their parent covered in an older release (printers_by_parent, keyed - // by the parent's name then) but no longer lists. - std::vector legacy_printer_migrations(const std::map> &printers_by_parent) const; - // Adds the migration's printers to the preset's own compatible_printers and saves the preset to disk. - // False if the preset no longer inherits its parent's list or could not be saved. - bool apply_legacy_printer_migration(const LegacyPrinterMigration &migration); + // and disappears from a printer once a profile update narrows that list. Lists the user presets whose + // files do not set compatible_printers, and the printers their parent covered in an older release but + // no longer lists. printers_by_parent is keyed by the parent's name in that release, which is the name + // the preset file's inherits still holds. + std::vector legacy_user_filament_presets(const std::map> &printers_by_parent); + // Adds the printers to the preset's own compatible_printers and saves the preset to disk. False if the + // preset file sets compatible_printers or names another parent by now, if another instance holds the + // user presets lock, or if the file could not be saved. + bool migrate_legacy_user_filament_preset(const LegacyUserFilamentPreset &legacy); //BBS: get user presets int get_user_presets(PresetBundle *preset_bundle, std::vector &result_presets); void set_sync_info_and_save(std::string name, std::string setting_id, std::string syncinfo, long long update_time, const std::string& user_id); @@ -909,10 +913,6 @@ public: size_t num_default_presets() { return m_num_default_presets; } protected: - // The system parent of a user preset whose compatible_printers is the parent's, or nullptr: the presets - // legacy_printer_migrations() considers. - const Preset* system_parent_with_inherited_printers(const Preset &preset) const; - PresetCollection() = default; // Deleted by the std::recursive_mutex member. PresetBundle copies by assignment. PresetCollection(const PresetCollection &other) = delete; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index d5d594b2ae..13a02c80b7 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include #include @@ -100,32 +99,19 @@ using json = nlohmann::json; namespace Slic3r { -// Per current system filament preset, the printers it listed in an older release and no longer does -// (resources/info/legacy_compatible_printers.json, written by orca_profile_tool.py legacy-compat). -// The file ships with the build, so it is read once it opens; until then (e.g. presets loaded before -// resources_dir() is set) every call tries again and there are no legacy printers. +// Per system filament preset of an older release, by its name then, the printers it listed that the preset it +// now loads as does not (resources/info/legacy_compatible_printers.json, written by orca_profile_tool.py +// legacy-compat). Empty if the file is missing or malformed. static std::map> legacy_compatible_printers() { - static std::mutex mutex; - static std::map> printers_by_parent; - static bool read = false; - - std::lock_guard guard(mutex); - if (!read) { - const fs::path path = fs::path(resources_dir()) / "info" / "legacy_compatible_printers.json"; + const fs::path path = fs::path(resources_dir()) / "info" / "legacy_compatible_printers.json"; + try { boost::nowide::ifstream in(path.string()); - if (!in) { - BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": " << path.string() << " not found"; - return {}; - } - read = true; - try { - printers_by_parent = json::parse(in).at("presets").get>>(); - } catch (const std::exception &err) { - BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not read " << path.string() << ": " << err.what(); - } + return json::parse(in).at("presets").get>>(); + } catch (const std::exception &err) { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": can not read " << path.string() << ": " << err.what(); + return {}; } - return printers_by_parent; } // Project-level options imported from a loaded 3MF into project_config. s_project_options_published @@ -1085,7 +1071,8 @@ bool PresetBundle::backup_user_folder_to_zip(const std::string &zip_path) const Zipper zipper(zip_path); // Directories get their own entries, which extract_archive_confined() needs to recreate them. zipper.add_entry(std::string(PRESET_USER_DIR) + "/", nullptr, 0); - for (const fs::directory_entry &entry : fs::recursive_directory_iterator(user_dir)) { + // The loader follows symlinked folders, and migration writes the presets in them. + for (const fs::directory_entry &entry : fs::recursive_directory_iterator(user_dir, fs::directory_options::follow_directory_symlink)) { const std::string name = (fs::path(PRESET_USER_DIR) / entry.path().lexically_relative(user_dir)).generic_string(); if (fs::is_directory(entry.status())) { zipper.add_entry(name + "/", nullptr, 0); @@ -1109,20 +1096,20 @@ bool PresetBundle::backup_user_folder_to_zip(const std::string &zip_path) const } } -std::vector PresetBundle::legacy_printer_migrations() const +std::vector PresetBundle::legacy_user_filament_presets() { - return filaments.legacy_printer_migrations(legacy_compatible_printers()); + return filaments.legacy_user_filament_presets(legacy_compatible_printers()); } -std::string PresetBundle::migrate_legacy_printers(const std::vector &migrations, std::vector &failed) +std::string PresetBundle::migrate_legacy_user_filament_presets(const std::vector &legacy_presets, std::vector &failed) { const std::string zip_path = data_dir() + "/filament_preset_migration_" + Utils::time2str(Utils::TimeZone::utc, Utils::TimeFormat::iso8601Z) + "_backup.zip"; if (!backup_user_folder_to_zip(zip_path)) return {}; - for (const LegacyPrinterMigration &migration : migrations) - if (!filaments.apply_legacy_printer_migration(migration)) - failed.push_back(migration.preset); + for (const LegacyUserFilamentPreset &legacy : legacy_presets) + if (!filaments.migrate_legacy_user_filament_preset(legacy)) + failed.push_back(legacy.preset); return zip_path; } diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 16c1052629..53d96fcf6d 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -379,13 +379,13 @@ public: // Writes the user preset folder into a zip archive at zip_path, its entries under "user/". bool backup_user_folder_to_zip(const std::string &zip_path) const; - // User filament presets that no longer show on printers their parent listed in an older release - // (resources/info/legacy_compatible_printers.json). - std::vector legacy_printer_migrations() const; - // Backs up the user preset folder into a zip in data_dir(), then writes each migration's printers into its - // preset file. Returns the zip's path, or an empty string when the backup failed and nothing was migrated. + // User filament presets made against an older release that no longer show on printers their parent + // listed then (resources/info/legacy_compatible_printers.json). + std::vector legacy_user_filament_presets(); + // Backs up the user preset folder into a zip in data_dir(), then writes each preset's printers into its + // file. Returns the zip's path, or an empty string when the backup failed and nothing was migrated. // The presets that could not be migrated are added to failed. - std::string migrate_legacy_printers(const std::vector &migrations, std::vector &failed); + std::string migrate_legacy_user_filament_presets(const std::vector &legacy_presets, std::vector &failed); //BBS: project embedded preset logic PresetsConfigSubstitutions load_project_embedded_presets(std::vector project_presets, ForwardCompatibilitySubstitutionRule substitution_rule); diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index a8854b70e4..dd13fd5353 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -362,8 +362,6 @@ set(SLIC3R_GUI_SOURCES GUI/Lazy.hpp GUI/LazyPage.cpp GUI/LazyPage.hpp - GUI/LegacyPrinterMigrationDialog.cpp - GUI/LegacyPrinterMigrationDialog.hpp GUI/LibVGCode/LibVGCodeWrapper.hpp GUI/LibVGCode/LibVGCodeWrapper.cpp GUI/LinuxDisplayBackend.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index b2a22aee93..d3eb49aeaf 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -12,7 +12,6 @@ #include "DeviceManager.hpp" #include "HMS.hpp" #include "PresetBundleDialog.hpp" -#include "LegacyPrinterMigrationDialog.hpp" #include "WebUserLoginDialog.hpp" #include "WebViewDialog.hpp" #include "slic3r/Utils/BBLCloudServiceAgent.hpp" @@ -1110,8 +1109,11 @@ void GUI_App::post_init() } this->check_new_version_sf(); - if (!cw_showed) - this->check_legacy_printer_migration(false); + // Only presets made against 2.4.2 or older, the release legacy_compatible_printers.json is generated + // against, can be missing printers, so the prompt runs only on the first start after one of those. + if (!cw_showed && m_last_config_version && m_last_config_version->valid() + && *m_last_config_version <= Semver(2, 4, 2)) + this->check_legacy_user_filament_presets(false); const auto cloud_provider = get_printer_cloud_provider(); if (is_user_login(cloud_provider) && !app_config->get_stealth_mode()) { // this->check_privacy_version(0); @@ -6819,42 +6821,56 @@ void GUI_App::reload_settings() } } -void GUI_App::check_legacy_printer_migration(bool by_user) +void GUI_App::check_legacy_user_filament_presets(bool by_user) { - const std::vector migrations = preset_bundle->legacy_printer_migrations(); - if (migrations.empty()) { + if (!by_user && app_config->get_bool("legacy_user_filament_presets_dont_remind")) + return; + const std::vector legacy_presets = preset_bundle->legacy_user_filament_presets(); + if (legacy_presets.empty()) { if (by_user) MessageDialog(mainframe, _L("All your filament presets are available on the printers they were made for."), _L("Migrate Filament Presets"), wxOK | wxICON_INFORMATION).ShowModal(); return; } - if (!by_user && app_config->get_bool("legacy_printer_migration_dont_remind")) - return; - LegacyPrinterMigrationDialog dlg(mainframe, migrations, !by_user); + wxString message = _L("OrcaSlicer 2.5.0 restructured many system filament presets to fix incorrect profiles and prepare " + "for future features. As a result, the presets below no longer show on some printers they were made " + "for. Sorry for the trouble; this is a one-time fix.\n\n" + "Add those printers back to your presets now? Your presets are backed up first. You can also do this " + "later from Help > Migrate Filament Presets.") + "\n"; + for (const LegacyUserFilamentPreset &legacy : legacy_presets) { + message += "\n" + format_wxstr(_L("%1% (based on %2%)"), legacy.preset, legacy.parent); + for (const std::string &printer : legacy.printers) + message += "\n " + from_u8(printer); + } + RichMessageDialog dlg(mainframe, message, _L("Migrate Filament Presets"), wxYES | wxNO | wxICON_QUESTION); + if (!by_user) + dlg.ShowCheckBox(_L("Don't remind me again")); const bool migrate = dlg.ShowModal() == wxID_YES; - if (dlg.dont_remind()) - app_config->set_bool("legacy_printer_migration_dont_remind", true); + if (dlg.IsCheckBoxChecked()) + app_config->set_bool("legacy_user_filament_presets_dont_remind", true); if (!migrate) return; std::vector failed; - const std::string backup = preset_bundle->migrate_legacy_printers(migrations, failed); + const std::string backup = preset_bundle->migrate_legacy_user_filament_presets(legacy_presets, failed); if (backup.empty()) { show_error(mainframe, _L("Your presets could not be backed up, so they were not migrated.")); return; } + if (failed.size() == legacy_presets.size()) { + show_error(mainframe, format_wxstr(_L("Your filament presets could not be migrated. A backup of your presets was saved to:\n%1%"), backup)); + return; + } preset_bundle->update_compatible(PresetSelectCompatibleType::Never); mainframe->update_side_preset_ui(); for (auto tab : tabs_list) { tab->reload_config(); tab->update_changed_ui(); } - if (plater_) - plater_->sidebar().update_all_preset_comboboxes(); - wxString message = format_wxstr(_L("Your filament presets were migrated. Select them again in the filament list where you used them.\n\n" - "A backup of your presets was saved to:\n%1%"), backup); + message = format_wxstr(_L("Your filament presets were migrated. Select them again in the filament list where you used them.\n\n" + "A backup of your presets was saved to:\n%1%"), backup); if (!failed.empty()) { message += "\n\n" + _L("These presets could not be migrated:"); for (const std::string &name : failed) diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 0b7a2d4780..7ab9dd58a9 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -567,10 +567,10 @@ public: void check_update(bool show_tips, int by_user); void check_new_version(bool show_tips = false, int by_user = 0); void check_new_version_sf(bool show_tips = false, int by_user = 0); - // Offers to migrate user filament presets that lost printers their parent listed in an older release. - // At startup it asks once per release unless the user chose not to be reminded; by_user also reports - // when there is nothing to migrate. - void check_legacy_printer_migration(bool by_user); + // Offers to migrate user filament presets made against an older release that lost printers their parent + // listed then. At startup it runs only on the first start after 2.4.2 or older, unless the user chose not + // to be reminded; by_user also reports when there is nothing to migrate. + void check_legacy_user_filament_presets(bool by_user); bool process_network_msg(std::string dev_id, std::string msg); void request_new_version(int by_user); void enter_force_upgrade(); diff --git a/src/slic3r/GUI/LegacyPrinterMigrationDialog.cpp b/src/slic3r/GUI/LegacyPrinterMigrationDialog.cpp deleted file mode 100644 index e2cc65056a..0000000000 --- a/src/slic3r/GUI/LegacyPrinterMigrationDialog.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "LegacyPrinterMigrationDialog.hpp" - -#include "GUI.hpp" -#include "GUI_App.hpp" -#include "GUI_Utils.hpp" -#include "I18N.hpp" -#include "MainFrame.hpp" -#include "format.hpp" -#include "libslic3r/Preset.hpp" -#include "Widgets/Button.hpp" -#include "Widgets/CheckBox.hpp" -#include "Widgets/DialogButtons.hpp" -#include "Widgets/Label.hpp" - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace Slic3r { namespace GUI { - -LegacyPrinterMigrationDialog::LegacyPrinterMigrationDialog(wxWindow *parent, const std::vector &migrations, - bool offer_dont_remind) - : DPIDialog(parent ? parent : static_cast(wxGetApp().mainframe), wxID_ANY, _L("Migrate Filament Presets"), - wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX) -{ - SetBackgroundColour(*wxWHITE); - SetFont(Label::Body_14); - - auto *sizer = new wxBoxSizer(wxVERTICAL); - - auto *message = new Label(this, - _L("Since OrcaSlicer 2.5.0, filament presets have been restructured to support more features. Some of your " - "presets need to be migrated to the new structure, or they will no longer be available for some of your " - "printers.\n\n" - "Would you like to migrate them now? Your presets will be backed up first, and this only needs to be done " - "once. You can also do it later from Help > Migrate Filament Presets."), - 0); - message->Wrap(FromDIP(520)); - sizer->Add(message, 0, wxEXPAND | wxALL, FromDIP(15)); - - sizer->Add(new Label(this, _L("Affected presets:")), 0, wxLEFT | wxRIGHT, FromDIP(15)); - sizer->AddSpacer(FromDIP(5)); - - wxString details; - for (const LegacyPrinterMigration &migration : migrations) { - details += format_wxstr(_L("%1% (based on %2%)"), migration.preset, migration.parent) + "\n"; - for (const std::string &printer : migration.printers) - details += " " + from_u8(printer) + "\n"; - } - auto *list = new wxTextCtrl(this, wxID_ANY, details, wxDefaultPosition, wxSize(-1, FromDIP(220)), - wxTE_MULTILINE | wxTE_READONLY | wxTE_DONTWRAP); - sizer->Add(list, 1, wxEXPAND | wxLEFT | wxRIGHT, FromDIP(15)); - - if (offer_dont_remind) { - auto *dont_remind_sizer = new wxBoxSizer(wxHORIZONTAL); - m_dont_remind = new ::CheckBox(this); - auto *dont_remind_label = new Label(this, _L("Don't remind me again")); - dont_remind_label->Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &e) { - m_dont_remind->SetValue(!m_dont_remind->GetValue()); - e.Skip(); - }); - dont_remind_sizer->Add(m_dont_remind, 0, wxALIGN_CENTER_VERTICAL); - dont_remind_sizer->Add(dont_remind_label, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, FromDIP(6)); - sizer->Add(dont_remind_sizer, 0, wxLEFT | wxRIGHT | wxTOP, FromDIP(15)); - } - - auto *buttons = new DialogButtons(this, {"Yes", "No"}, "Yes"); - buttons->GetYES()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_YES); }); - buttons->GetNO()->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_NO); }); - sizer->Add(buttons, 0, wxEXPAND); - - SetSizerAndFit(sizer); - CenterOnParent(); - wxGetApp().UpdateDlgDarkUI(this); -} - -bool LegacyPrinterMigrationDialog::dont_remind() const -{ - return m_dont_remind != nullptr && m_dont_remind->GetValue(); -} - -void LegacyPrinterMigrationDialog::on_dpi_changed(const wxRect &) -{ - if (m_dont_remind != nullptr) - m_dont_remind->Rescale(); - GetSizer()->SetSizeHints(this); - Refresh(); -} - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/LegacyPrinterMigrationDialog.hpp b/src/slic3r/GUI/LegacyPrinterMigrationDialog.hpp deleted file mode 100644 index 5d5cf1b2f6..0000000000 --- a/src/slic3r/GUI/LegacyPrinterMigrationDialog.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#include "GUI_Utils.hpp" - -#include - -class CheckBox; -class wxWindow; -class wxRect; - -namespace Slic3r { - -struct LegacyPrinterMigration; - -namespace GUI { - -// Lists the user filament presets that no longer show on printers their parent listed in an older -// release, and asks whether to migrate them. ShowModal() returns wxID_YES to migrate. With -// offer_dont_remind it also offers to stop the startup reminder for good. -class LegacyPrinterMigrationDialog : public DPIDialog -{ -public: - LegacyPrinterMigrationDialog(wxWindow *parent, const std::vector &migrations, bool offer_dont_remind); - - bool dont_remind() const; - -protected: - void on_dpi_changed(const wxRect &suggested_rect) override; - -private: - ::CheckBox *m_dont_remind{nullptr}; -}; - -}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 110b7c7bac..02bf418d62 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2829,7 +2829,7 @@ wxMenu* MainFrame::generate_help_menu() append_menu_item(helpMenu, wxID_ANY, _L("Migrate Filament Presets"), _L("Bring back filament presets that a profile update removed from printers they were made for"), - [](wxCommandEvent&) { wxGetApp().check_legacy_printer_migration(true); }); + [](wxCommandEvent&) { wxGetApp().check_legacy_user_filament_presets(true); }); helpMenu->AppendSeparator(); diff --git a/src/slic3r/GUI/NativeCommands.cpp b/src/slic3r/GUI/NativeCommands.cpp index 2c9fba17bc..4da288d1ea 100644 --- a/src/slic3r/GUI/NativeCommands.cpp +++ b/src/slic3r/GUI/NativeCommands.cpp @@ -590,7 +590,7 @@ std::vector build_command_catalog() return AppActionRunResult{AppActionRunResult::Level::Success}; }); add("help_migrate_filament_presets", L("Migrate Filament Presets"), L("Help"), [](const std::string&) { - wxGetApp().check_legacy_printer_migration(true); + wxGetApp().check_legacy_user_filament_presets(true); return AppActionRunResult{AppActionRunResult::Level::Success}; }); add("help_troubleshoot", L("Troubleshoot Center"), L("Help"), [](const std::string&) { diff --git a/tests/libslic3r/test_preset_bundle_loading.cpp b/tests/libslic3r/test_preset_bundle_loading.cpp index a142355240..e667f7e79f 100644 --- a/tests/libslic3r/test_preset_bundle_loading.cpp +++ b/tests/libslic3r/test_preset_bundle_loading.cpp @@ -7163,90 +7163,6 @@ TEST_CASE("A user filament whose derived compatible printer cannot be written ba } #endif -TEST_CASE("User filaments that lost printers their narrowed system parent listed in an older release are found", "[Preset][Bundle]") -{ - PresetBundle bundle; - PresetCollection &filaments = bundle.filaments; - const std::string parent = "Generic PETG @Creality K1-all"; - - // load_preset inserts in sorted order, so presets are looked up again rather than held by reference. - auto set_printers = [&filaments](const std::string &name, std::vector printers) { - filaments.find_preset(name)->config.option("compatible_printers", true)->values = std::move(printers); - }; - add_inmemory_preset(filaments, parent).is_system = true; - add_inmemory_preset(filaments, "My PETG", parent); - add_inmemory_preset(filaments, "My PETG own list", parent); - set_printers(parent, { "Creality K1C 0.6 nozzle" }); - set_printers("My PETG", { "Creality K1C 0.6 nozzle" }); - set_printers("My PETG own list", { "Creality K1C 0.6 nozzle", "Creality K1C 0.8 nozzle" }); - - // A printer the parent still lists is not reported again. The system preset and a user preset - // with its own list are left alone. - std::vector migrations = - filaments.legacy_printer_migrations({ { parent, { "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle" } } }); - REQUIRE(migrations.size() == 1); - CHECK(migrations[0].preset == "My PETG"); - CHECK(migrations[0].parent == parent); - CHECK(migrations[0].printers == std::vector{ "Creality K1C 0.4 nozzle" }); - - // A parent renamed after the map was written is still found under its old name. - add_inmemory_preset(filaments, "Generic PETG @Creality K1 renamed").is_system = true; - add_inmemory_preset(filaments, "My renamed PETG", "Generic PETG @Creality K1 renamed"); - set_renamed_from(filaments, "Generic PETG @Creality K1 renamed", { parent }); - set_printers("Generic PETG @Creality K1 renamed", { "Creality K1C 0.6 nozzle" }); - set_printers("My renamed PETG", { "Creality K1C 0.6 nozzle" }); - migrations = filaments.legacy_printer_migrations({ { parent, { "Creality K1C 0.4 nozzle" } } }); - REQUIRE(migrations.size() == 2); - CHECK(migrations[1].preset == "My renamed PETG"); - CHECK(migrations[1].parent == "Generic PETG @Creality K1 renamed"); -} - -TEST_CASE("Migrating a user filament backs up the user folder to a zip and writes the legacy printers to its file", "[Preset][Bundle]") -{ - ScopedTemporaryDir temp_dir; - ScopedDataDir data(temp_dir.path()); - const std::string parent = "Generic PETG @Creality K1-all"; - auto with_parent = [&parent](PresetBundle &bundle) { - add_inmemory_preset(bundle.filaments, parent).is_system = true; - bundle.filaments.find_preset(parent)->config.option("compatible_printers", true)->values = { "Creality K1C 0.6 nozzle" }; - }; - // Like a real user preset, the file states only its diff, so the printer list comes from the parent. - const fs::path user_dir = temp_dir.path() / PRESET_USER_DIR / "default"; - const fs::path file = user_dir / PRESET_FILAMENT_NAME / "My PETG.json"; - fs::create_directories(file.parent_path()); - const std::string original = std::string(R"({"type":"filament","name":"My PETG","from":"User","version":"1.0.0","inherits":")") + parent + R"("})"; - std::ofstream(file.string()) << original; - auto load = [&user_dir](PresetCollection &filaments) { - PresetsConfigSubstitutions substitutions; - filaments.load_presets(user_dir.string(), PRESET_FILAMENT_NAME, substitutions, ForwardCompatibilitySubstitutionRule::EnableSilent); - }; - PresetBundle bundle; - PresetCollection &filaments = bundle.filaments; - with_parent(bundle); - load(filaments); - const std::map> map{ { parent, { "Creality K1C 0.4 nozzle" } } }; - const std::vector migrations = filaments.legacy_printer_migrations(map); - REQUIRE(migrations.size() == 1); - - std::vector failed; - const std::string backup = bundle.migrate_legacy_printers(migrations, failed); - REQUIRE_FALSE(backup.empty()); - CHECK(std::regex_match(fs::path(backup).filename().string(), std::regex("filament_preset_migration_\\d{8}T\\d{6}Z_backup\\.zip"))); - CHECK(failed.empty()); - const fs::path extracted = temp_dir.path() / "extracted"; - REQUIRE(extract_archive_confined(backup, extracted.string())); - CHECK(read_file(extracted / PRESET_USER_DIR / "default" / PRESET_FILAMENT_NAME / "My PETG.json") == original); - - // Reloaded from disk, the preset carries both printers itself and needs no further migration. - PresetBundle reloaded; - with_parent(reloaded); - load(reloaded.filaments); - const std::vector expected{ "Creality K1C 0.6 nozzle", "Creality K1C 0.4 nozzle" }; - CHECK(reloaded.filaments.find_preset("My PETG")->config.option("compatible_printers")->values == expected); - CHECK(reloaded.filaments.legacy_printer_migrations(map).empty()); - CHECK_FALSE(reloaded.filaments.apply_legacy_printer_migration(migrations[0])); -} - namespace { // A filament collection that exposes the rename map update, which PresetBundle runs after loading system presets. @@ -7259,29 +7175,184 @@ struct RenameTestFilaments : public PresetCollection using PresetCollection::update_map_system_profile_renamed; }; +// A system filament preset that lists the given printers and is known by the given former names. +void add_system_filament(PresetCollection &filaments, const std::string &name, std::vector printers, + std::vector renamed_from = {}) +{ + add_inmemory_preset(filaments, name).is_system = true; + filaments.find_preset(name)->config.option("compatible_printers", true)->values = std::move(printers); + set_renamed_from(filaments, name, std::move(renamed_from)); +} + +// A user filament preset file as the settings tab saves it: only its diff, so unless extra sets +// compatible_printers, the printer list comes from the parent. +fs::path write_user_filament(const fs::path &dir, const std::string &name, const std::string &inherits, const std::string &extra = {}) +{ + const fs::path file = dir / PRESET_FILAMENT_NAME / (name + ".json"); + fs::create_directories(file.parent_path()); + std::ofstream(file.string()) << R"({"type":"filament","name":")" << name << R"(","from":"User","version":"1.0.0","inherits":")" + << inherits << '"' << extra << '}'; + return file; +} + +void load_user_filaments(PresetCollection &filaments, const fs::path &dir) +{ + PresetsConfigSubstitutions substitutions; + filaments.load_presets(dir.string(), PRESET_FILAMENT_NAME, substitutions, ForwardCompatibilitySubstitutionRule::EnableSilent); +} + +const std::string k1_parent = "Generic PETG @Creality K1-all"; +const std::string k1_old_parent = "Creality Generic PETG @K1-all"; + } // namespace +TEST_CASE("User filaments made against an older release that lost printers their narrowed system parent listed are found", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + RenameTestFilaments filaments; + add_system_filament(filaments, k1_parent, { "Creality K1C 0.6 nozzle" }, { k1_old_parent }); + filaments.update_map_system_profile_renamed(); + write_user_filament(temp_dir.path(), "Old PETG", k1_old_parent); + write_user_filament(temp_dir.path(), "New PETG", k1_parent); + write_user_filament(temp_dir.path(), "Own list PETG", k1_old_parent, R"(,"compatible_printers":["Creality K1C 0.6 nozzle"])"); + write_user_filament(temp_dir.path(), "Narrowed PETG", k1_old_parent, R"(,"compatible_printers":["Creality K1C 0.8 nozzle"])"); + write_user_filament(temp_dir.path(), "Empty list PETG", k1_old_parent, R"(,"compatible_printers":[])"); + load_user_filaments(filaments, temp_dir.path()); + + // The map is keyed by the parent's old name, which only the file still holds: a preset made against the + // parent's current name is left alone, as is one whose file sets compatible_printers at all, whether to the + // parent's list, to other printers or to an empty list. A printer the parent still lists is not reported again. + const std::vector legacy_presets = + filaments.legacy_user_filament_presets({ { k1_old_parent, { "Creality K1C 0.4 nozzle", "Creality K1C 0.6 nozzle" } } }); + REQUIRE(legacy_presets.size() == 1); + CHECK(legacy_presets[0].preset == "Old PETG"); + CHECK(legacy_presets[0].parent == k1_parent); + CHECK(legacy_presets[0].inherits == k1_old_parent); + CHECK(legacy_presets[0].printers == std::vector{ "Creality K1C 0.4 nozzle" }); +} + +TEST_CASE("Migrating a user filament backs up the user folder to a zip and writes the legacy printers to its file", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + ScopedDataDir data(temp_dir.path()); + const fs::path user_dir = temp_dir.path() / PRESET_USER_DIR / "default"; + const fs::path file = write_user_filament(user_dir, "My PETG", k1_parent); + const std::string original = read_file(file); + PresetBundle bundle; + add_system_filament(bundle.filaments, k1_parent, { "Creality K1C 0.6 nozzle" }); + load_user_filaments(bundle.filaments, user_dir); + const std::map> map{ { k1_parent, { "Creality K1C 0.4 nozzle" } } }; + const std::vector legacy_presets = bundle.filaments.legacy_user_filament_presets(map); + REQUIRE(legacy_presets.size() == 1); + + std::vector failed; + const std::string backup = bundle.migrate_legacy_user_filament_presets(legacy_presets, failed); + REQUIRE_FALSE(backup.empty()); + CHECK(std::regex_match(fs::path(backup).filename().string(), std::regex("filament_preset_migration_\\d{8}T\\d{6}Z_backup\\.zip"))); + CHECK(failed.empty()); + const fs::path extracted = temp_dir.path() / "extracted"; + REQUIRE(extract_archive_confined(backup, extracted.string())); + CHECK(read_file(extracted / PRESET_USER_DIR / "default" / PRESET_FILAMENT_NAME / "My PETG.json") == original); + + // Reloaded from disk, the preset carries both printers itself and needs no further migration. + PresetBundle reloaded; + add_system_filament(reloaded.filaments, k1_parent, { "Creality K1C 0.6 nozzle" }); + load_user_filaments(reloaded.filaments, user_dir); + const std::vector expected{ "Creality K1C 0.6 nozzle", "Creality K1C 0.4 nozzle" }; + CHECK(reloaded.filaments.find_preset("My PETG")->config.option("compatible_printers")->values == expected); + CHECK(reloaded.filaments.legacy_user_filament_presets(map).empty()); + CHECK_FALSE(reloaded.filaments.migrate_legacy_user_filament_preset(legacy_presets[0])); +} + TEST_CASE("Migrating a user filament keeps the old name of its renamed parent", "[Preset][Bundle]") { ScopedTemporaryDir temp_dir; RenameTestFilaments filaments; - const std::string parent = "Generic PETG @Creality K1-all"; - const std::string old_parent = "Creality Generic PETG @K1-all"; - add_inmemory_preset(filaments, parent).is_system = true; - filaments.find_preset(parent)->config.option("compatible_printers", true)->values = { "Creality K1C 0.6 nozzle" }; - set_renamed_from(filaments, parent, { old_parent }); + add_system_filament(filaments, k1_parent, { "Creality K1C 0.6 nozzle" }, { k1_old_parent }); filaments.update_map_system_profile_renamed(); - const fs::path file = temp_dir.path() / PRESET_FILAMENT_NAME / "My PETG.json"; - fs::create_directories(file.parent_path()); - std::ofstream(file.string()) << R"({"type":"filament","name":"My PETG","from":"User","version":"1.0.0","inherits":")" << old_parent << R"("})"; - PresetsConfigSubstitutions substitutions; - filaments.load_presets(temp_dir.path().string(), PRESET_FILAMENT_NAME, substitutions, ForwardCompatibilitySubstitutionRule::EnableSilent); + const fs::path file = write_user_filament(temp_dir.path(), "My PETG", k1_old_parent); + load_user_filaments(filaments, temp_dir.path()); - const std::vector migrations = filaments.legacy_printer_migrations({ { parent, { "Creality K1C 0.4 nozzle" } } }); - REQUIRE(migrations.size() == 1); - REQUIRE(filaments.apply_legacy_printer_migration(migrations[0])); + const std::vector legacy_presets = + filaments.legacy_user_filament_presets({ { k1_old_parent, { "Creality K1C 0.4 nozzle" } } }); + REQUIRE(legacy_presets.size() == 1); + REQUIRE(filaments.migrate_legacy_user_filament_preset(legacy_presets[0])); // An older release knows the parent only by its old name. const std::string saved = read_file(file); - CHECK(saved.find("\"inherits\": \"" + old_parent + "\"") != std::string::npos); + CHECK(saved.find("\"inherits\": \"" + k1_old_parent + "\"") != std::string::npos); CHECK(saved.find("Creality K1C 0.4 nozzle") != std::string::npos); } + +TEST_CASE("Migrating a user filament leaves a file another instance changed after it was found", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + RenameTestFilaments filaments; + add_system_filament(filaments, k1_parent, { "Creality K1C 0.6 nozzle" }); + const fs::path file = write_user_filament(temp_dir.path(), "My PETG", k1_parent); + load_user_filaments(filaments, temp_dir.path()); + const std::vector legacy_presets = + filaments.legacy_user_filament_presets({ { k1_parent, { "Creality K1C 0.4 nozzle" } } }); + REQUIRE(legacy_presets.size() == 1); + CHECK(legacy_presets[0].inherits == k1_parent); + + SECTION("to set its own printers") { + write_user_filament(temp_dir.path(), "My PETG", k1_parent, R"(,"compatible_printers":["Creality K1C 0.8 nozzle"])"); + } + SECTION("to inherit another parent") { + write_user_filament(temp_dir.path(), "My PETG", "Generic PETG @System"); + } + const std::string edited = read_file(file); + CHECK_FALSE(filaments.migrate_legacy_user_filament_preset(legacy_presets[0])); + CHECK(read_file(file) == edited); +} + +TEST_CASE("Migrating a user filament writes nothing while it can not take the user presets lock", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + RenameTestFilaments filaments; + add_system_filament(filaments, k1_parent, { "Creality K1C 0.6 nozzle" }); + const fs::path file = write_user_filament(temp_dir.path(), "My PETG", k1_parent); + const std::string original = read_file(file); + load_user_filaments(filaments, temp_dir.path()); + const std::vector legacy_presets = + filaments.legacy_user_filament_presets({ { k1_parent, { "Creality K1C 0.4 nozzle" } } }); + REQUIRE(legacy_presets.size() == 1); + + // A folder in the lock file's place can not be opened as the lock. + ScopedDataDir data(temp_dir.path() / "data"); + fs::create_directories(fs::path(user_presets_lock_path())); + CHECK_FALSE(filaments.migrate_legacy_user_filament_preset(legacy_presets[0])); + CHECK(read_file(file) == original); +} + +TEST_CASE("Migrating the selected user filament keeps a printer list edit pending in the settings tab", "[Preset][Bundle]") +{ + ScopedTemporaryDir temp_dir; + RenameTestFilaments filaments; + add_system_filament(filaments, k1_parent, { "Creality K1C 0.6 nozzle" }); + write_user_filament(temp_dir.path(), "My PETG", k1_parent); + load_user_filaments(filaments, temp_dir.path()); + REQUIRE(filaments.select_preset_by_name("My PETG", true)); + const std::vector legacy_presets = + filaments.legacy_user_filament_presets({ { k1_parent, { "Creality K1C 0.4 nozzle" } } }); + REQUIRE(legacy_presets.size() == 1); + const std::vector migrated{ "Creality K1C 0.6 nozzle", "Creality K1C 0.4 nozzle" }; + auto edited_printers = [&filaments]() -> std::vector & { + return filaments.get_edited_preset().config.option("compatible_printers", true)->values; + }; + + std::vector expected_edited = migrated; + bool expected_dirty = false; + SECTION("an unedited list follows the saved one") {} + SECTION("an edited list stays as edited") { + edited_printers() = { "Creality K1C 0.8 nozzle" }; + expected_edited = edited_printers(); + expected_dirty = true; + } + REQUIRE(filaments.migrate_legacy_user_filament_preset(legacy_presets[0])); + // find_preset() returns the edited copy of the selected preset unless asked for the real one. + CHECK(filaments.find_preset("My PETG", false, true)->config.option("compatible_printers")->values == migrated); + CHECK(edited_printers() == expected_edited); + // What marks the project as having unsaved preset changes. + CHECK(filaments.saved_is_dirty() == expected_dirty); +}