diff --git a/My Settings b/My Settings new file mode 100644 index 000000000..f896db767 --- /dev/null +++ b/My Settings @@ -0,0 +1,27 @@ +# generated by Slic3r Prusa Edition 1.38.4 on 2017-12-20 at 11:02:03 +bed_temperature = 0 +bridge_fan_speed = 100 +compatible_printers = +compatible_printers_condition = +cooling = 1 +disable_fan_first_layers = 3 +end_filament_gcode = "; Filament-specific end gcode \n;END gcode for filament\n" +extrusion_multiplier = 1 +fan_always_on = 0 +fan_below_layer_time = 60 +filament_colour = #29b2b2 +filament_cost = 0 +filament_density = 0 +filament_diameter = 3 +filament_max_volumetric_speed = 0 +filament_notes = "" +filament_soluble = 0 +filament_type = PLA +first_layer_bed_temperature = 0 +first_layer_temperature = 205 +max_fan_speed = 100 +min_fan_speed = 35 +min_print_speed = 10 +slowdown_below_layer_time = 5 +start_filament_gcode = "; Filament gcode\n" +temperature = 200 diff --git a/lib/Slic3r/GUI.pm b/lib/Slic3r/GUI.pm index 74456ca2f..4ec388c14 100644 --- a/lib/Slic3r/GUI.pm +++ b/lib/Slic3r/GUI.pm @@ -106,12 +106,11 @@ sub OnInit { # Suppress the '- default -' presets. $self->{preset_bundle}->set_default_suppressed($self->{app_config}->get('no_defaults') ? 1 : 0); - eval { $self->{preset_bundle}->load_presets }; + eval { $self->{preset_bundle}->load_presets($self->{app_config}); }; if ($@) { warn $@ . "\n"; show_error(undef, $@); } - eval { $self->{preset_bundle}->load_selections($self->{app_config}) }; $run_wizard = 1 if $self->{preset_bundle}->has_defauls_only; Slic3r::GUI::set_preset_bundle($self->{preset_bundle}); diff --git a/lib/Slic3r/GUI/MainFrame.pm b/lib/Slic3r/GUI/MainFrame.pm index 4868ed7e2..b2f51b9e1 100644 --- a/lib/Slic3r/GUI/MainFrame.pm +++ b/lib/Slic3r/GUI/MainFrame.pm @@ -666,7 +666,13 @@ sub config_wizard { wxTheApp->{preset_bundle}->load_config('My Settings', $result->{config}); } else { # Wizard returned a name of a preset bundle bundled with the installation. Unpack it. - wxTheApp->{preset_bundle}->load_configbundle($directory . '/' . $result->{preset_name} . '.ini'); + wxTheApp->{preset_bundle}->install_vendor_configbundle($directory . '/' . $result->{preset_name} . '.ini'); + # Reset the print / filament / printer selections, so that following line will select some sensible defaults. + if ($fresh_start) { + wxTheApp->{app_config}->reset_selections; + } + # Reload all presets after the vendor config bundle has been installed. + wxTheApp->{preset_bundle}->load_presets(wxTheApp->{app_config}); } }; Slic3r::GUI::catch_error($self) and return; diff --git a/resources/icons/lock.png b/resources/icons/lock.png new file mode 100644 index 000000000..2ebc4f6f9 Binary files /dev/null and b/resources/icons/lock.png differ diff --git a/resources/icons/lock_open.png b/resources/icons/lock_open.png new file mode 100644 index 000000000..a471765ff Binary files /dev/null and b/resources/icons/lock_open.png differ diff --git a/resources/icons/sys_lock.png b/resources/icons/sys_lock.png new file mode 100644 index 000000000..0519c44a1 Binary files /dev/null and b/resources/icons/sys_lock.png differ diff --git a/resources/icons/sys_unlock.png b/resources/icons/sys_unlock.png new file mode 100644 index 000000000..189bc24e7 Binary files /dev/null and b/resources/icons/sys_unlock.png differ diff --git a/resources/localization/uk/Slic3rPE.mo b/resources/localization/uk/Slic3rPE.mo index c980ae64d..7ced15dc8 100644 Binary files a/resources/localization/uk/Slic3rPE.mo and b/resources/localization/uk/Slic3rPE.mo differ diff --git a/resources/localization/uk/Slic3rPE_uk.po b/resources/localization/uk/Slic3rPE_uk.po index 60354fd61..24b12095d 100644 --- a/resources/localization/uk/Slic3rPE_uk.po +++ b/resources/localization/uk/Slic3rPE_uk.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-02-28 13:53+0100\n" -"PO-Revision-Date: 2018-02-28 14:04+0100\n" +"PO-Revision-Date: 2018-03-21 16:01+0100\n" "Last-Translator: Oleksandra Iushchenko \n" "Language-Team: \n" "MIME-Version: 1.0\n" @@ -3330,7 +3330,7 @@ msgstr "" #: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:137 msgid "None" -msgstr "Жадне" +msgstr "Жодне" #: c:\src\Slic3r\xs\src\libslic3r\GCode\PreviewData.cpp:138 #: c:\src\Slic3r\lib\Slic3r\GUI\Plater\3DPreview.pm:80 @@ -3718,11 +3718,11 @@ msgstr "Файл" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:419 msgid "&Plater" -msgstr "Платер" +msgstr "&Платер" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:420 msgid "&Object" -msgstr "Об'єкт" +msgstr "&Об'єкт" #: c:\src\Slic3r\lib\Slic3r\GUI\MainFrame.pm:421 msgid "&Window" diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 43d2ecbd1..fa4d48cda 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -909,10 +909,10 @@ use_volumetric_e = 0 variable_layer_height = 1 wipe = 1 z_offset = 0 -#printer_model = MK2S -#printer_variant = 0.4 -#default_print_profile = 0.15mm OPTIMAL -#default_filament_profile = Prusa PLA +printer_model = MK2S +printer_variant = 0.4 +default_print_profile = 0.15mm OPTIMAL +default_filament_profile = Prusa PLA [printer:*multimaterial*] inherits = *common* @@ -928,7 +928,7 @@ retract_restart_extra = 0 retract_restart_extra_toolchange = 0 retract_speed = 80 single_extruder_multi_material = 1 -#printer_model = MK2SMM +printer_model = MK2SMM [printer:*mm-single*] inherits = *multimaterial* @@ -956,15 +956,15 @@ nozzle_diameter = 0.25 retract_length = 1 retract_speed = 50 variable_layer_height = 0 -#printer_variant = 0.25 -#default_print_profile = 0.10mm DETAIL 0.25 nozzle +printer_variant = 0.25 +default_print_profile = 0.10mm DETAIL 0.25 nozzle [printer:Original Prusa i3 MK2 0.6 nozzle] inherits = *common* max_layer_height = 0.35 min_layer_height = 0.1 nozzle_diameter = 0.6 -#printer_variant = 0.6 +printer_variant = 0.6 [printer:Original Prusa i3 MK2 MM Single Mode] inherits = *mm-single* @@ -972,7 +972,7 @@ inherits = *mm-single* [printer:Original Prusa i3 MK2 MM Single Mode 0.6 nozzle] inherits = *mm-single* nozzle_diameter = 0.6 -#printer_variant = 0.6 +printer_variant = 0.6 [printer:Original Prusa i3 MK2 MultiMaterial] inherits = *mm-multi* @@ -981,7 +981,7 @@ nozzle_diameter = 0.4,0.4,0.4,0.4 [printer:Original Prusa i3 MK2 MultiMaterial 0.6 nozzle] inherits = *mm-multi* nozzle_diameter = 0.6,0.6,0.6,0.6 -#printer_variant = 0.6 +printer_variant = 0.6 [printer:Original Prusa i3 MK3] inherits = *common* @@ -989,8 +989,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} -#printer_model = MK3 -#default_print_profile = 0.15mm OPTIMAL MK3 +printer_model = MK3 +default_print_profile = 0.15mm OPTIMAL MK3 [printer:Original Prusa i3 MK3 0.25 nozzle] inherits = *common* @@ -999,8 +999,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} -#printer_model = MK3 -#default_print_profile = 0.10mm DETAIL MK3 +printer_model = MK3 +default_print_profile = 0.10mm DETAIL MK3 [printer:Original Prusa i3 MK3 0.6 nozzle] inherits = *common* @@ -1009,8 +1009,8 @@ end_gcode = G4 ; wait\nM221 S100\nM104 S0 ; turn off temperature\nM140 S0 ; turn printer_notes = Don't remove the following keywords! These keywords are used in the "compatible printer" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_VENDOR_PRUSA3D\nPRINTER_MODEL_MK3\n retract_lift_below = 209 start_gcode = M115 U3.1.1-RC5 ; tell printer latest fw version\nM201 X1000 Y1000 Z200 E5000 ; sets maximum accelerations, mm/sec^2\nM203 X200 Y200 Z12 E120 ; sets maximum feedrates, mm/sec\nM204 S1250 T1250 ; sets acceleration (S) and retract acceleration (T)\nM205 X10 Y10 Z0.4 E2.5 ; sets the jerk limits, mm/sec\nM205 S0 T0 ; sets the minimum extruding and travel feed rate, mm/sec\nM83 ; extruder relative mode\nM104 S[first_layer_temperature] ; set extruder temp\nM140 S[first_layer_bed_temperature] ; set bed temp\nM190 S[first_layer_bed_temperature] ; wait for bed temp\nM109 S[first_layer_temperature] ; wait for extruder temp\nG28 W ; home all without mesh bed level\nG80 ; mesh bed leveling\nG1 Y-3.0 F1000.0 ; go outside print area\nG92 E0.0\nG1 X60.0 E9.0 F1000.0 ; intro line\nG1 X100.0 E12.5 F1000.0 ; intro line\nG92 E0.0\nM221 S{if layer_height==0.05}100{else}95{endif} -#printer_model = MK3 -#default_print_profile = 0.15mm OPTIMAL MK3 +printer_model = MK3 +default_print_profile = 0.15mm OPTIMAL MK3 [presets] print = 0.15mm OPTIMAL MK3 diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index ca398da5a..f28db8f92 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -171,6 +171,8 @@ add_library(libslic3r STATIC add_library(libslic3r_gui STATIC ${LIBDIR}/slic3r/GUI/AppConfig.cpp ${LIBDIR}/slic3r/GUI/AppConfig.hpp + ${LIBDIR}/slic3r/GUI/BitmapCache.cpp + ${LIBDIR}/slic3r/GUI/BitmapCache.hpp ${LIBDIR}/slic3r/GUI/3DScene.cpp ${LIBDIR}/slic3r/GUI/3DScene.hpp ${LIBDIR}/slic3r/GUI/GLShader.cpp diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 1a0b8fb4a..a4eaf3072 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -185,7 +185,7 @@ void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys // This is only possible if other is of DynamicConfig type. if (ignore_nonexistent) continue; - throw UnknownOptionException(); + throw UnknownOptionException(opt_key); } const ConfigOption *other_opt = other.option(opt_key); if (other_opt != nullptr) @@ -206,6 +206,18 @@ t_config_option_keys ConfigBase::diff(const ConfigBase &other) const return diff; } +t_config_option_keys ConfigBase::equal(const ConfigBase &other) const +{ + t_config_option_keys equal; + for (const t_config_option_key &opt_key : this->keys()) { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt == *other_opt) + equal.emplace_back(opt_key); + } + return equal; +} + std::string ConfigBase::serialize(const t_config_option_key &opt_key) const { const ConfigOption* opt = this->option(opt_key); @@ -232,7 +244,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con // Try to deserialize the option by its name. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. @@ -248,7 +260,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con break; } if (optdef == nullptr) - throw UnknownOptionException(); + throw UnknownOptionException(opt_key); } if (! optdef->shortcut.empty()) { @@ -278,7 +290,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const // Get option definition. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + throw NoDefinitionException(opt_key); const ConfigOptionDef *opt_def = def->get(opt_key); assert(opt_def != nullptr); // Compute absolute value over the absolute value of the base option. @@ -468,7 +480,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // Try to create a new ConfigOption. const ConfigDef *def = this->def(); if (def == nullptr) - throw NoDefinitionException(); + throw NoDefinitionException(opt_key); const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) // throw std::runtime_error(std::string("Invalid option name: ") + opt_key); diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 3dccedbf0..6eb307c5c 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -1046,6 +1046,7 @@ public: void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; + t_config_option_keys equal(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. @@ -1232,17 +1233,22 @@ protected: }; /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::exception -{ +class UnknownOptionException : public std::runtime_error { public: - const char* what() const noexcept override { return "Unknown config option"; } + UnknownOptionException() : + std::runtime_error("Unknown option exception") {} + UnknownOptionException(const std::string &opt_key) : + std::runtime_error(std::string("Unknown option exception: ") + opt_key) {} }; /// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). -class NoDefinitionException : public std::exception +class NoDefinitionException : public std::runtime_error { public: - const char* what() const noexcept override { return "No config definition"; } + NoDefinitionException() : + std::runtime_error("No definition exception") {} + NoDefinitionException(const std::string &opt_key) : + std::runtime_error(std::string("No definition exception: ") + opt_key) {} }; } diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index d7ee1e591..657e5a452 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -176,6 +176,18 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(0); + def = this->add("default_filament_profile", coStrings); + def->label = L("Default filament profile"); + def->tooltip = L("Default filament profile associated with the current printer profile. " + "On selection of the current printer profile, this filament profile will be activated."); + def->default_value = new ConfigOptionStrings(); + + def = this->add("default_print_profile", coString); + def->label = L("Default print profile"); + def->tooltip = L("Default print profile associated with the current printer profile. " + "On selection of the current printer profile, this print profile will be activated."); + def->default_value = new ConfigOptionString(); + def = this->add("disable_fan_first_layers", coInts); def->label = L("Disable fan for the first"); def->tooltip = L("You can set this to a positive value to disable fan at all " @@ -406,7 +418,7 @@ PrintConfigDef::PrintConfigDef() def->tooltip = L("This is only used in the Slic3r interface as a visual help."); def->cli = "filament-color=s@"; def->gui_type = "color"; - def->default_value = new ConfigOptionStrings { "#29b2b2" }; + def->default_value = new ConfigOptionStrings { "#29B2B2" }; def = this->add("filament_notes", coStrings); def->label = L("Filament notes"); @@ -753,6 +765,13 @@ PrintConfigDef::PrintConfigDef() def->min = 0; def->default_value = new ConfigOptionFloat(80); + def = this->add("inherits", coString); + def->label = L("Inherits profile"); + def->tooltip = L("Name of the profile, from which this profile inherits."); + def->full_width = true; + def->height = 50; + def->default_value = new ConfigOptionString(""); + def = this->add("interface_shells", coBool); def->label = L("Interface shells"); def->tooltip = L("Force the generation of solid shells between adjacent materials/volumes. " @@ -1022,7 +1041,12 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 60; - def->default_value = new ConfigOptionStrings{ "" }; + def->default_value = new ConfigOptionStrings(); + + def = this->add("printer_model", coString); + def->label = L("Printer type"); + def->tooltip = L("Type of the printer."); + def->default_value = new ConfigOptionString(); def = this->add("printer_notes", coString); def->label = L("Printer notes"); @@ -1033,6 +1057,16 @@ PrintConfigDef::PrintConfigDef() def->height = 130; def->default_value = new ConfigOptionString(""); + def = this->add("printer_vendor", coString); + def->label = L("Printer vendor"); + def->tooltip = L("Name of the printer vendor."); + def->default_value = new ConfigOptionString(); + + def = this->add("printer_variant", coString); + def->label = L("Printer variant"); + def->tooltip = L("Name of the printer variant. For example, the printer variants may be differentiated by a nozzle diameter."); + def->default_value = new ConfigOptionString(); + def = this->add("print_settings_id", coString); def->default_value = new ConfigOptionString(""); diff --git a/xs/src/slic3r/GUI/AppConfig.cpp b/xs/src/slic3r/GUI/AppConfig.cpp index 99339e2f3..e32b645b4 100644 --- a/xs/src/slic3r/GUI/AppConfig.cpp +++ b/xs/src/slic3r/GUI/AppConfig.cpp @@ -145,6 +145,17 @@ void AppConfig::update_last_output_dir(const std::string &dir) this->set("", "last_output_path", dir); } +void AppConfig::reset_selections() +{ + auto it = m_storage.find("presets"); + if (it != m_storage.end()) { + it->second.erase("print"); + it->second.erase("filament"); + it->second.erase("printer"); + m_dirty = true; + } +} + std::string AppConfig::config_path() { return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string(); diff --git a/xs/src/slic3r/GUI/AppConfig.hpp b/xs/src/slic3r/GUI/AppConfig.hpp index c6d7766a4..9b1d5a712 100644 --- a/xs/src/slic3r/GUI/AppConfig.hpp +++ b/xs/src/slic3r/GUI/AppConfig.hpp @@ -73,6 +73,11 @@ public: std::string get_last_output_dir(const std::string &alt) const; void update_last_output_dir(const std::string &dir); + // reset the current print / filament / printer selections, so that + // the PresetBundle::load_selections(const AppConfig &config) call will select + // the first non-default preset when called. + void reset_selections(); + // Get the default config path from Slic3r::data_dir(). static std::string config_path(); diff --git a/xs/src/slic3r/GUI/BedShapeDialog.cpp b/xs/src/slic3r/GUI/BedShapeDialog.cpp index 51dbd6a27..24d84a7df 100644 --- a/xs/src/slic3r/GUI/BedShapeDialog.cpp +++ b/xs/src/slic3r/GUI/BedShapeDialog.cpp @@ -183,7 +183,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) vertex_distances.push_back(distance); avg_dist += distance; } - + + avg_dist /= vertex_distances.size(); bool defined_value = true; for (auto el: vertex_distances) { diff --git a/xs/src/slic3r/GUI/BitmapCache.cpp b/xs/src/slic3r/GUI/BitmapCache.cpp new file mode 100644 index 000000000..f92369650 --- /dev/null +++ b/xs/src/slic3r/GUI/BitmapCache.cpp @@ -0,0 +1,120 @@ +#include "BitmapCache.hpp" + +namespace Slic3r { namespace GUI { + +void BitmapCache::clear() +{ + for (std::pair &bitmap : m_map) + delete bitmap.second; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height) +{ + wxBitmap *bitmap = nullptr; + auto it = m_map.find(bitmap_key); + if (it == m_map.end()) { + bitmap = new wxBitmap(width, height); + m_map[bitmap_key] = bitmap; + } else { + bitmap = it->second; + if (bitmap->GetWidth() != width || bitmap->GetHeight() != height) + bitmap->Create(width, height); + } +#if defined(__APPLE__) || defined(_MSC_VER) + bitmap->UseAlpha(); +#endif + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp) +{ + wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth(), bmp.GetHeight()); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + memDC.DrawBitmap(bmp, 0, 0, true); + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2) +{ + wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth() + bmp2.GetWidth(), std::max(bmp.GetHeight(), bmp2.GetHeight())); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, 0, 0, true); + if (bmp2.GetWidth() > 0) + memDC.DrawBitmap(bmp2, bmp.GetWidth(), 0, true); + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3) +{ + wxBitmap *bitmap = this->insert(bitmap_key, bmp.GetWidth() + bmp2.GetWidth() + bmp3.GetWidth(), std::max(std::max(bmp.GetHeight(), bmp2.GetHeight()), bmp3.GetHeight())); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, 0, 0, true); + if (bmp2.GetWidth() > 0) + memDC.DrawBitmap(bmp2, bmp.GetWidth(), 0, true); + if (bmp3.GetWidth() > 0) + memDC.DrawBitmap(bmp3, bmp.GetWidth() + bmp2.GetWidth(), 0, true); + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap* BitmapCache::insert(const std::string &bitmap_key, std::vector &bmps) +{ + size_t width = 0; + size_t height = 0; + for (wxBitmap &bmp : bmps) { + width += bmp.GetWidth(); + height = std::max(height, bmp.GetHeight()); + } + wxBitmap *bitmap = this->insert(bitmap_key, width, height); + + wxMemoryDC memDC; + memDC.SelectObject(*bitmap); + memDC.SetBackground(*wxTRANSPARENT_BRUSH); + memDC.Clear(); + size_t x = 0; + for (wxBitmap &bmp : bmps) { + if (bmp.GetWidth() > 0) + memDC.DrawBitmap(bmp, x, 0, true); + x += bmp.GetWidth(); + } + memDC.SelectObject(wxNullBitmap); + + return bitmap; +} + +wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency) +{ + wxImage image(width, height); + image.InitAlpha(); + unsigned char* imgdata = image.GetData(); + unsigned char* imgalpha = image.GetAlpha(); + for (size_t i = 0; i < width * height; ++ i) { + *imgdata ++ = r; + *imgdata ++ = g; + *imgdata ++ = b; + *imgalpha ++ = transparency; + } + return wxBitmap(std::move(image)); +} + +} // namespace GUI +} // namespace Slic3r diff --git a/xs/src/slic3r/GUI/BitmapCache.hpp b/xs/src/slic3r/GUI/BitmapCache.hpp new file mode 100644 index 000000000..0cf9d8acf --- /dev/null +++ b/xs/src/slic3r/GUI/BitmapCache.hpp @@ -0,0 +1,43 @@ +#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP +#define SLIC3R_GUI_BITMAP_CACHE_HPP + +#include +#ifndef WX_PRECOMP + #include +#endif + +#include "../../libslic3r/libslic3r.h" +#include "../../libslic3r/Config.hpp" + +#include "GUI.hpp" + +namespace Slic3r { namespace GUI { + +class BitmapCache +{ +public: + BitmapCache() {} + ~BitmapCache() { clear(); } + void clear(); + + wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; } + const wxBitmap* find(const std::string &name) const { return const_cast(this)->find(name); } + + wxBitmap* insert(const std::string &name, size_t width, size_t height); + wxBitmap* insert(const std::string &name, const wxBitmap &bmp); + wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2); + wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3); + wxBitmap* insert(const std::string &name, std::vector &bmps); + + static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency); + static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); } + static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); } + +private: + std::map m_map; +}; + +} // GUI +} // Slic3r + +#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */ diff --git a/xs/src/slic3r/GUI/Field.cpp b/xs/src/slic3r/GUI/Field.cpp index c2fc5e4e4..fee08e27e 100644 --- a/xs/src/slic3r/GUI/Field.cpp +++ b/xs/src/slic3r/GUI/Field.cpp @@ -19,16 +19,24 @@ namespace Slic3r { namespace GUI { } void Field::PostInitialize(){ - m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); - // use bouth of temporary_icons till don't have "undo_icon" auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - if (wxMSW) m_Undo_btn->SetBackgroundColour(color); + m_Undo_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_Undo_to_sys_btn = new wxButton(m_parent, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) { + m_Undo_btn->SetBackgroundColour(color); + m_Undo_to_sys_btn->SetBackgroundColour(color); + } m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); + m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); BUILD(); } + void Field::set_nonsys_btn_icon(const std::string& icon){ + m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); + } + void Field::on_kill_focus(wxEvent& event) { // Without this, there will be nasty focus bugs on Windows. // Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all @@ -45,12 +53,16 @@ namespace Slic3r { namespace GUI { m_on_change(m_opt_id, get_value()); } - void Field::on_back_to_initial_value() - { + void Field::on_back_to_initial_value(){ if (m_back_to_initial_value != nullptr && m_is_modified_value) m_back_to_initial_value(m_opt_id); } + void Field::on_back_to_sys_value(){ + if (m_back_to_sys_value != nullptr && m_is_nonsys_value) + m_back_to_sys_value(m_opt_id); + } + wxString Field::get_tooltip_text(const wxString& default_string) { wxString tooltip_text(""); @@ -161,7 +173,8 @@ namespace Slic3r { namespace GUI { temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e) { - on_kill_focus(e); +// on_kill_focus(e); + e.Skip(); temp->GetToolTip()->Enable(true); }), temp->GetId()); @@ -253,8 +266,8 @@ void SpinCtrl::BUILD() { auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, 0, min_val, max_val, default_value); - temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); - temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId()); +// temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId()); +// temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId()); temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { // # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value @@ -392,6 +405,7 @@ void Choice::set_value(boost::any value, bool change_event) case coInt: case coFloat: case coPercent: + case coString: case coStrings:{ wxString text_value; if (m_opt.type == coInt) @@ -405,7 +419,6 @@ void Choice::set_value(boost::any value, bool change_event) break; ++idx; } -// if (m_opt.type == coPercent) text_value += "%"; idx == m_opt.enum_values.size() ? dynamic_cast(window)->SetValue(text_value) : dynamic_cast(window)->SetSelection(idx); @@ -434,6 +447,7 @@ void Choice::set_values(const std::vector values) auto ww = dynamic_cast(window); auto value = ww->GetValue(); ww->Clear(); + ww->Append(""); for (auto el : values) ww->Append(wxString(el)); ww->SetValue(value); diff --git a/xs/src/slic3r/GUI/Field.hpp b/xs/src/slic3r/GUI/Field.hpp index 2ddb5d9f8..da3e23ccd 100644 --- a/xs/src/slic3r/GUI/Field.hpp +++ b/xs/src/slic3r/GUI/Field.hpp @@ -52,6 +52,8 @@ protected: void on_change_field(); /// Call the attached m_back_to_initial_value method. void on_back_to_initial_value(); + /// Call the attached m_back_to_sys_value method. + void on_back_to_sys_value(); public: /// parent wx item, opportunity to refactor (probably not necessary - data duplication) @@ -63,13 +65,14 @@ public: /// Function object to store callback passed in from owning object. t_change m_on_change {nullptr}; - /// Function object to store callback passed in from owning object. + /// Function object to store callback passed in from owning object. t_back_to_init m_back_to_initial_value{ nullptr }; + t_back_to_init m_back_to_sys_value{ nullptr }; // This is used to avoid recursive invocation of the field change/update by wxWidgets. bool m_disable_change_event {false}; - // This is used to avoid recursive invocation of the field change/update by wxWidgets. bool m_is_modified_value {false}; + bool m_is_nonsys_value {true}; /// Copy of ConfigOption for deduction purposes const ConfigOptionDef m_opt {ConfigOptionDef()}; @@ -89,12 +92,16 @@ public: wxStaticText* m_Label = nullptr; wxButton* m_Undo_btn = nullptr; + wxButton* m_Undo_to_sys_btn = nullptr; /// Fires the enable or disable function, based on the input. inline void toggle(bool en) { en ? enable() : disable(); } virtual wxString get_tooltip_text(const wxString& default_string); + // set icon to "UndoToSystemValue" button according to an inheritance of preset + void set_nonsys_btn_icon(const std::string& icon); + Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}; Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}; @@ -196,11 +203,12 @@ public: } void set_value(boost::any value, bool change_event = false) { m_disable_change_event = !change_event; - dynamic_cast(window)->SetValue(boost::any_cast(value)); + tmp_value = boost::any_cast(value); + dynamic_cast(window)->SetValue(tmp_value/*boost::any_cast(value)*/); m_disable_change_event = false; } boost::any get_value() override { - return boost::any(dynamic_cast(window)->GetValue()); + return boost::any(tmp_value); } void enable() override { dynamic_cast(window)->Enable(); } @@ -280,9 +288,7 @@ public: wxSizer* getSizer() override { return sizer; } }; - -#endif } // GUI } // Slic3r - +#endif /* SLIC3R_GUI_FIELD_HPP */ diff --git a/xs/src/slic3r/GUI/GUI.cpp b/xs/src/slic3r/GUI/GUI.cpp index 454852639..3eca4e707 100644 --- a/xs/src/slic3r/GUI/GUI.cpp +++ b/xs/src/slic3r/GUI/GUI.cpp @@ -428,8 +428,7 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b std::vector values = boost::any_cast>(value); if (values.size() == 1 && values[0] == "") break; - for (auto el : values) - config.option(opt_key)->values.push_back(el); + config.option(opt_key)->values = values; } else{ ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast(value) }; @@ -465,6 +464,10 @@ void change_opt_value(DynamicPrintConfig& config, t_config_option_key opt_key, b } break; case coPoints:{ + if (opt_key.compare("bed_shape") == 0){ + config.option(opt_key)->values = boost::any_cast>(value); + break; + } ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast(value) }; config.option(opt_key)->set_at(vec_new, opt_index, 0); } @@ -511,11 +514,14 @@ wxApp* get_app(){ return g_wxApp; } -wxColour* get_modified_label_clr() -{ +wxColour* get_modified_label_clr(){ return new wxColour(253, 88, 0); } +wxColour* get_sys_label_clr(){ + return new wxColour(26, 132, 57); +} + void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value) { if (comboCtrl == nullptr) diff --git a/xs/src/slic3r/GUI/GUI.hpp b/xs/src/slic3r/GUI/GUI.hpp index 7b23702b2..362b15307 100644 --- a/xs/src/slic3r/GUI/GUI.hpp +++ b/xs/src/slic3r/GUI/GUI.hpp @@ -80,6 +80,7 @@ void set_preset_bundle(PresetBundle *preset_bundle); AppConfig* get_app_config(); wxApp* get_app(); wxColour* get_modified_label_clr(); +wxColour* get_sys_label_clr(); void add_debug_menu(wxMenuBar *menu, int event_language_change); diff --git a/xs/src/slic3r/GUI/OptionsGroup.cpp b/xs/src/slic3r/GUI/OptionsGroup.cpp index 0be24824c..24e1ddf2e 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.cpp +++ b/xs/src/slic3r/GUI/OptionsGroup.cpp @@ -82,7 +82,16 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co if (!this->m_disabled) this->back_to_initial_value(opt_id); }; - if (!m_is_tab_opt) field->m_Undo_btn->Hide(); + field->m_back_to_sys_value = [this](std::string opt_id){ + if (!this->m_disabled) + this->back_to_sys_value(opt_id); + }; + if (!m_is_tab_opt) { + field->m_Undo_btn->Hide(); + field->m_Undo_to_sys_btn->Hide(); + } + if (nonsys_btn_icon != nullptr) + field->set_nonsys_btn_icon(nonsys_btn_icon()); // assign function objects for callbacks, etc. return field; @@ -112,7 +121,10 @@ void OptionsGroup::append_line(const Line& line) { const auto& option = option_set.front(); const auto& field = build_field(option); - sizer->Add(field->m_Undo_btn); + auto btn_sizer = new wxBoxSizer(wxHORIZONTAL); + btn_sizer->Add(field->m_Undo_to_sys_btn); + btn_sizer->Add(field->m_Undo_btn); + sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0); if (is_window_field(field)) sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5); if (is_sizer_field(field)) @@ -149,6 +161,7 @@ void OptionsGroup::append_line(const Line& line) { const auto& option = option_set.front(); const auto& field = build_field(option, label); + sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL); if (is_window_field(field)) sizer->Add(field->getWindow(), 0, (option.opt.full_width ? wxEXPAND : 0) | @@ -177,6 +190,7 @@ void OptionsGroup::append_line(const Line& line) { // add field const Option& opt_ref = opt; auto& field = build_field(opt_ref, label); + sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL, 0); is_sizer_field(field) ? sizer->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) : @@ -287,7 +301,20 @@ void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key) { if (m_get_initial_config == nullptr) return; - DynamicPrintConfig config = m_get_initial_config(); + back_to_config_value(m_get_initial_config(), opt_key); +} + +void ConfigOptionsGroup::back_to_sys_value(const std::string opt_key) +{ + if (m_get_sys_config == nullptr) + return; + if (!have_sys_config()) + return; + back_to_config_value(m_get_sys_config(), opt_key); +} + +void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string opt_key) +{ boost::any value; if (opt_key == "extruders_count"){ auto *nozzle_diameter = dynamic_cast(config.option("nozzle_diameter")); @@ -300,15 +327,18 @@ void ConfigOptionsGroup::back_to_initial_value(const std::string opt_key) int opt_index = m_opt_map.at(opt_id).second; value = get_config_value(config, opt_short_key, opt_index); } - else + else{ value = get_config_value(config, opt_key); + change_opt_value(*m_config, opt_key, value); + return; + } set_value(opt_key, value); on_change_OG(opt_key, get_value(opt_key)); } void ConfigOptionsGroup::reload_config(){ - for (std::map< std::string, std::pair >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { + for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { auto opt_id = it->first; std::string opt_key = m_opt_map.at(opt_id).first; int opt_index = m_opt_map.at(opt_id).second; @@ -335,7 +365,7 @@ boost::any ConfigOptionsGroup::config_value(std::string opt_key, int opt_index, } } -boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index/* = -1*/) +boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, std::string opt_key, int opt_index /*= -1*/) { size_t idx = opt_index == -1 ? 0 : opt_index; @@ -379,8 +409,9 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std: ret = text_value; else if (opt->gui_flags.compare("serialized") == 0){ std::vector values = config.option(opt_key)->values; - for (auto el : values) - text_value += el + ";"; + if (!values.empty() && values[0].compare("") != 0) + for (auto el : values) + text_value += el + ";"; ret = text_value; } else @@ -414,7 +445,10 @@ boost::any ConfigOptionsGroup::get_config_value(DynamicPrintConfig& config, std: } break; case coPoints: - ret = config.option(opt_key)->get_at(idx); + if (opt_key.compare("bed_shape") == 0) + ret = config.option(opt_key)->values; + else + ret = config.option(opt_key)->get_at(idx); break; case coNone: default: @@ -428,7 +462,7 @@ Field* ConfigOptionsGroup::get_fieldc(t_config_option_key opt_key, int opt_index if (field != nullptr) return field; std::string opt_id = ""; - for (std::map< std::string, std::pair >::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { + for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) { if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){ opt_id = it->first; break; diff --git a/xs/src/slic3r/GUI/OptionsGroup.hpp b/xs/src/slic3r/GUI/OptionsGroup.hpp index aa0563866..e58d9c9a9 100644 --- a/xs/src/slic3r/GUI/OptionsGroup.hpp +++ b/xs/src/slic3r/GUI/OptionsGroup.hpp @@ -69,6 +69,7 @@ private: }; using t_optionfield_map = std::map; +using t_opt_map = std::map< std::string, std::pair >; class OptionsGroup { public: @@ -79,10 +80,14 @@ public: column_t extra_column {nullptr}; t_change m_on_change {nullptr}; std::function m_get_initial_config{ nullptr }; + std::function m_get_sys_config{ nullptr }; + std::function have_sys_config{ nullptr }; wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) }; + std::function nonsys_btn_icon{ nullptr }; + /// Returns a copy of the pointer of the parent wxWindow. /// Accessor function is because users are not allowed to change the parent /// but defining it as const means a lot of const_casts to deal with wx functions. @@ -149,6 +154,7 @@ protected: virtual void on_kill_focus (){}; virtual void on_change_OG(t_config_option_key opt_id, boost::any value); virtual void back_to_initial_value(const std::string opt_key){}; + virtual void back_to_sys_value(const std::string opt_key){}; }; class ConfigOptionsGroup: public OptionsGroup { @@ -159,7 +165,7 @@ public: /// reference to libslic3r config, non-owning pointer (?). DynamicPrintConfig* m_config {nullptr}; bool m_full_labels {0}; - std::map< std::string, std::pair > m_opt_map; + t_opt_map m_opt_map; Option get_option(const std::string opt_key, int opt_index = -1); Line create_single_option_line(const std::string title, int idx = -1) /*const*/{ @@ -177,11 +183,13 @@ public: void on_change_OG(t_config_option_key opt_id, boost::any value) override; void back_to_initial_value(const std::string opt_key) override; + void back_to_sys_value(const std::string opt_key) override; + void back_to_config_value(const DynamicPrintConfig& config, const std::string opt_key); void on_kill_focus() override{ reload_config();} void reload_config(); boost::any config_value(std::string opt_key, int opt_index, bool deserialize); // return option value from config - boost::any get_config_value(DynamicPrintConfig& config, std::string opt_key, int opt_index = -1); + boost::any get_config_value(const DynamicPrintConfig& config, std::string opt_key, int opt_index = -1); Field* get_fieldc(t_config_option_key opt_key, int opt_index); }; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index aa1917bd5..c437f8b41 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -80,7 +80,7 @@ void Preset::set_num_extruders(DynamicPrintConfig &config, unsigned int num_extr auto *opt = config.option(key, false); assert(opt != nullptr); assert(opt->is_vector()); - if (opt != nullptr && opt->is_vector()) + if (opt != nullptr && opt->is_vector() && key != "default_filament_profile") static_cast(opt)->resize(num_extruders, defaults.option(key)); } } @@ -200,7 +200,7 @@ const std::vector& Preset::print_options() "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "bridge_flow_ratio", "clip_multipart_objects", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_per_color_wipe", - "compatible_printers", "compatible_printers_condition" + "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } @@ -213,7 +213,7 @@ const std::vector& Preset::filament_options() "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed", "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed", "start_filament_gcode", "end_filament_gcode", - "compatible_printers", "compatible_printers_condition" + "compatible_printers", "compatible_printers_condition", "inherits" }; return s_opts; } @@ -226,13 +226,16 @@ const std::vector& Preset::printer_options() "bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "octoprint_host", "octoprint_apikey", "octoprint_cafile", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", - "between_objects_gcode", "printer_notes", "max_print_height" + "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "max_print_height", + "default_print_profile", "inherits", }; s_opts.insert(s_opts.end(), Preset::nozzle_options().begin(), Preset::nozzle_options().end()); } return s_opts; } +// The following nozzle options of a printer profile will be adjusted to match the size +// of the nozzle_diameter vector. const std::vector& Preset::nozzle_options() { // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings @@ -240,7 +243,8 @@ const std::vector& Preset::nozzle_options() "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", "retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_before_wipe", "retract_restart_extra", "retract_before_travel", "wipe", - "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour" + "retract_layer_change", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", + "default_filament_profile" }; return s_opts; } @@ -269,7 +273,7 @@ void PresetCollection::reset(bool delete_files) if (delete_files) { // Erase the preset files. for (Preset &preset : m_presets) - if (! preset.is_default && ! preset.is_external) + if (! preset.is_default && ! preset.is_external && ! preset.is_system) boost::nowide::remove(preset.file.c_str()); } // Don't use m_presets.resize() here as it requires a default constructor for Preset. @@ -284,7 +288,6 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri { boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred(); m_dir_path = dir.string(); - m_presets.erase(m_presets.begin()+1, m_presets.end()); t_config_option_keys keys = this->default_preset().config.keys(); std::string errors_cummulative; for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) @@ -292,6 +295,10 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri std::string name = dir_entry.path().filename().string(); // Remove the .ini suffix. name.erase(name.size() - 4); + if (this->find_preset(name, false)) { + errors_cummulative += "The user preset \"" + name + "\" cannot be loaded. A system preset of the same name has already been loaded."; + continue; + } try { Preset preset(m_type, name, false); preset.file = dir_entry.path().string(); @@ -342,16 +349,33 @@ void PresetCollection::save_current_preset(const std::string &new_name) if (it != m_presets.end() && it->name == new_name) { // Preset with the same name found. Preset &preset = *it; - if (preset.is_default) + if (preset.is_default || preset.is_external || preset.is_system) // Cannot overwrite the default preset. return; // Overwriting an existing preset. preset.config = std::move(m_edited_preset.config); } else { // Creating a new preset. - Preset &preset = *m_presets.insert(it, m_edited_preset); + Preset &preset = *m_presets.insert(it, m_edited_preset); + std::string &inherits = preset.config.opt_string("inherits", true); + std::string old_name = preset.name; preset.name = new_name; preset.file = this->path_from_name(new_name); + preset.vendor = nullptr; + if (preset.is_system) { + // Inheriting from a system preset. + inherits = /* preset.vendor->name + "/" + */ old_name; + } else if (inherits.empty()) { + // Inheriting from a user preset. Link the new preset to the old preset. + // inherits = old_name; + } else { + // Inherited from a user preset. Just maintain the "inherited" flag, + // meaning it will inherit from either the system preset, or the inherited user preset. + } + preset.inherits = inherits; + preset.is_default = false; + preset.is_system = false; + preset.is_external = false; } // 2) Activate the saved preset. this->select_preset_by_name(new_name, true); @@ -364,7 +388,7 @@ void PresetCollection::delete_current_preset() const Preset &selected = this->get_selected_preset(); if (selected.is_default) return; - if (! selected.is_external) { + if (! selected.is_external && ! selected.is_system) { // Erase the preset file. boost::nowide::remove(selected.file.c_str()); } @@ -384,6 +408,15 @@ bool PresetCollection::load_bitmap_default(const std::string &file_name) return m_bitmap_main_frame->LoadFile(wxString::FromUTF8(Slic3r::var(file_name).c_str()), wxBITMAP_TYPE_PNG); } +const Preset* PresetCollection::get_selected_preset_parent() const +{ + auto *inherits = dynamic_cast(this->get_edited_preset().config.option("inherits")); + if (inherits == nullptr || inherits->value.empty()) + return this->get_selected_preset().is_system ? &this->get_selected_preset() : nullptr; // nullptr; + const Preset* preset = this->find_preset(inherits->value, false); + return (preset == nullptr || preset->is_default || preset->is_external) ? nullptr : preset; +} + // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. Preset* PresetCollection::find_preset(const std::string &name, bool first_visible_if_not_found) @@ -527,20 +560,34 @@ bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui) return was_dirty != is_dirty; } -std::vector PresetCollection::current_dirty_options() const -{ - std::vector changed = this->get_selected_preset().config.diff(this->get_edited_preset().config); - // The "compatible_printers" option key is handled differently from the others: - // It is not mandatory. If the key is missing, it means it is compatible with any printer. - // If the key exists and it is empty, it means it is compatible with no printer. - std::initializer_list optional_keys { "compatible_printers", "compatible_printers_condition" }; - for (auto &opt_key : optional_keys) { - if (this->get_selected_preset().config.has(opt_key) != this->get_edited_preset().config.has(opt_key)) - changed.emplace_back(opt_key); +std::vector PresetCollection::dirty_options(const Preset *edited, const Preset *reference) +{ + std::vector changed; + if (edited != nullptr && reference != nullptr) { + changed = reference->config.diff(edited->config); + // The "compatible_printers" option key is handled differently from the others: + // It is not mandatory. If the key is missing, it means it is compatible with any printer. + // If the key exists and it is empty, it means it is compatible with no printer. + std::initializer_list optional_keys { "compatible_printers", "compatible_printers_condition" }; + for (auto &opt_key : optional_keys) { + if (reference->config.has(opt_key) != edited->config.has(opt_key)) + changed.emplace_back(opt_key); + } } return changed; } +std::vector PresetCollection::system_equal_options() const +{ + const Preset *edited = &this->get_edited_preset(); + const Preset *reference = this->get_selected_preset_parent(); + std::vector equal; + if (edited != nullptr && reference != nullptr) { + equal = reference->config.equal(edited->config); + } + return equal; +} + // Select a new preset. This resets all the edits done to the currently selected preset. // If the preset with index idx does not exist, a first visible preset is selected. Preset& PresetCollection::select_preset(size_t idx) diff --git a/xs/src/slic3r/GUI/Preset.hpp b/xs/src/slic3r/GUI/Preset.hpp index c1d85833b..3634c5dd9 100644 --- a/xs/src/slic3r/GUI/Preset.hpp +++ b/xs/src/slic3r/GUI/Preset.hpp @@ -23,6 +23,46 @@ enum ConfigFileType extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree); +class VendorProfile +{ +public: + std::string name; + std::string id; + std::string config_version; + std::string config_update_url; + + struct PrinterVariant { + PrinterVariant() {} + PrinterVariant(const std::string &name) : name(name) {} + std::string name; + bool enabled = true; + }; + + struct PrinterModel { + PrinterModel() {} + PrinterModel(const std::string &name) : name(name) {} + std::string name; + bool enabled = true; + std::vector variants; + PrinterVariant* variant(const std::string &name) { + for (auto &v : this->variants) + if (v.name == name) + return &v; + return nullptr; + } + const PrinterVariant* variant(const std::string &name) const { return const_cast(this)->variant(name); } + + bool operator< (const PrinterModel &rhs) const { return this->name < rhs.name; } + bool operator==(const PrinterModel &rhs) const { return this->name == rhs.name; } + }; + std::set models; + + size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; } + + bool operator< (const VendorProfile &rhs) const { return this->id < rhs.id; } + bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; } +}; + class Preset { public: @@ -44,6 +84,8 @@ public: // External preset points to a configuration, which has been loaded but not imported // into the Slic3r default configuration location. bool is_external = false; + // System preset is read-only. + bool is_system = false; // Preset is visible, if it is compatible with the active Printer. // Also the "default" preset is only visible, if it is the only preset in the list. bool is_visible = true; @@ -55,9 +97,14 @@ public: // Name of the preset, usually derived form the file name. std::string name; // File name of the preset. This could be a Print / Filament / Printer preset, - // or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external will be true), + // or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external and possibly is_system will be true), // or it could be a G-code (again, is_external will be true). std::string file; + // A user profile may inherit its settings either from a system profile, or from a user profile. + // A system profile shall never derive from any other profile, as the system profile hierarchy is being flattened during loading. + std::string inherits; + // If this is a system profile, then there should be a vendor data available to display at the UI. + const VendorProfile *vendor = nullptr; // Has this profile been loaded? bool loaded = false; @@ -144,6 +191,8 @@ public: // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items. void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; } void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; } + void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; } + void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; } // Enable / disable the "- default -" preset. void set_default_suppressed(bool default_suppressed); @@ -155,6 +204,11 @@ public: Preset& get_selected_preset() { return m_presets[m_idx_selected]; } const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; } int get_selected_idx() const { return m_idx_selected; } + // For the current edited preset, return the parent preset if there is one. + // If there is no parent preset, nullptr is returned. + // The parent preset may be a system preset or a user preset, which will be + // reflected by the UI. + const Preset* get_selected_preset_parent() const; // Return the selected preset including the user modifications. Preset& get_edited_preset() { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; } @@ -191,7 +245,13 @@ public: // Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ. bool current_is_dirty() const { return ! this->current_dirty_options().empty(); } // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. - std::vector current_dirty_options() const; + std::vector current_dirty_options() const + { return dirty_options(&this->get_edited_preset(), &this->get_selected_preset()); } + // Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ. + std::vector current_different_from_parent_options() const + { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent()); } + // Compare the content of get_selected_preset() with get_selected_preset_parent() configs, return the list of keys where they equal. + std::vector system_equal_options() const; // Update the choice UI from the list of presets. // If show_incompatible, all presets are shown, otherwise only the compatible presets are shown. @@ -231,6 +291,8 @@ private: std::deque::const_iterator find_preset_internal(const std::string &name) const { return const_cast(this)->find_preset_internal(name); } + static std::vector dirty_options(const Preset *edited, const Preset *reference); + // Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER. Preset::Type m_type; // List of presets, starting with the "- default -" preset. @@ -245,8 +307,10 @@ private: bool m_default_suppressed = true; // Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter. // These bitmaps are not owned by PresetCollection, but by a PresetBundle. - const wxBitmap *m_bitmap_compatible = nullptr; + const wxBitmap *m_bitmap_compatible = nullptr; const wxBitmap *m_bitmap_incompatible = nullptr; + const wxBitmap *m_bitmap_lock = nullptr; + const wxBitmap *m_bitmap_lock_open = nullptr; // Marks placed at the wxBitmapComboBox of a MainFrame. // These bitmaps are owned by PresetCollection. wxBitmap *m_bitmap_main_frame; diff --git a/xs/src/slic3r/GUI/PresetBundle.cpp b/xs/src/slic3r/GUI/PresetBundle.cpp index bf79c6562..7131bf771 100644 --- a/xs/src/slic3r/GUI/PresetBundle.cpp +++ b/xs/src/slic3r/GUI/PresetBundle.cpp @@ -2,6 +2,7 @@ #include #include "PresetBundle.hpp" +#include "BitmapCache.hpp" #include #include @@ -37,7 +38,10 @@ PresetBundle::PresetBundle() : filaments(Preset::TYPE_FILAMENT, Preset::filament_options()), printers(Preset::TYPE_PRINTER, Preset::printer_options()), m_bitmapCompatible(new wxBitmap), - m_bitmapIncompatible(new wxBitmap) + m_bitmapIncompatible(new wxBitmap), + m_bitmapLock(new wxBitmap), + m_bitmapLockOpen(new wxBitmap), + m_bitmapCache(new GUI::BitmapCache) { if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler); @@ -51,6 +55,14 @@ PresetBundle::PresetBundle() : this->filaments.preset(0).config.optptr("compatible_printers_condition", true); this->prints.preset(0).config.optptr("compatible_printers", true); this->prints.preset(0).config.optptr("compatible_printers_condition", true); + // Create the "inherits" keys. + this->prints.preset(0).config.optptr("inherits", true); + this->filaments.preset(0).config.optptr("inherits", true); + this->printers.preset(0).config.optptr("inherits", true); + // Create the "printer_vendor", "printer_model" and "printer_variant" keys. + this->printers.preset(0).config.optptr("printer_vendor", true); + this->printers.preset(0).config.optptr("printer_model", true); + this->printers.preset(0).config.optptr("printer_variant", true); this->prints .load_bitmap_default("cog.png"); this->filaments.load_bitmap_default("spool.png"); @@ -62,12 +74,18 @@ PresetBundle::~PresetBundle() { assert(m_bitmapCompatible != nullptr); assert(m_bitmapIncompatible != nullptr); + assert(m_bitmapLock != nullptr); + assert(m_bitmapLockOpen != nullptr); delete m_bitmapCompatible; m_bitmapCompatible = nullptr; delete m_bitmapIncompatible; m_bitmapIncompatible = nullptr; - for (std::pair &bitmap : m_mapColorToBitmap) - delete bitmap.second; + delete m_bitmapLock; + m_bitmapLock = nullptr; + delete m_bitmapLockOpen; + m_bitmapLockOpen = nullptr; + delete m_bitmapCache; + m_bitmapCache = nullptr; } void PresetBundle::reset(bool delete_files) @@ -84,7 +102,8 @@ void PresetBundle::setup_directories() { boost::filesystem::path data_dir = boost::filesystem::path(Slic3r::data_dir()); std::initializer_list paths = { - data_dir, + data_dir, + data_dir / "vendor", #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. data_dir / "presets", @@ -107,10 +126,12 @@ void PresetBundle::setup_directories() } } -void PresetBundle::load_presets() +void PresetBundle::load_presets(const AppConfig &config) { - std::string errors_cummulative; - const std::string dir_path = data_dir() + // First load the vendor specific system presets. + std::string errors_cummulative = this->load_system_presets(); + + const std::string dir_user_presets = data_dir() #ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR // Store the print/filament/printer presets into a "presets" directory. + "/presets" @@ -119,17 +140,17 @@ void PresetBundle::load_presets() #endif ; try { - this->prints.load_presets(dir_path, "print"); + this->prints.load_presets(dir_user_presets, "print"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->filaments.load_presets(dir_path, "filament"); + this->filaments.load_presets(dir_user_presets, "filament"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->printers.load_presets(dir_path, "printer"); + this->printers.load_presets(dir_user_presets, "printer"); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } @@ -137,6 +158,31 @@ void PresetBundle::load_presets() this->update_compatible_with_printer(false); if (! errors_cummulative.empty()) throw std::runtime_error(errors_cummulative); + + this->load_selections(config); +} + +// Load system presets into this PresetBundle. +// For each vendor, there will be a single PresetBundle loaded. +std::string PresetBundle::load_system_presets() +{ + // Here the vendor specific read only Config Bundles are stored. + boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred(); + std::string errors_cummulative; + for (auto &dir_entry : boost::filesystem::directory_iterator(dir)) + if (boost::filesystem::is_regular_file(dir_entry.status()) && boost::algorithm::iends_with(dir_entry.path().filename().string(), ".ini")) { + std::string name = dir_entry.path().filename().string(); + // Remove the .ini suffix. + name.erase(name.size() - 4); + try { + // Load the config bundle, flatten it. + this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + } catch (const std::runtime_error &err) { + errors_cummulative += err.what(); + errors_cummulative += "\n"; + } + } + return errors_cummulative; } static inline std::string remove_ini_suffix(const std::string &name) @@ -147,6 +193,14 @@ static inline std::string remove_ini_suffix(const std::string &name) return out; } +// Set the "enabled" 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. +void PresetBundle::load_installed_printers(const AppConfig &config) +{ + // m_storage +} + // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. void PresetBundle::load_selections(const AppConfig &config) @@ -200,10 +254,16 @@ bool PresetBundle::load_compatible_bitmaps() { const std::string path_bitmap_compatible = "flag-green-icon.png"; const std::string path_bitmap_incompatible = "flag-red-icon.png"; + const std::string path_bitmap_lock = "lock.png"; + const std::string path_bitmap_lock_open = "lock_open.png"; bool loaded_compatible = m_bitmapCompatible ->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_compatible).c_str()), wxBITMAP_TYPE_PNG); bool loaded_incompatible = m_bitmapIncompatible->LoadFile( wxString::FromUTF8(Slic3r::var(path_bitmap_incompatible).c_str()), wxBITMAP_TYPE_PNG); + bool loaded_lock = m_bitmapLock->LoadFile( + wxString::FromUTF8(Slic3r::var(path_bitmap_lock).c_str()), wxBITMAP_TYPE_PNG); + bool loaded_lock_open = m_bitmapLockOpen->LoadFile( + wxString::FromUTF8(Slic3r::var(path_bitmap_lock_open).c_str()), wxBITMAP_TYPE_PNG); if (loaded_compatible) { prints .set_bitmap_compatible(m_bitmapCompatible); filaments.set_bitmap_compatible(m_bitmapCompatible); @@ -214,7 +274,17 @@ bool PresetBundle::load_compatible_bitmaps() filaments.set_bitmap_incompatible(m_bitmapIncompatible); // printers .set_bitmap_incompatible(m_bitmapIncompatible); } - return loaded_compatible && loaded_incompatible; + if (loaded_lock) { + prints .set_bitmap_lock(m_bitmapLock); + filaments.set_bitmap_lock(m_bitmapLock); + printers .set_bitmap_lock(m_bitmapLock); + } + if (loaded_lock_open) { + prints .set_bitmap_lock_open(m_bitmapLock); + filaments.set_bitmap_lock_open(m_bitmapLock); + printers .set_bitmap_lock_open(m_bitmapLock); + } + return loaded_compatible && loaded_incompatible && loaded_lock && loaded_lock_open; } DynamicPrintConfig PresetBundle::full_config() const @@ -594,11 +664,54 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree) flatten_configbundle_hierarchy(tree, "printer"); } +static void load_vendor_profile(const boost::property_tree::ptree &tree, VendorProfile &vendor_profile) +{ + const std::string printer_model_key = "printer_model:"; + for (auto §ion : tree) + if (section.first == "vendor") { + // Load the names of the active presets. + for (auto &kvp : section.second) { + if (kvp.first == "name") + vendor_profile.name = kvp.second.data(); + else if (kvp.first == "id") + vendor_profile.id = kvp.second.data(); + else if (kvp.first == "config_version") + vendor_profile.config_version = kvp.second.data(); + else if (kvp.first == "config_update_url") + vendor_profile.config_update_url = kvp.second.data(); + } + } else if (boost::starts_with(section.first, printer_model_key)) { + VendorProfile::PrinterModel model; + model.name = section.first.substr(printer_model_key.size()); + section.second.get("variants", ""); + std::vector variants; + if (Slic3r::unescape_strings_cstyle(section.second.get("variants", ""), variants)) { + for (const std::string &variant_name : variants) { + if (model.variant(variant_name) == nullptr) + model.variants.emplace_back(VendorProfile::PrinterVariant(variant_name)); + } + } else { + // Log error? + } + if (! model.name.empty() && ! model.variants.empty()) + vendor_profile.models.insert(model); + } +} + +// Load a config bundle file, into presets and store the loaded presets into separate files +// of the local configuration directory. +void PresetBundle::install_vendor_configbundle(const std::string &src_path0) +{ + boost::filesystem::path src_path(src_path0); + boost::filesystem::copy_file(src_path, (boost::filesystem::path(data_dir()) / "vendor" / src_path.filename()).make_preferred(), boost::filesystem::copy_option::overwrite_if_exists); +} + // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. size_t PresetBundle::load_configbundle(const std::string &path, unsigned int flags) { - if (flags & LOAD_CFGBNDLE_RESET_USER_PROFILE) + if (flags & (LOAD_CFGBNDLE_RESET_USER_PROFILE | LOAD_CFGBNDLE_SYSTEM)) + // Reset this bundle, delete user profile files if LOAD_CFGBNDLE_SAVE. this->reset(flags & LOAD_CFGBNDLE_SAVE); // 1) Read the complete config file into a boost::property_tree. @@ -609,6 +722,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla // Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. flatten_configbundle_hierarchy(tree); + const VendorProfile *vendor_profile = nullptr; + if (flags & LOAD_CFGBNDLE_SYSTEM) { + VendorProfile vp; + load_vendor_profile(tree, vp); + if (vp.name.empty()) + throw std::runtime_error(std::string("Vendor Config Bundle is not valid: Missing vendor name key.")); + if (vp.num_variants() == 0) + return 0; + vendor_profile = &(*this->vendors.insert(vp).first); + } + // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. std::vector loaded_prints; std::vector loaded_filaments; @@ -622,15 +746,15 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla std::vector *loaded = nullptr; std::string preset_name; if (boost::starts_with(section.first, "print:")) { - presets = &prints; + presets = &this->prints; loaded = &loaded_prints; preset_name = section.first.substr(6); } else if (boost::starts_with(section.first, "filament:")) { - presets = &filaments; + presets = &this->filaments; loaded = &loaded_filaments; preset_name = section.first.substr(9); } else if (boost::starts_with(section.first, "printer:")) { - presets = &printers; + presets = &this->printers; loaded = &loaded_printers; preset_name = section.first.substr(8); } else if (section.first == "presets") { @@ -664,6 +788,40 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla for (auto &kvp : section.second) config.set_deserialize(kvp.first, kvp.second.data()); Preset::normalize(config); + if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { + // Filter out printer presets, which are not mentioned in the vendor profile. + // These presets are considered not installed. + auto printer_model = config.opt_string("printer_model"); + if (printer_model.empty()) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines no printer model, it will be ignored."; + continue; + } + auto printer_variant = config.opt_string("printer_variant"); + if (printer_variant.empty()) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines no printer variant, it will be ignored."; + continue; + } + auto it_model = vendor_profile->models.find(VendorProfile::PrinterModel(printer_model)); + if (it_model == vendor_profile->models.end()) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines invalid printer model \"" << printer_model << "\", it will be ignored."; + continue; + } + auto it_variant = it_model->variant(printer_variant); + if (it_variant == nullptr) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" defines invalid printer variant \"" << printer_variant << "\", it will be ignored."; + continue; + } + const Preset *preset_existing = presets->find_preset(section.first, false); + if (preset_existing != nullptr) { + BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << + section.first << "\" has already been loaded from another Confing Bundle."; + continue; + } + } // Decide a full path to this .ini file. auto file_name = boost::algorithm::iends_with(preset_name, ".ini") ? preset_name : preset_name + ".ini"; auto file_path = (boost::filesystem::path(data_dir()) @@ -678,24 +836,29 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); if (flags & LOAD_CFGBNDLE_SAVE) loaded.save(); + if (flags & LOAD_CFGBNDLE_SYSTEM) { + loaded.is_system = true; + loaded.vendor = vendor_profile; + } ++ presets_loaded; } } // 3) Activate the presets. - if (! active_print.empty()) - prints.select_preset_by_name(active_print, true); - if (! active_printer.empty()) - printers.select_preset_by_name(active_printer, true); - // Activate the first filament preset. - if (! active_filaments.empty() && ! active_filaments.front().empty()) - filaments.select_preset_by_name(active_filaments.front(), true); + if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { + if (! active_print.empty()) + prints.select_preset_by_name(active_print, true); + if (! active_printer.empty()) + printers.select_preset_by_name(active_printer, true); + // Activate the first filament preset. + if (! active_filaments.empty() && ! active_filaments.front().empty()) + filaments.select_preset_by_name(active_filaments.front(), true); + this->update_multi_material_filament_presets(); + for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) + this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; + this->update_compatible_with_printer(false); + } - this->update_multi_material_filament_presets(); - for (size_t i = 0; i < std::min(this->filament_presets.size(), active_filaments.size()); ++ i) - this->filament_presets[i] = filaments.find_preset(active_filaments[i], true)->name; - - this->update_compatible_with_printer(false); return presets_loaded; } @@ -836,49 +999,29 @@ void PresetBundle::update_platter_filament_ui(unsigned int idx_extruder, wxBitma // If the filament preset is not compatible and there is a "red flag" icon loaded, show it left // to the filament color image. if (wide_icons) - bitmap_key += preset.is_compatible ? "comp" : "notcomp"; - auto it = m_mapColorToBitmap.find(bitmap_key); - wxBitmap *bitmap = (it == m_mapColorToBitmap.end()) ? nullptr : it->second; + bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt"; + bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst"; + if (preset.is_dirty) + bitmap_key += ",drty"; + wxBitmap *bitmap = m_bitmapCache->find(bitmap_key); if (bitmap == nullptr) { // Create the bitmap with color bars. - bitmap = new wxBitmap((wide_icons ? 16 : 0) + 24, 16); -#if defined(__APPLE__) || defined(_MSC_VER) - bitmap->UseAlpha(); -#endif - wxMemoryDC memDC; - memDC.SelectObject(*bitmap); - memDC.SetBackground(*wxTRANSPARENT_BRUSH); - memDC.Clear(); - if (wide_icons && ! preset.is_compatible) - // Paint the red flag. - memDC.DrawBitmap(*m_bitmapIncompatible, 0, 0, true); + std::vector bmps; + if (wide_icons) + // Paint a red flag for incompatible presets. + bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(16, 16) : *m_bitmapIncompatible); // Paint the color bars. parse_color(filament_rgb, rgb); - wxImage image(24, 16); - image.InitAlpha(); - unsigned char* imgdata = image.GetData(); - unsigned char* imgalpha = image.GetAlpha(); - for (size_t i = 0; i < image.GetWidth() * image.GetHeight(); ++ i) { - *imgdata ++ = rgb[0]; - *imgdata ++ = rgb[1]; - *imgdata ++ = rgb[2]; - *imgalpha ++ = wxALPHA_OPAQUE; - } + bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? 24 : 16, 16, rgb)); if (! single_bar) { parse_color(extruder_rgb, rgb); - imgdata = image.GetData(); - for (size_t r = 0; r < 16; ++ r) { - imgdata = image.GetData() + r * image.GetWidth() * 3; - for (size_t c = 0; c < 16; ++ c) { - *imgdata ++ = rgb[0]; - *imgdata ++ = rgb[1]; - *imgdata ++ = rgb[2]; - } - } + bmps.emplace_back(m_bitmapCache->mksolid(8, 16, rgb)); } - memDC.DrawBitmap(wxBitmap(image), wide_icons ? 16 : 0, 0, true); - memDC.SelectObject(wxNullBitmap); - m_mapColorToBitmap[bitmap_key] = bitmap; + // Paint a lock at the system presets. + bmps.emplace_back(m_bitmapCache->mkclear(4, 16)); + bmps.emplace_back((preset.is_system || preset.is_default) ? + (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16)); + bitmap = m_bitmapCache->insert(bitmap_key, bmps); } ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()), (bitmap == 0) ? wxNullBitmap : *bitmap); if (selected) diff --git a/xs/src/slic3r/GUI/PresetBundle.hpp b/xs/src/slic3r/GUI/PresetBundle.hpp index e6aea206d..f481ba374 100644 --- a/xs/src/slic3r/GUI/PresetBundle.hpp +++ b/xs/src/slic3r/GUI/PresetBundle.hpp @@ -4,8 +4,14 @@ #include "AppConfig.hpp" #include "Preset.hpp" +#include + namespace Slic3r { +namespace GUI { + class BitmapCache; +}; + class PlaceholderParser; // Bundle of Print + Filament + Printer presets. @@ -22,11 +28,10 @@ public: void setup_directories(); // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. - void load_presets(); - // Load selections (current print, current filaments, current printer) from config.ini // This is done just once on application start up. - void load_selections(const AppConfig &config); + void load_presets(const AppConfig &config); + // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); // Export selections (current print, current filaments, current printer) into a placeholder parser. @@ -39,6 +44,10 @@ public: // extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size() std::vector filament_presets; + // There will be an entry for each system profile loaded, + // and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors. + std::set vendors; + bool has_defauls_only() const { return prints.size() <= 1 && filaments.size() <= 1 && printers.size() <= 1; } @@ -69,11 +78,16 @@ public: // Save the profiles, which have been loaded. LOAD_CFGBNDLE_SAVE = 1, // Delete all old config profiles before loading. - LOAD_CFGBNDLE_RESET_USER_PROFILE = 2 + LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, + // Load a system config bundle. + LOAD_CFGBNDLE_SYSTEM = 4, }; // Load the config bundle, store it to the user profile directory by default. size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE); + // Install the Vendor specific config bundle into user's directory. + void install_vendor_configbundle(const std::string &src_path); + // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings); @@ -99,6 +113,17 @@ public: void update_compatible_with_printer(bool select_other_if_incompatible); private: + std::string load_system_presets(); + + // Set the "enabled" 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. + void load_installed_printers(const AppConfig &config); + + // Load selections (current print, current filaments, current printer) from config.ini + // This is done just once on application start up. + void load_selections(const AppConfig &config); + // Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path. // and the external config is just referenced, not stored into user profile directory. // If it is not an external config, then the config will be stored into the user profile directory. @@ -110,8 +135,12 @@ private: wxBitmap *m_bitmapCompatible; // Indicator, that the preset is NOT compatible with the selected printer. wxBitmap *m_bitmapIncompatible; - // Caching color bitmaps for the - std::map m_mapColorToBitmap; + // Indicator, that the preset is system and not modified. + wxBitmap *m_bitmapLock; + // Indicator, that the preset is system and user modified. + wxBitmap *m_bitmapLockOpen; + // Caching color bitmaps for the filament combo box. + GUI::BitmapCache *m_bitmapCache; }; } // namespace Slic3r diff --git a/xs/src/slic3r/GUI/Tab.cpp b/xs/src/slic3r/GUI/Tab.cpp index d0f9f0ce3..babff7d80 100644 --- a/xs/src/slic3r/GUI/Tab.cpp +++ b/xs/src/slic3r/GUI/Tab.cpp @@ -59,6 +59,17 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_btn_delete_preset->SetToolTip(_(L("Delete this preset"))); m_btn_delete_preset->Disable(); + m_undo_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + m_undo_to_sys_btn = new wxButton(panel, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER); + if (wxMSW) { + m_undo_btn->SetBackgroundColour(color); + m_undo_to_sys_btn->SetBackgroundColour(color); + } + m_undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + m_undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); })); + m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + m_undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); })); + m_hsizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(m_hsizer, 0, wxBOTTOM, 3); m_hsizer->Add(m_presets_choice, 1, wxLEFT | wxRIGHT | wxTOP | wxALIGN_CENTER_VERTICAL, 3); @@ -68,6 +79,9 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) m_hsizer->Add(m_btn_delete_preset, 0, wxALIGN_CENTER_VERTICAL); m_hsizer->AddSpacer(16); m_hsizer->Add(m_btn_hide_incompatible_presets, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->AddSpacer(64); + m_hsizer->Add(m_undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL); + m_hsizer->Add(m_undo_btn, 0, wxALIGN_CENTER_VERTICAL); //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -101,7 +115,6 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) if (selected_item >= 0){ std::string selected_string = m_presets_choice->GetString(selected_item).ToUTF8().data(); select_preset(selected_string); - update_changed_ui(); } })); @@ -117,6 +130,14 @@ void Tab::create_preset_tab(PresetBundle *preset_bundle) update(); } +void Tab::load_initial_data() +{ + m_config = &m_presets->get_edited_preset().config; + m_nonsys_btn_icon = m_presets->get_selected_preset_parent() == nullptr ? + "bullet_white.png" : + wxMSW ? "sys_unlock.png" : "lock_open.png"; +} + PageShp Tab::add_options_page(wxString title, std::string icon, bool is_extruder_pages/* = false*/) { // Index of icon in an icon list $self->{icons}. @@ -157,6 +178,23 @@ void add_correct_opts_to_dirty_options(const std::string &opt_key, std::vector +void add_correct_opts_to_sys_options(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +{ + const Preset* sys_preset = tab->m_presets->get_selected_preset_parent(); + if (sys_preset == nullptr) + return; + T *opt_cur = static_cast(tab->m_config->option(opt_key)); + const T *opt_sys = static_cast(sys_preset->config.option(opt_key)); + int opt_max_id = opt_sys->values.size()-1; + for (int i = 0; i < opt_cur->values.size(); i++) + { + int init_id = i <= opt_max_id ? i : 0; + if (opt_cur->values[i] == opt_sys->values[init_id]) + vec->emplace_back(opt_key + "#" + std::to_string(i)); + } +} + // Update UI according to changes void Tab::update_changed_ui() { @@ -165,63 +203,273 @@ void Tab::update_changed_ui() if (name() == "printer"){ // Update dirty_options in case changes of Extruder's options TabPrinter* tab = static_cast(this); - std::vector new_dirty; + m_dirty_options.resize(0); for (auto opt_key : dirty_options) { + if (opt_key == "bed_shape"){ m_dirty_options.emplace_back(opt_key); continue; } switch (m_config->option(opt_key)->type()) { - case coInts: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coBools: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coFloats: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coStrings: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coPercents:add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - case coPoints: add_correct_opts_to_dirty_options(opt_key, &new_dirty, tab); break; - default: new_dirty.emplace_back(opt_key); break; + case coInts: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coBools: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coFloats: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coStrings: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coPercents:add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + case coPoints: add_correct_opts_to_dirty_options(opt_key, &m_dirty_options, tab); break; + default: m_dirty_options.emplace_back(opt_key); break; } } + if (tab->m_initial_extruders_count != tab->m_extruders_count) + m_dirty_options.emplace_back("extruders_count"); - dirty_options.resize(0); - dirty_options = new_dirty; - if (tab->m_initial_extruders_count != tab->m_extruders_count){ - dirty_options.emplace_back("extruders_count"); - } - } - - // Add new dirty options to m_dirty_options - for (auto opt_key : dirty_options){ - Field* field = get_field(opt_key); - if (field != nullptr && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()){ - if (field->m_Label != nullptr){ - field->m_Label->SetForegroundColour(*get_modified_label_clr()); - field->m_Label->Refresh(true); + m_sys_options.resize(0); + const auto sys_preset = m_presets->get_selected_preset_parent(); + if (sys_preset){ + for (auto opt_key : m_config->keys()) + { + if (opt_key == "bed_shape"){ + if (*tab->m_config->option(opt_key) == *sys_preset->config.option(opt_key)) + m_sys_options.emplace_back(opt_key); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coBools: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coFloats: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coStrings: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coPercents:add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + case coPoints: add_correct_opts_to_sys_options(opt_key, &m_sys_options, tab); break; + default:{ + const ConfigOption *opt_cur = tab->m_config->option(opt_key); + const ConfigOption *opt_sys = sys_preset->config.option(opt_key); + if (opt_cur != nullptr && opt_sys != nullptr && *opt_cur == *opt_sys) + m_sys_options.emplace_back(opt_key); + break; + } + } } - field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(wxMSW ? var("action_undo.png") : var("arrow_undo.png")), wxBITMAP_TYPE_PNG)); - field->m_is_modified_value = true; - m_dirty_options.push_back(opt_key); + if (tab->m_sys_extruders_count == tab->m_extruders_count) + m_sys_options.emplace_back("extruders_count"); } } + else{ + m_sys_options = m_presets->system_equal_options(); + m_dirty_options = dirty_options; + } - // Delete clear options from m_dirty_options - for (auto i = 0; i < m_dirty_options.size(); ++i) + //update options "decoration" + for (const auto opt_key : m_full_options_list) { - const std::string &opt_key = m_dirty_options[i]; - Field* field = get_field(opt_key); - if (field != nullptr && find(dirty_options.begin(), dirty_options.end(), opt_key) == dirty_options.end()) + bool is_nonsys_value = false; + bool is_modified_value = true; + std::string sys_icon = wxMSW ? "sys_lock.png" : "lock.png"; + std::string icon = wxMSW ? "action_undo.png" : "arrow_undo.png"; + wxColour& color = *get_sys_label_clr(); + if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) { + is_nonsys_value = true; + sys_icon = m_nonsys_btn_icon; + if(find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) + color = wxSYS_COLOUR_WINDOWTEXT; + else + color = *get_modified_label_clr(); + } + if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) == m_dirty_options.end()) { - field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG)); + is_modified_value = false; + icon = "bullet_white.png"; + } + Field* field = get_field(opt_key); + if (field == nullptr) continue; + field->m_is_nonsys_value = is_nonsys_value; + field->m_is_modified_value = is_modified_value; + field->m_Undo_btn->SetBitmap(wxBitmap(from_u8(var(icon)), wxBITMAP_TYPE_PNG)); + field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(sys_icon)), wxBITMAP_TYPE_PNG)); + if (field->m_Label != nullptr){ + field->m_Label->SetForegroundColour(color); + field->m_Label->Refresh(true); + } + } + + wxTheApp->CallAfter([this]() { + update_changed_tree_ui(); + }); +} + +template +void add_correct_opts_to_full_options_list(const std::string &opt_key, std::vector *vec, TabPrinter *tab) +{ + T *opt_cur = static_cast(tab->m_config->option(opt_key)); + for (int i = 0; i < opt_cur->values.size(); i++) + vec->emplace_back(opt_key + "#" + std::to_string(i)); +} + +void Tab::update_full_options_list() +{ + if (!m_full_options_list.empty()) + m_full_options_list.resize(0); + + if (m_name != "printer"){ + m_full_options_list = m_config->keys(); + return; + } + + TabPrinter* tab = static_cast(this); + for (const auto opt_key : m_config->keys()) + { + if (opt_key == "bed_shape"){ + m_full_options_list.emplace_back(opt_key); + continue; + } + switch (m_config->option(opt_key)->type()) + { + case coInts: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coBools: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coFloats: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coStrings: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coPercents:add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + case coPoints: add_correct_opts_to_full_options_list(opt_key, &m_full_options_list, tab); break; + default: m_full_options_list.emplace_back(opt_key); break; + } + } + m_full_options_list.emplace_back("extruders_count"); +} + +void Tab::update_sys_ui_after_sel_preset() +{ + for (const auto opt_key : m_full_options_list){ + Field* field = get_field(opt_key); + if (field != nullptr){ + field->m_Undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(m_nonsys_btn_icon)), wxBITMAP_TYPE_PNG)); + field->m_is_nonsys_value = true; if (field->m_Label != nullptr){ field->m_Label->SetForegroundColour(wxSYS_COLOUR_WINDOWTEXT); field->m_Label->Refresh(true); } - field->m_is_modified_value = false; - std::vector::iterator itr = find(m_dirty_options.begin(), m_dirty_options.end(), opt_key); - if (itr != m_dirty_options.end()){ - m_dirty_options.erase(itr); - --i; - } } } + m_sys_options.resize(0); +} + +void Tab::update_changed_tree_ui() +{ + auto cur_item = m_treectrl->GetFirstVisibleItem(); + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + while (cur_item){ + auto title = m_treectrl->GetItemText(cur_item); + int i=0; + for (auto page : m_pages) + { + if (page->title() != title) + continue; + bool sys_page = true; + bool modified_page = false; + if (title == _("General")){ + std::initializer_list optional_keys{ "extruders_count", "bed_shape" }; + for (auto &opt_key : optional_keys) { + if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + sys_page = false; + if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + modified_page = true; + } + } + for (auto group : page->m_optgroups) + { + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + if (sys_page && find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + sys_page = false; + if (!modified_page && find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + modified_page = true; + } + if (!sys_page && modified_page) + break; + } + if (sys_page) + m_treectrl->SetItemTextColour(cur_item, *get_sys_label_clr()); + else if (modified_page) + m_treectrl->SetItemTextColour(cur_item, *get_modified_label_clr()); + else + m_treectrl->SetItemTextColour(cur_item, wxSYS_COLOUR_WINDOWTEXT); + + page->m_is_nonsys_values = !sys_page; + page->m_is_modified_values = modified_page; + + if (selection == title){ + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; + } + break; + } + auto next_item = m_treectrl->GetNextVisible(cur_item); + cur_item = next_item; + } + update_undo_buttons(); +} + +void Tab::update_undo_buttons() +{ + const std::string& undo_icon = !m_is_modified_values ? "bullet_white.png" : + wxMSW ? "action_undo.png" : "arrow_undo.png"; + const std::string& undo_to_sys_icon = m_is_nonsys_values ? m_nonsys_btn_icon : + wxMSW ? "sys_lock.png" : "lock.png"; + + m_undo_btn->SetBitmap(wxBitmap(from_u8(var(undo_icon)), wxBITMAP_TYPE_PNG)); + m_undo_to_sys_btn->SetBitmap(wxBitmap(from_u8(var(undo_to_sys_icon)), wxBITMAP_TYPE_PNG)); +} + +void Tab::on_back_to_initial_value() +{ + if (!m_is_modified_values) return; + + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + for (auto page : m_pages) + if (page->title() == selection) { + for (auto group : page->m_optgroups){ + if (group->title == _("Capabilities")){ + if (find(m_dirty_options.begin(), m_dirty_options.end(), "extruders_count") != m_dirty_options.end()) + group->back_to_initial_value("extruders_count"); + } + if (group->title == _("Size and coordinates")){ + if (find(m_dirty_options.begin(), m_dirty_options.end(), "bed_shape") != m_dirty_options.end()) + group->back_to_initial_value("bed_shape"); + } + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + if (find(m_dirty_options.begin(), m_dirty_options.end(), opt_key) != m_dirty_options.end()) + group->back_to_initial_value(opt_key); + } + } + break; + } + update_changed_ui(); +} + +void Tab::on_back_to_sys_value() +{ + if (!m_is_nonsys_values) return; + + auto selection = m_treectrl->GetItemText(m_treectrl->GetSelection()); + for (auto page : m_pages) + if (page->title() == selection) { + for (auto group : page->m_optgroups) { + if (group->title == _("Capabilities")){ + if (find(m_sys_options.begin(), m_sys_options.end(), "extruders_count") == m_sys_options.end()) + group->back_to_sys_value("extruders_count"); + } + if (group->title == _("Size and coordinates")){ + if (find(m_sys_options.begin(), m_sys_options.end(), "bed_shape") == m_sys_options.end()) + group->back_to_sys_value("bed_shape"); + } + for (t_opt_map::iterator it = group->m_opt_map.begin(); it != group->m_opt_map.end(); ++it) { + const std::string& opt_key = it->first; + if (find(m_sys_options.begin(), m_sys_options.end(), opt_key) == m_sys_options.end()) + group->back_to_sys_value(opt_key); + } + } + break; + } + update_changed_ui(); } // Update the combo box label of the selected preset based on its "dirty" state, @@ -350,6 +598,13 @@ void Tab::on_presets_changed() event.SetString(m_name); g_wxMainFrame->ProcessWindowEvent(event); } + + const Preset* parent = m_presets->get_selected_preset_parent(); + const wxString description_line = parent == nullptr ? + _(L("It's default preset")) : parent == &m_presets->get_selected_preset() ? + _(L("It's system preset")) : + _(L("Current preset is inherited from")) + ":\n" + parent->name; + m_parent_preset_description_line->SetText(description_line); } void Tab::update_frequently_changed_parameters() @@ -379,7 +634,7 @@ void Tab::reload_compatible_printers_widget() void TabPrint::build() { m_presets = &m_preset_bundle->prints; - m_config = &m_presets->get_edited_preset().config; + load_initial_data(); auto page = add_options_page(_(L("Layers and perimeters")), "layers.png"); auto optgroup = page->new_optgroup(_(L("Layer height"))); @@ -521,7 +776,7 @@ void TabPrint::build() optgroup->append_single_option_line("interface_shells"); page = add_options_page(_(L("Advanced")), "wrench.png"); - optgroup = page->new_optgroup(_(L("Extrusion width")), 180); + optgroup = page->new_optgroup(_(L("Extrusion width"))); optgroup->append_single_option_line("extrusion_width"); optgroup->append_single_option_line("first_layer_extrusion_width"); optgroup->append_single_option_line("perimeter_extrusion_width"); @@ -586,6 +841,13 @@ void TabPrint::build() option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. @@ -598,9 +860,11 @@ void TabPrint::update() { Freeze(); + double fill_density = m_config->option("fill_density")->value; + if (m_config->opt_bool("spiral_vase") && !(m_config->opt_int("perimeters") == 1 && m_config->opt_int("top_solid_layers") == 0 && - m_config->option("fill_density")->value == 0)) { + fill_density == 0)) { wxString msg_text = _(L("The Spiral Vase mode requires:\n" "- one perimeter\n" "- no top solid layers\n" @@ -617,11 +881,13 @@ void TabPrint::update() new_conf.set_key_value("support_material", new ConfigOptionBool(false)); new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0)); new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(false)); + fill_density = 0; } else { new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); } load_config(new_conf); + on_value_change("fill_density", fill_density); } auto first_layer_height = m_config->option("first_layer_height")->value; @@ -735,7 +1001,6 @@ void TabPrint::update() "\nShall I switch to rectilinear fill pattern?")); auto dialog = new wxMessageDialog(parent(), msg_text, _(L("Infill")), wxICON_WARNING | wxYES | wxNO); DynamicPrintConfig new_conf = *m_config; - double fill_density; if (dialog->ShowModal() == wxID_YES) { new_conf.set_key_value("fill_pattern", new ConfigOptionEnum(ipRectilinear)); fill_density = 100; @@ -856,7 +1121,7 @@ void TabPrint::OnActivate() void TabFilament::build() { m_presets = &m_preset_bundle->filaments; - m_config = &m_preset_bundle->filaments.get_edited_preset().config; + load_initial_data(); auto page = add_options_page(_(L("Filament")), "spool.png"); auto optgroup = page->new_optgroup(_(L("Filament"))); @@ -950,6 +1215,13 @@ void TabFilament::build() option = optgroup->get_option("compatible_printers_condition"); option.opt.full_width = true; optgroup->append_single_option_line(option); + + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); } // Reload current config (aka presets->edited_preset->config) into the UI fields. @@ -1003,11 +1275,13 @@ bool Tab::current_preset_is_dirty() void TabPrinter::build() { m_presets = &m_preset_bundle->printers; - m_config = &m_preset_bundle->printers.get_edited_preset().config; - auto default_config = m_preset_bundle->full_config(); + load_initial_data(); auto *nozzle_diameter = dynamic_cast(m_config->option("nozzle_diameter")); m_initial_extruders_count = m_extruders_count = nozzle_diameter->values.size(); + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); auto page = add_options_page(_(L("General")), "printer_empty.png"); auto optgroup = page->new_optgroup(_(L("Size and coordinates"))); @@ -1025,8 +1299,10 @@ void TabPrinter::build() { auto dlg = new BedShapeDialog(this); dlg->build_dialog(m_config->option("bed_shape")); - if (dlg->ShowModal() == wxID_OK) + if (dlg->ShowModal() == wxID_OK){ load_key_value("bed_shape", dlg->GetValue()); + update_changed_ui(); + } })); return sizer; @@ -1244,6 +1520,15 @@ void TabPrinter::build() option.opt.height = 250; optgroup->append_single_option_line(option); + page = add_options_page(_(L("Dependencies")), "wrench.png"); + optgroup = page->new_optgroup(_(L("Profile dependencies"))); + line = Line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_parent_preset_description_line); + }; + optgroup->append_line(line); + build_extruder_pages(); if (!m_no_controller) @@ -1420,12 +1705,17 @@ void TabPrinter::update(){ void Tab::load_current_preset() { auto preset = m_presets->get_edited_preset(); + preset.is_default ? m_btn_delete_preset->Disable() : m_btn_delete_preset->Enable(true); update(); // For the printer profile, generate the extruder pages. on_preset_loaded(); // Reload preset pages with the new configuration values. reload_config(); + const Preset* parent = m_presets->get_selected_preset_parent(); + m_nonsys_btn_icon = parent == nullptr ? + "bullet_white.png" : + wxMSW ? "sys_unlock.png" : "lock_open.png"; // use CallAfter because some field triggers schedule on_change calls using CallAfter, // and we don't want them to be called after this update_dirty() as they would mark the @@ -1440,8 +1730,14 @@ void Tab::load_current_preset() if (name() == "print") update_frequently_changed_parameters(); - if (m_name == "printer") + if (m_name == "printer"){ static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + const Preset* parent_preset = m_presets->get_selected_preset_parent(); + static_cast(this)->m_sys_extruders_count = parent_preset == nullptr ? 0 : + static_cast(parent_preset->config.option("nozzle_diameter"))->values.size(); + } + update_sys_ui_after_sel_preset(); + update_full_options_list(); update_changed_ui(); }); } @@ -1587,6 +1883,8 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) if (p->title() == selection) { page = p.get(); + m_is_nonsys_values = page->m_is_nonsys_values; + m_is_modified_values = page->m_is_modified_values; break; } if (page == nullptr) return; @@ -1596,6 +1894,8 @@ void Tab::OnTreeSelChange(wxTreeEvent& event) page->Show(); m_hsizer->Layout(); Refresh(); + + update_undo_buttons(); } void Tab::OnKeyDown(wxKeyEvent& event) @@ -1631,7 +1931,7 @@ void Tab::save_preset(std::string name /*= ""*/) std::vector values; for (size_t i = 0; i < m_presets->size(); ++i) { const Preset &preset = m_presets->preset(i); - if (preset.is_default || preset.is_external) + if (preset.is_default || preset.is_system || preset.is_external) continue; values.push_back(preset.name); } @@ -1645,6 +1945,15 @@ void Tab::save_preset(std::string name /*= ""*/) show_error(this, _(L("The supplied name is empty. It can't be saved."))); return; } + const Preset *existing = m_presets->find_preset(name, false); + if (existing && (existing->is_default || existing->is_system)) { + show_error(this, _(L("Cannot overwrite a system profile."))); + return; + } + if (existing && (existing->is_external)) { + show_error(this, _(L("Cannot overwrite an external."))); + return; + } } // Save the preset into Slic3r::data_dir / presets / section_name / preset_name.ini @@ -1655,6 +1964,10 @@ void Tab::save_preset(std::string name /*= ""*/) update_tab_ui(); // Update the selection boxes at the platter. on_presets_changed(); + + if (m_name == "printer") + static_cast(this)->m_initial_extruders_count = static_cast(this)->m_extruders_count; + update_changed_ui(); } // Called for a currently selected preset. @@ -1744,7 +2057,7 @@ wxSizer* Tab::compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox for (size_t idx = 0; idx < printers->size(); ++idx) { Preset& preset = printers->preset(idx); - if (!preset.is_default && !preset.is_external) + if (!preset.is_default && !preset.is_external && !preset.is_system) presets.Add(preset.name); } @@ -1830,6 +2143,19 @@ ConfigOptionsGroupShp Page::new_optgroup(wxString title, int noncommon_label_wid return config; }; + optgroup->m_get_sys_config = [this](){ + DynamicPrintConfig config = static_cast(GetParent())->m_presets->get_selected_preset_parent()->config; + return config; + }; + + optgroup->have_sys_config = [this](){ + return static_cast(GetParent())->m_presets->get_selected_preset_parent() != nullptr; + }; + + optgroup->nonsys_btn_icon = [this](){ + return static_cast(GetParent())->m_nonsys_btn_icon; + }; + vsizer()->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 10); m_optgroups.push_back(optgroup); diff --git a/xs/src/slic3r/GUI/Tab.hpp b/xs/src/slic3r/GUI/Tab.hpp index 4f65f1475..f39ff728c 100644 --- a/xs/src/slic3r/GUI/Tab.hpp +++ b/xs/src/slic3r/GUI/Tab.hpp @@ -54,6 +54,9 @@ public: } ~Page(){} + bool m_is_modified_values{ false }; + bool m_is_nonsys_values{ true }; + public: std::vector m_optgroups; DynamicPrintConfig* m_config; @@ -90,26 +93,35 @@ protected: wxImageList* m_icons; wxCheckBox* m_compatible_printers_checkbox; wxButton* m_compatible_printers_btn; + wxButton* m_undo_btn; + wxButton* m_undo_to_sys_btn; int m_icon_count; - std::map m_icon_index; // Map from an icon file name to its index in $self->{icons}. - std::vector m_pages; // $self->{pages} = []; + std::map m_icon_index; // Map from an icon file name to its index + std::vector m_pages; bool m_disable_tree_sel_changed_event; bool m_show_incompatible_presets; bool m_no_controller; std::vector m_reload_dependent_tabs = {}; std::vector m_dirty_options = {}; + std::vector m_sys_options = {}; + std::vector m_full_options_list = {}; // The two following two event IDs are generated at Plater.pm by calling Wx::NewEventType. wxEventType m_event_value_change = 0; wxEventType m_event_presets_changed = 0; + bool m_is_modified_values{ false }; + bool m_is_nonsys_values{ true }; + public: PresetBundle* m_preset_bundle; bool m_show_btn_incompatible_presets = false; PresetCollection* m_presets; DynamicPrintConfig* m_config; + std::string m_nonsys_btn_icon; + ogStaticText* m_parent_preset_description_line; public: Tab() {} @@ -147,13 +159,21 @@ public: void update_show_hide_incompatible_button(); void update_ui_from_settings(); void update_changed_ui(); - + void update_full_options_list(); + void update_sys_ui_after_sel_preset(); + void update_changed_tree_ui(); + void update_undo_buttons(); + + void on_back_to_initial_value(); + void on_back_to_sys_value(); + PageShp add_options_page(wxString title, std::string icon, bool is_extruder_pages = false); virtual void OnActivate(){} virtual void on_preset_loaded(){} virtual void build() = 0; virtual void update() = 0; + void load_initial_data(); void update_dirty(); void update_tab_ui(); void load_config(DynamicPrintConfig config); @@ -220,6 +240,7 @@ public: size_t m_extruders_count; size_t m_initial_extruders_count; + size_t m_sys_extruders_count; std::vector m_extruder_pages; TabPrinter() {} diff --git a/xs/xsp/GUI_AppConfig.xsp b/xs/xsp/GUI_AppConfig.xsp index 8162b9a5e..08a88883d 100644 --- a/xs/xsp/GUI_AppConfig.xsp +++ b/xs/xsp/GUI_AppConfig.xsp @@ -41,4 +41,6 @@ void update_skein_dir(char *dir); std::string get_last_output_dir(const char *alt = ""); void update_last_output_dir(char *dir); + + void reset_selections(); }; diff --git a/xs/xsp/GUI_Preset.xsp b/xs/xsp/GUI_Preset.xsp index 0033ebd0e..84efdde53 100644 --- a/xs/xsp/GUI_Preset.xsp +++ b/xs/xsp/GUI_Preset.xsp @@ -12,6 +12,7 @@ bool default() %code%{ RETVAL = THIS->is_default; %}; bool external() %code%{ RETVAL = THIS->is_external; %}; + bool system() %code%{ RETVAL = THIS->is_system; %}; bool visible() %code%{ RETVAL = THIS->is_visible; %}; bool dirty() %code%{ RETVAL = THIS->is_dirty; %}; bool compatible() %code%{ RETVAL = THIS->is_compatible; %}; @@ -110,10 +111,10 @@ PresetCollection::arrayref() croak("Cannot create configuration directories:\n%s\n", e.what()); } %}; - void load_presets() + void load_presets(AppConfig *config) %code%{ try { - THIS->load_presets(); + THIS->load_presets(*config); } catch (std::exception& e) { croak("Loading of Slic3r presets from %s failed.\n\n%s\n", Slic3r::data_dir().c_str(), e.what()); @@ -143,6 +144,14 @@ PresetCollection::arrayref() croak("Loading of a config bundle %s failed:\n%s\n", path, e.what()); } %}; + void install_vendor_configbundle(const char *path) + %code%{ + try { + THIS->install_vendor_configbundle(path); + } catch (std::exception& e) { + croak("Installing a vendor config bundle %s failed:\n%s\n", path, e.what()); + } + %}; void export_configbundle(char *path) %code%{ try { @@ -154,7 +163,6 @@ PresetCollection::arrayref() void set_default_suppressed(bool default_suppressed); - void load_selections (AppConfig *config) %code%{ THIS->load_selections(*config); %}; void export_selections(AppConfig *config) %code%{ THIS->export_selections(*config); %}; void export_selections_pp(PlaceholderParser *pp) %code%{ THIS->export_selections(*pp); %};