diff --git a/resources/images/menu_export_config.svg b/resources/images/menu_export_config.svg new file mode 100644 index 000000000..1f09490f1 --- /dev/null +++ b/resources/images/menu_export_config.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index c71f4b771..14416cdef 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1824,9 +1824,12 @@ std::pair PresetCollection::load_external_preset( Preset& PresetCollection::load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select) { + lock(); auto it = this->find_preset_internal(name); if (it == m_presets.end() || it->name != name) { // The preset was not found. Create a new preset. + if (m_presets.begin() + m_idx_selected >= it) + ++m_idx_selected; it = m_presets.emplace(it, Preset(m_type, name, false)); } Preset &preset = *it; @@ -1836,6 +1839,7 @@ Preset& PresetCollection::load_preset(const std::string &path, const std::string preset.is_dirty = false; if (select) this->select_preset_by_name(name, true); + unlock(); //BBS: add config related logs BOOST_LOG_TRIVIAL(debug) << __FUNCTION__ << boost::format(", preset type %1%, name %2%, path %3%, is_system %4%, is_default %5% is_visible %6%")%Preset::get_type_string(m_type) %preset.name %preset.file %preset.is_system %preset.is_default %preset.is_visible; return preset; diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index 145535eb0..716a8b0d4 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -592,6 +592,102 @@ PresetsConfigSubstitutions PresetBundle::load_user_presets(AppConfig &config, st return substitutions; } +PresetsConfigSubstitutions PresetBundle::import_presets(std::vector & files, + std::function override_confirm, + ForwardCompatibilitySubstitutionRule rule) +{ + PresetsConfigSubstitutions substitutions; + int overwrite = 0; + std::vector result; + for (auto &file : files) { + if (Slic3r::is_json_file(file)) { + try { + DynamicPrintConfig config; + // BBS: change to json format + // ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); + std::map key_values; + std::string reason; + ConfigSubstitutions config_substitutions = config.load_from_json(file, rule, key_values, reason); + std::string name = key_values[BBL_JSON_KEY_NAME]; + std::string version_str = key_values[BBL_JSON_KEY_VERSION]; + boost::optional version = Semver::parse(version_str); + if (!version) continue; + Semver app_version = *(Semver::parse(SLIC3R_VERSION)); + if (version->maj() != app_version.maj()) { + BOOST_LOG_TRIVIAL(warning) << "Preset incompatibla, not loading: " << name; + continue; + } + + PresetCollection * collection = nullptr; + if (config.has("printer_settings_id")) + collection = &printers; + else if (config.has("print_settings_id")) + collection = &prints; + else if (config.has("filament_settings_id")) + collection = &filaments; + if (collection == nullptr) { + BOOST_LOG_TRIVIAL(warning) << "Preset type is unknown, not loading: " << name; + continue; + } + if (auto p = collection->find_preset(name, false)) { + if (p->is_default || p->is_system) { + BOOST_LOG_TRIVIAL(warning) << "Preset already present and is system preset, not loading: " << name; + continue; + } + overwrite = override_confirm(name); + if (overwrite == 0 || overwrite == 2) { + BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name; + continue; + } + } + + DynamicPrintConfig new_config; + Preset * inherit_preset = nullptr; + ConfigOption *inherits_config = config.option(BBL_JSON_KEY_INHERITS); + if (inherits_config) { + ConfigOptionString *option_str = dynamic_cast(inherits_config); + std::string inherits_value = option_str->value; + inherit_preset = collection->find_preset(inherits_value, false, true); + } + if (inherit_preset) { + new_config = inherit_preset->config; + } else { + // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. + // new_config = default_preset.config; + // we should skip this preset here + BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(", can not find inherit preset for user preset %1%, just skip") % name; + continue; + } + new_config.apply(std::move(config)); + + Preset &preset = collection->load_preset(collection->path_from_name(name), name, std::move(new_config), false); + preset.is_external = true; + preset.version = *version; + if (inherit_preset) + preset.base_id = inherit_preset->setting_id; + Preset::normalize(preset.config); + // Report configuration fields, which are misplaced into a wrong group. + const Preset &default_preset = collection->default_preset_for(new_config); + std::string incorrect_keys = Preset::remove_invalid_keys(preset.config, default_preset.config); + if (!incorrect_keys.empty()) + BOOST_LOG_TRIVIAL(error) << "Error in a preset file: The preset \"" << preset.file << "\" contains the following incorrect keys: " << incorrect_keys + << ", which were removed"; + if (!config_substitutions.empty()) + substitutions.push_back({name, collection->type(), PresetConfigSubstitutions::Source::UserFile, file, std::move(config_substitutions)}); + + preset.save(inherit_preset ? &inherit_preset->config : nullptr); + result.push_back(file); + } catch (const std::ifstream::failure &err) { + BOOST_LOG_TRIVIAL(error) << boost::format("The config cannot be loaded: %1%. Reason: %2%") % file % err.what(); + } catch (const std::runtime_error &err) { + BOOST_LOG_TRIVIAL(error) << boost::format("Failed importing config file: %1%. Reason: %2%") % file % err.what(); + } + } + } + files = result; + return substitutions; +} + //BBS save user preset to user_id preset folder void PresetBundle::save_user_presets(AppConfig& config, std::vector& need_to_delete_list) { @@ -3213,6 +3309,36 @@ void PresetBundle::update_compatible(PresetSelectCompatibleType select_other_pri } }*/ +std::vector PresetBundle::export_current_configs(const std::string & path, + std::function override_confirm, + bool include_modify, + bool export_system_settings) +{ + const Preset &print_preset = include_modify ? prints.get_edited_preset() : prints.get_selected_preset(); + const Preset &printer_preset = include_modify ? printers.get_edited_preset() : printers.get_selected_preset(); + std::set presets { &print_preset, &printer_preset }; + for (auto &f : filament_presets) { + auto filament_preset = filaments.find_preset(f, include_modify); + if (filament_preset) presets.insert(filament_preset); + } + + int overwrite = 0; + std::vector result; + for (auto preset : presets) { + if ((preset->is_system && !export_system_settings) || preset->is_default) + continue; + std::string file = path + "/" + preset->name + ".json"; + if (boost::filesystem::exists(file) && overwrite < 2) { + overwrite = override_confirm(preset->name); + if (overwrite == 0 || overwrite == 2) + continue; + } + preset->config.save_to_json(file, preset->name, "", preset->version.to_string()); + result.push_back(file); + } + return result; +} + // Set the filament preset name. As the name could come from the UI selection box, // an optional "(modified)" suffix will be removed from the filament name. void PresetBundle::set_filament_preset(size_t idx, const std::string &name) diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index cb279f004..c578bbfeb 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -46,6 +46,7 @@ public: // BBS Load user presets PresetsConfigSubstitutions load_user_presets(AppConfig &config, std::map>& my_presets, ForwardCompatibilitySubstitutionRule rule); + PresetsConfigSubstitutions import_presets(std::vector &files, std::function override_confirm, ForwardCompatibilitySubstitutionRule rule); void save_user_presets(AppConfig& config, std::vector& need_to_delete_list); void remove_users_preset(AppConfig &config); void update_user_presets_directory(const std::string preset_folder); @@ -164,6 +165,8 @@ public: //void export_current_configbundle(const std::string &path); //BBS: add a function to export system presets for cloud-slicer //void export_system_configs(const std::string &path); + std::vector export_current_configs(const std::string &path, std::function override_confirm, + bool include_modify, bool export_system_settings = false); // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index dfb4986ec..8cd745201 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -1737,18 +1737,24 @@ void MainFrame::init_menubar_as_editor() fileMenu->AppendSeparator(); - //BBS + // BBS + wxMenu *import_menu = new wxMenu(); #ifdef __WINDOWS__ - append_menu_item(fileMenu, wxID_ANY, _L("Import 3MF/STL/STEP/OBJ/AMF") + dots + "\tCtrl+I", _L("Load a model"), + append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/OBJ/AMF") + dots + "\tCtrl+I", _L("Load a model"), [this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "menu_import", nullptr, [this](){return can_add_models(); }, this); #else - append_menu_item(fileMenu, wxID_ANY, _L("Import 3MF/STL/STEP/OBJ/AMF") + dots + "\tCtrl+I", _L("Load a model"), + append_menu_item(import_menu, wxID_ANY, _L("Import 3MF/STL/STEP/OBJ/AMF") + dots + "\tCtrl+I", _L("Load a model"), [this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "", nullptr, [this](){return can_add_models(); }, this); #endif + append_menu_item(import_menu, wxID_ANY, _L("Import Configs") + dots /*+ "\tCtrl+I"*/, _L("Load configs"), + [this](wxCommandEvent&) { load_config_file(); }, "menu_import", nullptr, + [this](){return true; }, this); + + append_submenu(fileMenu, import_menu, wxID_ANY, _L("Import"), ""); wxMenu* export_menu = new wxMenu(); @@ -1763,6 +1769,11 @@ void MainFrame::init_menubar_as_editor() append_menu_item(export_menu, wxID_ANY, _L("Export G-code") + dots/* + "\tCtrl+G"*/, _L("Export current plate as G-code"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "menu_export_gcode", nullptr, [this]() {return can_export_gcode(); }, this); + append_menu_item( + export_menu, wxID_ANY, _L("Export &Configs") + dots /* + "\tCtrl+E"*/, _L("Export current configuration to files"), + [this](wxCommandEvent &) { export_config(); }, + "menu_export_config", nullptr, + []() { return true; }, this); append_submenu(fileMenu, export_menu, wxID_ANY, _L("Export"), ""); @@ -2213,28 +2224,48 @@ void MainFrame::reslice_now() m_plater->reslice(); } +struct ConfigsOverwriteConfirmDialog : MessageDialog +{ + ConfigsOverwriteConfirmDialog(wxWindow *parent, wxString name, bool exported) + : MessageDialog(parent, + wxString::Format(exported ? _("A file exists with the same name: %s, do you wan't to override it.") : + _("A config exists with the same name: %s, do you wan't to override it."), + name), + _L(exported ? "Overwrite file" : "Overwrite config"), + wxYES_NO | wxNO_DEFAULT) + { + add_button(wxID_YESTOALL, false, _L("Yes to All")); + add_button(wxID_NOTOALL, false, _L("No to All")); + } +}; + void MainFrame::export_config() { // Generate a cummulative configuration for the selected print, filaments and printer. - auto config = wxGetApp().preset_bundle->full_config(); - // Validate the cummulative configuration. - auto valid = config.validate(); - if (! valid.empty()) { - show_error(this, valid); - return; - } - // Ask user for the file name for the config file. - wxFileDialog dlg(this, _L("Save configuration as:"), - !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), - !m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini", - file_wildcards(FT_INI), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); - wxString file; + wxDirDialog dlg(this, _L("Choose a directory"), + from_u8(!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir()), wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST); + wxString path; if (dlg.ShowModal() == wxID_OK) - file = dlg.GetPath(); - if (!file.IsEmpty()) { - wxGetApp().app_config->update_config_dir(get_dir_name(file)); - m_last_config = file; - config.save(file.ToUTF8().data()); + path = dlg.GetPath(); + if (!path.IsEmpty()) { + // Export the config bundle. + wxGetApp().app_config->update_config_dir(into_u8(path)); + try { + auto files = wxGetApp().preset_bundle->export_current_configs(into_u8(path), [this](std::string const & name) { + ConfigsOverwriteConfirmDialog dlg(this, from_u8(name), true); + int res = dlg.ShowModal(); + int ids[]{wxID_NO, wxID_YES, wxID_NOTOALL, wxID_YESTOALL}; + return std::find(ids, ids + 4, res) - ids; + }, false); + if (!files.empty()) + m_last_config = from_u8(files.back()); + MessageDialog dlg(this, wxString::Format(_L_PLURAL("There is %d config exported. (Only non-system configs)", + "There are %d configs exported. (Only non-system configs)", files.size()), files.size()), + _L("Export result"), wxOK); + dlg.ShowModal(); + } catch (const std::exception &ex) { + show_error(this, ex.what()); + } } } @@ -2244,16 +2275,34 @@ void MainFrame::load_config_file() //BBS do not load config file // if (!wxGetApp().check_and_save_current_preset_changes(_L("Loading profile file"), "", false)) // return; - // wxFileDialog dlg(this, _L("Select profile to load:"), - // !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), - // "config.json", "INI files (*.ini, *.gcode)|*.json;;*.gcode;*.g", wxFD_OPEN | wxFD_FILE_MUST_EXIST); - // wxString file; - // if (dlg.ShowModal() == wxID_OK) - // file = dlg.GetPath(); - // if (! file.IsEmpty() && this->load_config_file(file.ToUTF8().data())) { - // wxGetApp().app_config->update_config_dir(get_dir_name(file)); - // m_last_config = file; - // } + wxFileDialog dlg(this, _L("Select profile to load:"), + !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), + "config.json", "Config files (*.json)|*.json", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); + wxArrayString files; + if (dlg.ShowModal() != wxID_OK) + return; + dlg.GetPaths(files); + std::vector cfiles; + for (auto file : files) { + cfiles.push_back(into_u8(file)); + m_last_config = file; + } + bool update = false; + wxGetApp().preset_bundle->import_presets(cfiles, [this](std::string const & name) { + ConfigsOverwriteConfirmDialog dlg(this, from_u8(name), false); + int res = dlg.ShowModal(); + int ids[]{wxID_NO, wxID_YES, wxID_NOTOALL, wxID_YESTOALL}; + return std::find(ids, ids + 4, res) - ids; + }, + ForwardCompatibilitySubstitutionRule::Enable); + if (!cfiles.empty()) { + wxGetApp().app_config->update_config_dir(get_dir_name(cfiles.back())); + wxGetApp().load_current_presets(); + } + MessageDialog dlg2(this, wxString::Format(_L_PLURAL("There is %d config imported. (Only non-system and compatible configs)", + "There are %d configs imported. (Only non-system and compatible configs)", cfiles.size()), cfiles.size()), + _L("Import result"), wxOK); + dlg2.ShowModal(); } // Load a config file containing a Print, Filament & Printer preset from command line. @@ -2737,7 +2786,7 @@ std::string MainFrame::get_base_name(const wxString &full_name, const char *exte std::string MainFrame::get_dir_name(const wxString &full_name) const { - return boost::filesystem::path(full_name.wx_str()).parent_path().string(); + return boost::filesystem::path(into_u8(full_name)).parent_path().string(); }