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();
}