diff --git a/resources/profiles/Creality.idx b/resources/profiles/Creality.idx index caa304bad..412be2ccf 100644 --- a/resources/profiles/Creality.idx +++ b/resources/profiles/Creality.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.3.2-alpha0 +0.1.0 Added Ender-7, Sermoon D1, CR-10 SMART min_slic3r_version = 2.3.1-beta 0.0.17 Updated start g-code. Added specific start g-code for straingauge printers. Improved output filename format. Added filament profile. 0.0.16 Updated CR6-SE start g-code. Added and updated filament profiles. diff --git a/resources/profiles/Creality.ini b/resources/profiles/Creality.ini index b055641ee..9c4dc373b 100644 --- a/resources/profiles/Creality.ini +++ b/resources/profiles/Creality.ini @@ -5,7 +5,7 @@ name = Creality # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 0.0.17 +config_version = 0.1.0 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Creality/ # changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -86,6 +86,15 @@ bed_model = ender6_bed.stl bed_texture = ender6.svg default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:ENDER7] +name = Creality Ender-7 +variants = 0.4 +technology = FFF +family = ENDER +bed_model = ender7_bed.stl +bed_texture = ender7.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY + [printer_model:ENDER2] name = Creality Ender-2 variants = 0.4 @@ -131,6 +140,15 @@ bed_model = cr10s4_bed.stl bed_texture = cr10s4.svg default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:CR10SMART] +name = Creality CR-10 SMART +variants = 0.4 +technology = FFF +family = CR +bed_model = cr10v2_bed.stl +bed_texture = cr10spro.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY + [printer_model:CR10MINI] name = Creality CR-10 Mini variants = 0.4 @@ -275,6 +293,15 @@ default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @ #bed_texture = cr10spro.svg #default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY +[printer_model:SERMOOND1] +name = Creality Sermoon-D1 +variants = 0.4 +technology = FFF +family = SERMOON +bed_model = sermoond1_bed.stl +bed_texture = sermoond1.svg +default_materials = Generic PLA @CREALITY; Generic PETG @CREALITY; Generic ABS @CREALITY; Creality PLA @CREALITY; Prusament PLA @CREALITY; Prusament PETG @CREALITY; AzureFilm PLA @CREALITY; Devil Design PLA @CREALITY; Devil Design PLA Matt @CREALITY; Devil Design PLA Galaxy @CREALITY; Extrudr PLA NX2 @CREALITY; Real Filament PLA @CREALITY; Velleman PLA @CREALITY; 3DJAKE ecoPLA @CREALITY; 3DJAKE ecoPLA Matt @CREALITY; 3DJAKE ecoPLA Tough @CREALITY; 123-3D Jupiter PLA @CREALITY + # All presets starting with asterisk, for example *common*, are intermediate and they will # not make it into the user interface. @@ -432,36 +459,36 @@ top_solid_layers = 4 [print:0.08mm SUPERDETAIL @CREALITY] inherits = *0.08mm* -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 [print:0.10mm HIGHDETAIL @CREALITY] inherits = *0.10mm* renamed_from = "0.10mm HIGHDETAIL @ENDER3" -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 [print:0.12mm DETAIL @CREALITY] inherits = *0.12mm* renamed_from = "0.12mm DETAIL @ENDER3" -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 [print:0.16mm OPTIMAL @CREALITY] inherits = *0.16mm* renamed_from = "0.15mm OPTIMAL @ENDER3"; "0.15mm OPTIMAL @CREALITY" -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 [print:0.20mm NORMAL @CREALITY] inherits = *0.20mm* renamed_from = "0.20mm NORMAL @ENDER3" -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 [print:0.24mm DRAFT @CREALITY] inherits = *0.24mm* renamed_from = "0.24mm DRAFT @ENDER3" -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 [print:0.28mm SUPERDRAFT @CREALITY] inherits = *0.28mm* -compatible_printers_condition = printer_model=~/(ENDER|CR).*/ and nozzle_diameter[0]==0.4 +compatible_printers_condition = printer_model=~/(ENDER|CR|SERMOON).*/ and nozzle_diameter[0]==0.4 # When submitting new filaments please print the following temperature tower at 0.1mm layer height: # https://www.thingiverse.com/thing:2615842 @@ -960,6 +987,13 @@ max_print_height = 400 printer_model = ENDER6 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_CREALITY\nPRINTER_MODEL_ENDER6\nPRINTER_HAS_BOWDEN +[printer:Creality Ender-7] +inherits = *common*; *descendingz* +bed_shape = 5x5,245x5,245x245,5x245 +max_print_height = 300 +printer_model = ENDER7 +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_CREALITY\nPRINTER_MODEL_ENDER7\nPRINTER_HAS_BOWDEN + [printer:Creality Ender-2] inherits = *common* renamed_from = "Creality ENDER-2" @@ -998,6 +1032,14 @@ max_print_height = 400 printer_model = CR6MAX 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_CREALITY\nPRINTER_MODEL_CR6MAX\nPRINTER_HAS_BOWDEN +[printer:Creality CR-10 SMART] +inherits = *common*; *straingauge* +retract_length = 6 +bed_shape = 5x5,295x5,295x295,5x295 +max_print_height = 400 +printer_model = CR10SMART +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_CREALITY\nPRINTER_MODEL_CR10SMART\nPRINTER_HAS_BOWDEN + [printer:Creality CR-10 Mini] inherits = *common* retract_length = 6 @@ -1118,3 +1160,11 @@ printer_notes = Don't remove the following keywords! These keywords are used in #max_print_height = 400 #printer_model = CRXPRO #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_CREALITY\nPRINTER_MODEL_CRXPRO\nPRINTER_HAS_BOWDEN + +[printer:Creality Sermoon-D1] +inherits = *common*; *descendingz* +retract_length = 1 +bed_shape = 5x5,275x5,275x255,5x255 +max_print_height = 310 +printer_model = SERMOOND1 +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_CREALITY\nPRINTER_MODEL_SERMOOND1\nPRINTER_HAS_BOWDEN diff --git a/resources/profiles/Creality/ENDER7_thumbnail.png b/resources/profiles/Creality/ENDER7_thumbnail.png new file mode 100644 index 000000000..0ede9649a Binary files /dev/null and b/resources/profiles/Creality/ENDER7_thumbnail.png differ diff --git a/resources/profiles/Creality/ender7.svg b/resources/profiles/Creality/ender7.svg new file mode 100644 index 000000000..29e93753f --- /dev/null +++ b/resources/profiles/Creality/ender7.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/profiles/Creality/ender7_bed.stl b/resources/profiles/Creality/ender7_bed.stl new file mode 100644 index 000000000..9cf19e483 Binary files /dev/null and b/resources/profiles/Creality/ender7_bed.stl differ diff --git a/resources/profiles/Creality/sermoond1.svg b/resources/profiles/Creality/sermoond1.svg new file mode 100644 index 000000000..1a93d9537 --- /dev/null +++ b/resources/profiles/Creality/sermoond1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/resources/profiles/Creality/sermoond1_bed.stl b/resources/profiles/Creality/sermoond1_bed.stl new file mode 100644 index 000000000..edab280e9 Binary files /dev/null and b/resources/profiles/Creality/sermoond1_bed.stl differ diff --git a/resources/profiles/PrusaResearch.idx b/resources/profiles/PrusaResearch.idx index bf9229753..a09ebd5b3 100644 --- a/resources/profiles/PrusaResearch.idx +++ b/resources/profiles/PrusaResearch.idx @@ -1,4 +1,6 @@ min_slic3r_version = 2.4.0-alpha0 +1.4.0-alpha4 Decreased Area Fill (SL1S). +1.4.0-alpha3 Updated SL1S tilt times. 1.4.0-alpha2 Updated Prusa MINI machine limits. 1.4.0-alpha1 Added new SL1S resin profiles. 1.4.0-alpha0 Bumped up config version. diff --git a/resources/profiles/PrusaResearch.ini b/resources/profiles/PrusaResearch.ini index 3dd910057..74fead88d 100644 --- a/resources/profiles/PrusaResearch.ini +++ b/resources/profiles/PrusaResearch.ini @@ -5,7 +5,7 @@ name = Prusa Research # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.4.0-alpha2 +config_version = 1.4.0-alpha4 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaResearch/ changelog_url = https://files.prusa3d.com/?latest=slicer-profiles&lng=%1% @@ -5440,20 +5440,6 @@ initial_exposure_time = 25 material_type = Tough material_vendor = 3DM -[sla_material:Peopoly Clear Tough @0.025 SL1S] -inherits = *0.025_sl1s* -exposure_time = 1.8 -initial_exposure_time = 25 -material_type = Tough -material_vendor = Peopoly - -[sla_material:Peopoly White Tough @0.025 SL1S] -inherits = *0.025_sl1s* -exposure_time = 1.8 -initial_exposure_time = 25 -material_type = Tough -material_vendor = Peopoly - [sla_material:Peopoly Deft White @0.025 SL1S] inherits = *0.025_sl1s* exposure_time = 1.8 @@ -5568,20 +5554,6 @@ initial_exposure_time = 25 material_type = Tough material_vendor = 3DM -[sla_material:Peopoly Clear Tough @0.05 SL1S] -inherits = *0.05_sl1s* -exposure_time = 2 -initial_exposure_time = 25 -material_type = Tough -material_vendor = Peopoly - -[sla_material:Peopoly White Tough @0.05 SL1S] -inherits = *0.05_sl1s* -exposure_time = 2 -initial_exposure_time = 25 -material_type = Tough -material_vendor = Peopoly - [sla_material:Peopoly Deft White @0.05 SL1S] inherits = *0.05_sl1s* exposure_time = 2 @@ -5696,20 +5668,6 @@ initial_exposure_time = 25 material_type = Tough material_vendor = 3DM -[sla_material:Peopoly Clear Tough @0.1 SL1S] -inherits = *0.1_sl1s* -exposure_time = 2.6 -initial_exposure_time = 25 -material_type = Tough -material_vendor = Peopoly - -[sla_material:Peopoly White Tough @0.1 SL1S] -inherits = *0.1_sl1s* -exposure_time = 2.6 -initial_exposure_time = 25 -material_type = Tough -material_vendor = Peopoly - [sla_material:Peopoly Deft White @0.1 SL1S] inherits = *0.1_sl1s* exposure_time = 2.6 @@ -6346,7 +6304,7 @@ thumbnails = 16x16,220x124 bed_shape = 0x0,180x0,180x180,0x180 default_filament_profile = "Prusament PLA" default_print_profile = 0.15mm QUALITY @MINI -gcode_flavor = marlinfirmware +gcode_flavor = marlin2 machine_max_acceleration_e = 5000 machine_max_acceleration_extruding = 1250 machine_max_acceleration_retracting = 1250 @@ -6458,8 +6416,8 @@ display_pixels_y = 1620 display_width = 128 elefant_foot_compensation = 0.2 elefant_foot_min_width = 0.2 -fast_tilt_time = 4 -slow_tilt_time = 5.7 +fast_tilt_time = 2.5 +slow_tilt_time = 5 gamma_correction = 1 max_print_height = 150 min_exposure_time = 1 @@ -6468,7 +6426,7 @@ min_initial_exposure_time = 1 max_initial_exposure_time = 300 printer_correction = 1,1,1 relative_correction = 1,1 -area_fill = 50 +area_fill = 45 # The obsolete presets will be removed when upgrading from the legacy configuration structure (up to Slic3r 1.39.2) to 1.40.0 and newer. [obsolete_presets] diff --git a/resources/profiles/PrusaResearch/sl1s.svg b/resources/profiles/PrusaResearch/sl1s.svg index f9301bae6..990382ffd 100644 --- a/resources/profiles/PrusaResearch/sl1s.svg +++ b/resources/profiles/PrusaResearch/sl1s.svg @@ -1,64 +1,54 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/profiles/PrusaResearch/sl1s_bed.stl b/resources/profiles/PrusaResearch/sl1s_bed.stl index 0c7479962..a9bca83f6 100644 Binary files a/resources/profiles/PrusaResearch/sl1s_bed.stl and b/resources/profiles/PrusaResearch/sl1s_bed.stl differ diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 50e5096bf..3f43c3ebd 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -118,7 +118,8 @@ int CLI::run(int argc, char **argv) boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), "gcodeviewer"); #endif // _WIN32 - const std::vector &load_configs = m_config.option("load", true)->values; + const std::vector &load_configs = m_config.option("load", true)->values; + const ForwardCompatibilitySubstitutionRule config_substitution_rule = m_config.option>("config_compatibility", true)->value; // load config files supplied via --load for (auto const &file : load_configs) { @@ -130,13 +131,19 @@ int CLI::run(int argc, char **argv) return 1; } } - DynamicPrintConfig config; + DynamicPrintConfig config; + ConfigSubstitutions config_substitutions; try { - config.load(file); + config_substitutions = config.load(file, config_substitution_rule); } catch (std::exception &ex) { - boost::nowide::cerr << "Error while reading config file: " << ex.what() << std::endl; + boost::nowide::cerr << "Error while reading config file \"" << file << "\": " << ex.what() << std::endl; return 1; } + if (! config_substitutions.empty()) { + boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; + for (const ConfigSubstitution &subst : config_substitutions) + boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; + } config.normalize_fdm(); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { @@ -174,7 +181,9 @@ int CLI::run(int argc, char **argv) try { // When loading an AMF or 3MF, config is imported as well, including the printer technology. DynamicPrintConfig config; - model = Model::read_from_file(file, &config, true); + ConfigSubstitutionContext config_substitutions(config_substitution_rule); + //FIXME should we check the version here? // | Model::LoadAttribute::CheckVersion ? + model = Model::read_from_file(file, &config, &config_substitutions, Model::LoadAttribute::AddDefaultInstances); PrinterTechnology other_printer_technology = get_printer_technology(config); if (printer_technology == ptUnknown) { printer_technology = other_printer_technology; @@ -183,6 +192,11 @@ int CLI::run(int argc, char **argv) boost::nowide::cerr << "Mixing configurations for FFF and SLA technologies" << std::endl; return 1; } + if (! config_substitutions.substitutions.empty()) { + boost::nowide::cout << "The following configuration values were substituted when loading \" << file << \":\n"; + for (const ConfigSubstitution& subst : config_substitutions.substitutions) + boost::nowide::cout << "\tkey = \"" << subst.opt_def->opt_key << "\"\t loaded = \"" << subst.old_value << "\tsubstituted = \"" << subst.new_value->serialize() << "\"\n"; + } // config is applied to m_print_config before the current m_config values. config += std::move(m_print_config); m_print_config = std::move(config); @@ -362,7 +376,7 @@ int CLI::run(int argc, char **argv) o->cut(Z, m_config.opt_float("cut"), &out); } #else - model.objects.front()->cut(0, m_config.opt_float("cut"), true, true, true); + model.objects.front()->cut(0, m_config.opt_float("cut"), ModelObjectCutAttribute::KeepLower | ModelObjectCutAttribute::KeepUpper | ModelObjectCutAttribute::FlipLower); #endif model.delete_object(size_t(0)); } diff --git a/src/libnest2d/include/libnest2d/geometry_traits.hpp b/src/libnest2d/include/libnest2d/geometry_traits.hpp index 7ea437339..f388e37b1 100644 --- a/src/libnest2d/include/libnest2d/geometry_traits.hpp +++ b/src/libnest2d/include/libnest2d/geometry_traits.hpp @@ -501,8 +501,9 @@ inline P _Box

::center() const BP2D_NOEXCEPT { using Coord = TCoord

; P ret = { // No rounding here, we dont know if these are int coords - Coord( (getX(minc) + getX(maxc)) / Coord(2) ), - Coord( (getY(minc) + getY(maxc)) / Coord(2) ) + // Doing the division like this increases the max range of x and y coord + getX(minc) / Coord(2) + getX(maxc) / Coord(2), + getY(minc) / Coord(2) + getY(maxc) / Coord(2) }; return ret; diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index 81e4db2ba..cee9eafdc 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -4,7 +4,7 @@ #include "Exception.hpp" #include "LocalesUtils.hpp" #include "Thread.hpp" - +#include "format.hpp" #include #include @@ -18,15 +18,24 @@ #include #include #include +#include -//#include -//#include "I18N.hpp" +#ifdef WIN32 +//FIXME replace the two following includes with after it becomes mainstream. +#include +#include +#endif namespace Slic3r { static const std::string VENDOR_PREFIX = "vendor:"; static const std::string MODEL_PREFIX = "model:"; -static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; +// Because of a crash in PrusaSlicer 2.3.0/2.3.1 when showing an update notification with some locales, we don't want PrusaSlicer 2.3.0/2.3.1 +// to show this notification. On the other hand, we would like PrusaSlicer 2.3.2 to show an update notification of the upcoming PrusaSlicer 2.4.0. +// Thus we will let PrusaSlicer 2.3.2 and couple of follow-up versions to download the version number from an alternate file until the PrusaSlicer 2.3.0/2.3.1 +// are phased out, then we will revert to the original name. +//static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version"; +static const std::string VERSION_CHECK_URL = "https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/PrusaSlicer.version2"; const std::string AppConfig::SECTION_FILAMENTS = "filaments"; const std::string AppConfig::SECTION_MATERIALS = "sla_materials"; @@ -177,25 +186,114 @@ void AppConfig::set_defaults() erase("", "object_settings_size"); } +#ifdef WIN32 +static std::string appconfig_md5_hash_line(const std::string_view data) +{ + //FIXME replace the two following includes with after it becomes mainstream. + // return boost::md5(data).hex_str_value(); + // boost::uuids::detail::md5 is an internal namespace thus it may change in the future. + // Also this implementation is not the fastest, it was designed for short blocks of text. + using boost::uuids::detail::md5; + md5 md5_hash; + // unsigned int[4], 128 bits + md5::digest_type md5_digest{}; + std::string md5_digest_str; + md5_hash.process_bytes(data.data(), data.size()); + md5_hash.get_digest(md5_digest); + boost::algorithm::hex(md5_digest, md5_digest + std::size(md5_digest), std::back_inserter(md5_digest_str)); + // MD5 hash is 32 HEX digits long. + assert(md5_digest_str.size() == 32); + // This line will be emited at the end of the file. + return "# MD5 checksum " + md5_digest_str + "\n"; +}; + +// Assume that the last line with the comment inside the config file contains a checksum and that the user didn't modify the config file. +static bool verify_config_file_checksum(boost::nowide::ifstream &ifs) +{ + auto read_whole_config_file = [&ifs]() -> std::string { + std::stringstream ss; + ss << ifs.rdbuf(); + return ss.str(); + }; + + ifs.seekg(0, boost::nowide::ifstream::beg); + std::string whole_config = read_whole_config_file(); + + // The checksum should be on the last line in the config file. + if (size_t last_comment_pos = whole_config.find_last_of('#'); last_comment_pos != std::string::npos) { + // Split read config into two parts, one with checksum, and the second part is part with configuration from the checksum was computed. + // Verify existence and validity of the MD5 checksum line at the end of the file. + // When the checksum isn't found, the checksum was not saved correctly, it was removed or it is an older config file without the checksum. + // If the checksum is incorrect, then the file was either not saved correctly or modified. + if (std::string_view(whole_config.c_str() + last_comment_pos, whole_config.size() - last_comment_pos) == appconfig_md5_hash_line({ whole_config.data(), last_comment_pos })) + return true; + } + return false; +} +#endif + std::string AppConfig::load() { // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; pt::ptree tree; - boost::nowide::ifstream ifs(AppConfig::config_path()); + boost::nowide::ifstream ifs; + bool recovered = false; + try { + ifs.open(AppConfig::config_path()); +#ifdef WIN32 + // Verify the checksum of the config file without taking just for debugging purpose. + if (!verify_config_file_checksum(ifs)) + BOOST_LOG_TRIVIAL(info) << "The configuration file " << AppConfig::config_path() << + " has a wrong MD5 checksum or the checksum is missing. This may indicate a file corruption or a harmless user edit."; + + ifs.seekg(0, boost::nowide::ifstream::beg); +#endif pt::read_ini(ifs, tree); } catch (pt::ptree_error& ex) { - // Error while parsing config file. We'll customize the error message and rethrow to be displayed. - // ! But to avoid the use of _utf8 (related to use of wxWidgets) - // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty - /* - throw Slic3r::RuntimeError( - _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " - "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) + - "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); - */ - return ex.what(); +#ifdef WIN32 + // The configuration file is corrupted, try replacing it with the backup configuration. + ifs.close(); + std::string backup_path = (boost::format("%1%.bak") % AppConfig::config_path()).str(); + if (boost::filesystem::exists(backup_path)) { + // Compute checksum of the configuration backup file and try to load configuration from it when the checksum is correct. + boost::nowide::ifstream backup_ifs(backup_path); + if (!verify_config_file_checksum(backup_ifs)) { + BOOST_LOG_TRIVIAL(error) << format("Both \"%1%\" and \"%2%\" are corrupted. It isn't possible to restore configuration from the backup.", AppConfig::config_path(), backup_path); + backup_ifs.close(); + boost::filesystem::remove(backup_path); + } else if (std::string error_message; copy_file(backup_path, AppConfig::config_path(), error_message, false) != SUCCESS) { + BOOST_LOG_TRIVIAL(error) << format("Configuration file \"%1%\" is corrupted. Failed to restore from backup \"%2%\": %3%", AppConfig::config_path(), backup_path, error_message); + backup_ifs.close(); + boost::filesystem::remove(backup_path); + } else { + BOOST_LOG_TRIVIAL(info) << format("Configuration file \"%1%\" was corrupted. It has been succesfully restored from the backup \"%2%\".", AppConfig::config_path(), backup_path); + // Try parse configuration file after restore from backup. + try { + ifs.open(AppConfig::config_path()); + pt::read_ini(ifs, tree); + recovered = true; + } catch (pt::ptree_error& ex) { + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\" after it has been restored from backup: %2%", AppConfig::config_path(), ex.what()); + } + } + } else +#endif // WIN32 + BOOST_LOG_TRIVIAL(info) << format("Failed to parse configuration file \"%1%\": %2%", AppConfig::config_path(), ex.what()); + if (! recovered) { + // Report the initial error of parsing PrusaSlicer.ini. + // Error while parsing config file. We'll customize the error message and rethrow to be displayed. + // ! But to avoid the use of _utf8 (related to use of wxWidgets) + // we will rethrow this exception from the place of load() call, if returned value wouldn't be empty + /* + throw Slic3r::RuntimeError( + _utf8(L("Error parsing PrusaSlicer config file, it is probably corrupted. " + "Try to manually delete the file to recover from the error. Your user profiles will not be affected.")) + + "\n\n" + AppConfig::config_path() + "\n\n" + ex.what()); + */ + return ex.what(); + } } // 2) Parse the property_tree, extract the sections and key / value pairs. @@ -272,22 +370,21 @@ void AppConfig::save() const auto path = config_path(); std::string path_pid = (boost::format("%1%.%2%") % path % get_current_pid()).str(); - boost::nowide::ofstream c; - c.open(path_pid, std::ios::out | std::ios::trunc); + std::stringstream config_ss; if (m_mode == EAppMode::Editor) - c << "# " << Slic3r::header_slic3r_generated() << std::endl; + config_ss << "# " << Slic3r::header_slic3r_generated() << std::endl; else - c << "# " << Slic3r::header_gcodeviewer_generated() << std::endl; + config_ss << "# " << Slic3r::header_gcodeviewer_generated() << std::endl; // Make sure the "no" category is written first. for (const auto& kvp : m_storage[""]) - c << kvp.first << " = " << kvp.second << std::endl; + config_ss << kvp.first << " = " << kvp.second << std::endl; // Write the other categories. for (const auto& category : m_storage) { if (category.first.empty()) continue; - c << std::endl << "[" << category.first << "]" << std::endl; + config_ss << std::endl << "[" << category.first << "]" << std::endl; for (const auto& kvp : category.second) - c << kvp.first << " = " << kvp.second << std::endl; + config_ss << kvp.first << " = " << kvp.second << std::endl; } // Write vendor sections for (const auto &vendor : m_vendors) { @@ -295,17 +392,42 @@ void AppConfig::save() for (const auto &model : vendor.second) { size_sum += model.second.size(); } if (size_sum == 0) { continue; } - c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl; + config_ss << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl; for (const auto &model : vendor.second) { - if (model.second.size() == 0) { continue; } + if (model.second.empty()) { continue; } const std::vector variants(model.second.begin(), model.second.end()); const auto escaped = escape_strings_cstyle(variants); - c << MODEL_PREFIX << model.first << " = " << escaped << std::endl; + config_ss << MODEL_PREFIX << model.first << " = " << escaped << std::endl; } } - c.close(); + // One empty line before the MD5 sum. + config_ss << std::endl; + std::string config_str = config_ss.str(); + boost::nowide::ofstream c; + c.open(path_pid, std::ios::out | std::ios::trunc); + c << config_str; +#ifdef WIN32 + // WIN32 specific: The final "rename_file()" call is not safe in case of an application crash, there is no atomic "rename file" API + // provided by Windows (sic!). Therefore we save a MD5 checksum to be able to verify file corruption. In addition, + // we save the config file into a backup first before moving it to the final destination. + c << appconfig_md5_hash_line(config_str); +#endif + c.close(); + +#ifdef WIN32 + // Make a backup of the configuration file before copying it to the final destination. + std::string error_message; + std::string backup_path = (boost::format("%1%.bak") % path).str(); + // Copy configuration file with PID suffix into the configuration file with "bak" suffix. + if (copy_file(path_pid, backup_path, error_message, false) != SUCCESS) + BOOST_LOG_TRIVIAL(error) << "Copying from " << path_pid << " to " << backup_path << " failed. Failed to create a backup configuration."; +#endif + + // Rename the config atomically. + // On Windows, the rename is likely NOT atomic, thus it may fail if PrusaSlicer crashes on another thread in the meanwhile. + // To cope with that, we already made a backup of the config on Windows. rename_file(path_pid, path); m_dirty = false; } diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index c1bc0837c..07b09c5c8 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -37,7 +37,7 @@ public: // Load the slic3r.ini from a user profile directory (or a datadir, if configured). // return error string or empty strinf - std::string load(); + std::string load(); // Store the slic3r.ini into a user profile directory (or a datadir, if configured). void save(); @@ -63,12 +63,12 @@ public: { std::string value; this->get("", key, value); return value; } void set(const std::string §ion, const std::string &key, const std::string &value) { -#ifndef _NDEBUG +#ifndef NDEBUG std::string key_trimmed = key; boost::trim_all(key_trimmed); assert(key_trimmed == key); assert(! key_trimmed.empty()); -#endif // _NDEBUG +#endif // NDEBUG std::string &old = m_storage[section][key]; if (old != value) { old = value; diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index d04c08272..4d4dece27 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -33,6 +33,7 @@ add_library(libslic3r STATIC EdgeGrid.hpp ElephantFootCompensation.cpp ElephantFootCompensation.hpp + enum_bitmask.hpp ExPolygon.cpp ExPolygon.hpp ExPolygonCollection.cpp diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index bd396243c..b9f9b266d 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -23,6 +23,10 @@ #include #include +//FIXME for GCodeFlavor and gcfMarlin (for forward-compatibility conversion) +// This is not nice, likely it would be better to pass the ConfigSubstitutionContext to handle_legacy(). +#include "PrintConfig.hpp" + namespace Slic3r { // Escape \n, \r and backslash @@ -211,6 +215,10 @@ std::string escape_ampersand(const std::string& str) return std::string(out.data(), outptr - out.data()); } +void ConfigOptionDeleter::operator()(ConfigOption* p) { + delete p; +} + std::vector ConfigOptionDef::cli_args(const std::string &key) const { std::vector args; @@ -361,7 +369,8 @@ std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, s // right: option description std::string descr = def.tooltip; - if (show_defaults && def.default_value && def.type != coBool + bool show_defaults_this = show_defaults || def.opt_key == "config_compatibility"; + if (show_defaults_this && def.default_value && def.type != coBool && (def.type != coString || !def.default_value->serialize().empty())) { descr += " ("; if (!def.sidetext.empty()) { @@ -469,7 +478,7 @@ void ConfigBase::set(const std::string &opt_key, double value, bool create) } } -bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) +bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append) { t_config_option_key opt_key = opt_key_src; std::string value = value_src; @@ -479,29 +488,29 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src, if (opt_key.empty()) // Ignore the option. return true; - return this->set_deserialize_raw(opt_key, value, append); + return this->set_deserialize_raw(opt_key, value, substitutions_ctxt, append); } -void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) +void ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions_ctxt, bool append) { - if (! this->set_deserialize_nothrow(opt_key_src, value_src, append)) + if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append)) throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src)); } -void ConfigBase::set_deserialize(std::initializer_list items) +void ConfigBase::set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions_ctxt) { for (const SetDeserializeItem &item : items) - this->set_deserialize(item.opt_key, item.opt_value, item.append); + this->set_deserialize(item.opt_key, item.opt_value, substitutions_ctxt, item.append); } -bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append) +bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, ConfigSubstitutionContext& substitutions_ctxt, bool append) { - t_config_option_key opt_key = opt_key_src; + t_config_option_key opt_key = opt_key_src; // Try to deserialize the option by its name. - const ConfigDef *def = this->def(); + const ConfigDef *def = this->def(); if (def == nullptr) throw NoDefinitionException(opt_key); - const ConfigOptionDef *optdef = def->get(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. for (const auto &opt : def->options) { @@ -523,14 +532,35 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con // Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers". for (const t_config_option_key &shortcut : optdef->shortcut) // Recursive call. - if (! this->set_deserialize_raw(shortcut, value, append)) + if (! this->set_deserialize_raw(shortcut, value, substitutions_ctxt, append)) return false; return true; } ConfigOption *opt = this->option(opt_key, true); assert(opt != nullptr); - return opt->deserialize(value, append); + bool success = opt->deserialize(value, append); + if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable && + // Only allow substitutions of an enum value by another enum value or a boolean value with an enum value. + // That means, we expect enum values being added in the future and possibly booleans being converted to enums. + (optdef->type == coEnum || optdef->type == coBool)) + { + // Deserialize failed, try to substitute with a default value. + assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent); + + opt->set(optdef->default_value.get()); + + if (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable) { + // Log the substitution. + ConfigSubstitution config_substitution; + config_substitution.opt_def = optdef; + config_substitution.old_value = value;//std::unique_ptr(opt); + config_substitution.new_value = ConfigOptionUniquePtr(this->option(opt_key, true)->clone()); + substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution)); + } + return true; + } + return success; } // Return an absolute value of a possibly relative config variable. @@ -589,36 +619,37 @@ void ConfigBase::setenv_() const } } -void ConfigBase::load(const std::string &file) +ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) { - if (is_gcode_file(file)) - this->load_from_gcode_file(file); - else - this->load_from_ini(file); + return is_gcode_file(file) ? + this->load_from_gcode_file(file, true /* check header */, compatibility_rule) : + this->load_from_ini(file, compatibility_rule); } -void ConfigBase::load_from_ini(const std::string &file) +ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule) { boost::property_tree::ptree tree; boost::nowide::ifstream ifs(file); boost::property_tree::read_ini(ifs, tree); - this->load(tree); + return this->load(tree, compatibility_rule); } -void ConfigBase::load(const boost::property_tree::ptree &tree) +ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule) { + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); for (const boost::property_tree::ptree::value_type &v : tree) { try { t_config_option_key opt_key = v.first; - this->set_deserialize(opt_key, v.second.get_value()); + this->set_deserialize(opt_key, v.second.get_value(), substitutions_ctxt); } catch (UnknownOptionException & /* e */) { // ignore } } + return std::move(substitutions_ctxt.substitutions); } // Load the config keys from the tail of a G-code file. -void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header) +ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, bool check_header, ForwardCompatibilitySubstitutionRule compatibility_rule) { // Read a 64k block from the end of the G-code. boost::nowide::ifstream ifs(file); @@ -639,13 +670,15 @@ void ConfigBase::load_from_gcode_file(const std::string& file, bool check_header ifs.read(data.data(), data_length); ifs.close(); - size_t key_value_pairs = load_from_gcode_string(data.data()); + ConfigSubstitutionContext substitutions_ctxt(compatibility_rule); + size_t key_value_pairs = load_from_gcode_string(data.data(), substitutions_ctxt); if (key_value_pairs < 80) throw Slic3r::RuntimeError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs)); + return std::move(substitutions_ctxt.substitutions); } // Load the config keys from the given string. -size_t ConfigBase::load_from_gcode_string(const char* str) +size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions) { if (str == nullptr) return 0; @@ -690,7 +723,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str) if (key == nullptr) break; try { - this->set_deserialize(std::string(key, key_end), std::string(value, end)); + this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions); ++num_key_value_pairs; } catch (UnknownOptionException & /* e */) { @@ -719,7 +752,7 @@ void ConfigBase::null_nullables() ConfigOption *opt = this->optptr(opt_key, false); assert(opt != nullptr); if (opt->nullable()) - opt->deserialize("nil"); + opt->deserialize("nil", ForwardCompatibilitySubstitutionRule::Disable); } } @@ -883,8 +916,10 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option // Do not unescape single string values, the unescaping is left to the calling shell. static_cast(opt_base)->value = value; } else { + // Just bail out if the configuration value is not understood. + ConfigSubstitutionContext context(ForwardCompatibilitySubstitutionRule::Disable); // Any scalar value of a type different from Bool and String. - if (! this->set_deserialize_nothrow(opt_key, value, false)) { + if (! this->set_deserialize_nothrow(opt_key, value, context, false)) { boost::nowide::cerr << "Invalid value supplied for --" << token.c_str() << std::endl; return false; } diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 14b4b7c2e..bf356dfe6 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -16,6 +16,7 @@ #include "Exception.hpp" #include "Point.hpp" +#include #include #include #include @@ -163,6 +164,41 @@ enum PrinterTechnology : unsigned char ptAny }; +enum ForwardCompatibilitySubstitutionRule +{ + Disable, + Enable, + EnableSilent, +}; + +class ConfigOption; +class ConfigOptionDef; +// For forward definition of ConfigOption in ConfigOptionUniquePtr, we have to define a custom deleter. +struct ConfigOptionDeleter { void operator()(ConfigOption* p); }; +using ConfigOptionUniquePtr = std::unique_ptr; + +// When parsing a configuration value, if the old_value is not understood by this PrusaSlicer version, +// it is being substituted with some default value that this PrusaSlicer could work with. +// This structure serves to inform the user about the substitutions having been done during file import. +struct ConfigSubstitution { + const ConfigOptionDef *opt_def { nullptr }; + std::string old_value; + ConfigOptionUniquePtr new_value; +}; + +using ConfigSubstitutions = std::vector; + +// Filled in by ConfigBase::set_deserialize_raw(), which based on "rule" either bails out +// or performs substitutions when encountering an unknown configuration value. +struct ConfigSubstitutionContext +{ + ConfigSubstitutionContext(ForwardCompatibilitySubstitutionRule rl) : rule(rl) {} + bool empty() const throw() { return substitutions.empty(); } + + ForwardCompatibilitySubstitutionRule rule; + ConfigSubstitutions substitutions; +}; + // A generic value of a configuration option. class ConfigOption { public: @@ -768,7 +804,7 @@ public: return escape_string_cstyle(this->value); } - bool deserialize(const std::string &str, bool append = false) override + bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); return unescape_string_cstyle(str, this->value); @@ -1272,8 +1308,15 @@ public: bool deserialize(const std::string &str, bool append = false) override { UNUSED(append); - this->value = (str.compare("1") == 0); - return true; + if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) { + this->value = true; + return true; + } + if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) { + this->value = false; + return true; + } + return false; } private: @@ -1687,6 +1730,14 @@ public: static const constexpr char *nocli = "~~~noCLI"; }; +inline bool operator<(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() { + return lhs.opt_def->opt_key < rhs.opt_def->opt_key || + (lhs.opt_def->opt_key == rhs.opt_def->opt_key && lhs.old_value < rhs.old_value); +} +inline bool operator==(const ConfigSubstitution &lhs, const ConfigSubstitution &rhs) throw() { + return lhs.opt_def == rhs.opt_def && lhs.old_value == rhs.old_value; +} + // Map from a config option name to its definition. // The definition does not carry an actual value of the config option, only its constant default value. // t_config_option_key is std::string @@ -1765,6 +1816,8 @@ public: } }; + + // An abstract configuration store. class ConfigBase : public ConfigOptionResolver { @@ -1853,9 +1906,11 @@ public: // Set a configuration value from a string, it will call an overridable handle_legacy() // to resolve renamed and removed configuration keys. - bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, bool append = false); + bool set_deserialize_nothrow(const t_config_option_key &opt_key_src, const std::string &value_src, ConfigSubstitutionContext& substitutions, bool append = false); // May throw BadOptionTypeException() if the operation fails. - void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext& config_substitutions, bool append = false); + void set_deserialize_strict(const t_config_option_key &opt_key, const std::string &str, bool append = false) + { ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(opt_key, str, ctxt, append); } struct SetDeserializeItem { SetDeserializeItem(const char *opt_key, const char *opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} SetDeserializeItem(const std::string &opt_key, const std::string &opt_value, bool append = false) : opt_key(opt_key), opt_value(opt_value), append(append) {} @@ -1870,17 +1925,19 @@ public: std::string opt_key; std::string opt_value; bool append = false; }; // May throw BadOptionTypeException() if the operation fails. - void set_deserialize(std::initializer_list items); + void set_deserialize(std::initializer_list items, ConfigSubstitutionContext& substitutions); + void set_deserialize_strict(std::initializer_list items) + { ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; this->set_deserialize(items, ctxt); } double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_() const; - void load(const std::string &file); - void load_from_ini(const std::string &file); - void load_from_gcode_file(const std::string& file, bool check_header = true); + ConfigSubstitutions load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); + ConfigSubstitutions load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule); + ConfigSubstitutions load_from_gcode_file(const std::string &file, bool check_header /* = true */, ForwardCompatibilitySubstitutionRule compatibility_rule); // Returns number of key/value pairs extracted. - size_t load_from_gcode_string(const char* str); - void load(const boost::property_tree::ptree &tree); + size_t load_from_gcode_string(const char* str, ConfigSubstitutionContext& substitutions); + ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule); void save(const std::string &file) const; // Set all the nullable values to nils. @@ -1888,7 +1945,7 @@ public: private: // Set a configuration value from a string. - bool set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &str, bool append); + bool set_deserialize_raw(const t_config_option_key& opt_key_src, const std::string& value, ConfigSubstitutionContext& substitutions, bool append); }; // Configuration store with dynamic number of configuration values. diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index fbf27c548..c2ba011a8 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -419,7 +419,7 @@ namespace Slic3r { _3MF_Importer(); ~_3MF_Importer(); - bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version); + bool load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version); private: void _destroy_xml_parser(); @@ -434,16 +434,16 @@ namespace Slic3r { XML_ErrorString(XML_GetErrorCode(m_xml_parser)); } - bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config); + bool _load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions); bool _extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); + void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions); void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat); - void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename); + void _extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, ConfigSubstitutionContext& subs_context, const std::string& archive_filename); bool _extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model); // handlers to parse the .model file @@ -510,7 +510,7 @@ namespace Slic3r { bool _handle_start_config_metadata(const char** attributes, unsigned int num_attributes); bool _handle_end_config_metadata(); - bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes); + bool _generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions); // callbacks to parse the .model file static void XMLCALL _handle_start_model_xml_element(void* userData, const char* name, const char** attributes); @@ -539,7 +539,7 @@ namespace Slic3r { _destroy_xml_parser(); } - bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, bool check_version) + bool _3MF_Importer::load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, bool check_version) { m_version = 0; m_check_version = check_version; @@ -560,7 +560,7 @@ namespace Slic3r { m_curr_characters.clear(); clear_errors(); - return _load_model_from_file(filename, model, config); + return _load_model_from_file(filename, model, config, config_substitutions); } void _3MF_Importer::_destroy_xml_parser() @@ -581,7 +581,7 @@ namespace Slic3r { XML_StopParser(m_xml_parser, false); } - bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config) + bool _3MF_Importer::_load_model_from_file(const std::string& filename, Model& model, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions) { mz_zip_archive archive; mz_zip_zero_struct(&archive); @@ -635,7 +635,7 @@ namespace Slic3r { } else if (boost::algorithm::iequals(name, LAYER_CONFIG_RANGES_FILE)) { // extract slic3r layer config ranges file - _extract_layer_config_ranges_from_archive(archive, stat); + _extract_layer_config_ranges_from_archive(archive, stat, config_substitutions); } else if (boost::algorithm::iequals(name, SLA_SUPPORT_POINTS_FILE)) { // extract sla support points file @@ -647,7 +647,7 @@ namespace Slic3r { } else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE)) { // extract slic3r print config file - _extract_print_config_from_archive(archive, stat, config, filename); + _extract_print_config_from_archive(archive, stat, config, config_substitutions, filename); } else if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE)) { // extract slic3r layer config ranges file @@ -704,7 +704,7 @@ namespace Slic3r { new_model_object->clear_instances(); new_model_object->add_instance(*model_object->instances.back()); model_object->delete_last_instance(); - if (!_generate_volumes(*new_model_object, *geometry, volumes)) + if (!_generate_volumes(*new_model_object, *geometry, volumes, config_substitutions)) return false; } } @@ -759,7 +759,7 @@ namespace Slic3r { if (metadata.key == "name") model_object->name = metadata.value; else - model_object->config.set_deserialize(metadata.key, metadata.value); + model_object->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } // select object's detected volumes @@ -775,7 +775,7 @@ namespace Slic3r { volumes_ptr = &volumes; } - if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr)) + if (!_generate_volumes(*model_object, obj_geometry->second, *volumes_ptr, config_substitutions)) return false; } @@ -867,7 +867,10 @@ namespace Slic3r { return true; } - void _3MF_Importer::_extract_print_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig& config, const std::string& archive_filename) + void _3MF_Importer::_extract_print_config_from_archive( + mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, + DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, + const std::string& archive_filename) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); @@ -876,7 +879,7 @@ namespace Slic3r { add_error("Error while reading config data to buffer"); return; } - config.load_from_gcode_string(buffer.data()); + config.load_from_gcode_string(buffer.data(), config_substitutions); } } @@ -942,7 +945,7 @@ namespace Slic3r { } } - void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat) + void _3MF_Importer::_extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, ConfigSubstitutionContext& config_substitutions) { if (stat.m_uncomp_size > 0) { std::string buffer((size_t)stat.m_uncomp_size, 0); @@ -987,8 +990,7 @@ namespace Slic3r { continue; std::string opt_key = option.second.get(".opt_key"); std::string value = option.second.data(); - - config.set_deserialize(opt_key, value); + config.set_deserialize(opt_key, value, config_substitutions); } config_ranges[{ min_z, max_z }].assign_config(std::move(config)); @@ -1827,7 +1829,7 @@ namespace Slic3r { return true; } - bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes) + bool _3MF_Importer::_generate_volumes(ModelObject& object, const Geometry& geometry, const ObjectMetadata::VolumeMetadataList& volumes, ConfigSubstitutionContext& config_substitutions) { if (!object.volumes.empty()) { add_error("Found invalid volumes count"); @@ -1943,7 +1945,7 @@ namespace Slic3r { else if (metadata.key == SOURCE_IN_METERS) volume->source.is_converted_from_meters = metadata.value == "1"; else - volume->config.set_deserialize(metadata.key, metadata.value); + volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions); } } @@ -2953,16 +2955,15 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv return true; } -bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) +bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version) { - if (path == nullptr || config == nullptr || model == nullptr) + if (path == nullptr || model == nullptr) return false; // All import should use "C" locales for number formatting. CNumericLocalesSetter locales_setter; - - _3MF_Importer importer; - bool res = importer.load_model_from_file(path, *model, *config, check_version); + _3MF_Importer importer; + bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version); importer.log_errors(); return res; } diff --git a/src/libslic3r/Format/3mf.hpp b/src/libslic3r/Format/3mf.hpp index a09a1b834..b91e90da7 100644 --- a/src/libslic3r/Format/3mf.hpp +++ b/src/libslic3r/Format/3mf.hpp @@ -25,11 +25,12 @@ namespace Slic3r { }; class Model; + struct ConfigSubstitutionContext; class DynamicPrintConfig; struct ThumbnailData; // Load the content of a 3mf file into the given model and preset bundle. - extern bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); + extern bool load_3mf(const char* path, DynamicPrintConfig& config, ConfigSubstitutionContext& config_substitutions, Model* model, bool check_version); // Save the given model and the config data contained in the given Print into a 3mf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 94318a930..d03cfd4fa 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -64,10 +64,11 @@ namespace Slic3r struct AMFParserContext { - AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, Model* model) : + AMFParserContext(XML_Parser parser, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model) : m_parser(parser), m_model(*model), - m_config(config) + m_config(config), + m_config_substitutions(config_substitutions) { m_path.reserve(12); } @@ -258,6 +259,8 @@ struct AMFParserContext std::string m_value[5]; // Pointer to config to update if config data are stored inside the amf file DynamicPrintConfig *m_config { nullptr }; + // Config substitution rules and collected config substitution log. + ConfigSubstitutionContext *m_config_substitutions { nullptr }; private: AMFParserContext& operator=(AMFParserContext&); @@ -702,8 +705,9 @@ void AMFParserContext::endElement(const char * /* name */) } case NODE_TYPE_METADATA: - if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) - m_config->load_from_gcode_string(m_value[1].c_str()); + if ((m_config != nullptr) && strncmp(m_value[0].c_str(), SLIC3R_CONFIG_TYPE, strlen(SLIC3R_CONFIG_TYPE)) == 0) { + m_config->load_from_gcode_string(m_value[1].c_str(), *m_config_substitutions); + } else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) { const char *opt_key = m_value[0].c_str() + 7; if (print_config_def.options.find(opt_key) != print_config_def.options.end()) { @@ -721,7 +725,7 @@ void AMFParserContext::endElement(const char * /* name */) config = &it->second; } if (config) - config->set_deserialize(opt_key, m_value[1]); + config->set_deserialize(opt_key, m_value[1], *m_config_substitutions); } else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "layer_height_profile") == 0) { // Parse object's layer height profile, a semicolon separated list of floats. char *p = m_value[1].data(); @@ -849,7 +853,7 @@ void AMFParserContext::endDocument() } // Load an AMF file into a provided model. -bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) +bool load_amf_file(const char *path, DynamicPrintConfig *config, ConfigSubstitutionContext *config_substitutions, Model *model) { if ((path == nullptr) || (model == nullptr)) return false; @@ -866,7 +870,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return false; } - AMFParserContext ctx(parser, config, model); + AMFParserContext ctx(parser, config, config_substitutions, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); @@ -908,7 +912,7 @@ bool load_amf_file(const char *path, DynamicPrintConfig *config, Model *model) return result; } -bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, Model* model, bool check_version) +bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version) { if (stat.m_uncomp_size == 0) { @@ -924,7 +928,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi return false; } - AMFParserContext ctx(parser, config, model); + AMFParserContext ctx(parser, config, config_substitutions, model); XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, AMFParserContext::startElement, AMFParserContext::endElement); XML_SetCharacterDataHandler(parser, AMFParserContext::characters); @@ -984,7 +988,7 @@ bool extract_model_from_archive(mz_zip_archive& archive, const mz_zip_archive_fi } // Load an AMF archive into a provided model. -bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) +bool load_amf_archive(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version) { if ((path == nullptr) || (model == nullptr)) return false; @@ -1010,7 +1014,7 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model { try { - if (!extract_model_from_archive(archive, stat, config, model, check_version)) + if (!extract_model_from_archive(archive, stat, config, config_substitutions, model, check_version)) { close_zip_reader(&archive); BOOST_LOG_TRIVIAL(error) << "Archive does not contain a valid model"; @@ -1052,13 +1056,13 @@ bool load_amf_archive(const char* path, DynamicPrintConfig* config, Model* model // Load an AMF file into a provided model. // If config is not a null pointer, updates it if the amf file/archive contains config data -bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version) +bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version) { CNumericLocalesSetter locales_setter; // use "C" locales and point as a decimal separator if (boost::iends_with(path, ".amf.xml")) // backward compatibility with older slic3r output - return load_amf_file(path, config, model); + return load_amf_file(path, config, config_substitutions, model); else if (boost::iends_with(path, ".amf")) { boost::nowide::ifstream file(path, boost::nowide::ifstream::binary); @@ -1069,7 +1073,7 @@ bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool c file.read(zip_mask.data(), 2); file.close(); - return (zip_mask == "PK") ? load_amf_archive(path, config, model, check_version) : load_amf_file(path, config, model); + return (zip_mask == "PK") ? load_amf_archive(path, config, config_substitutions, model, check_version) : load_amf_file(path, config, config_substitutions, model); } else return false; diff --git a/src/libslic3r/Format/AMF.hpp b/src/libslic3r/Format/AMF.hpp index b4e2c54ab..a073071fc 100644 --- a/src/libslic3r/Format/AMF.hpp +++ b/src/libslic3r/Format/AMF.hpp @@ -7,7 +7,7 @@ class Model; class DynamicPrintConfig; // Load the content of an amf file into the given model and configuration. -extern bool load_amf(const char* path, DynamicPrintConfig* config, Model* model, bool check_version); +extern bool load_amf(const char* path, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, Model* model, bool check_version); // Save the given model and the config data into an amf file. // The model could be modified during the export process if meshes are not repaired or have no shared vertices diff --git a/src/libslic3r/Format/PRUS.cpp b/src/libslic3r/Format/PRUS.cpp index abf30a53b..586fbafb5 100644 --- a/src/libslic3r/Format/PRUS.cpp +++ b/src/libslic3r/Format/PRUS.cpp @@ -284,11 +284,8 @@ static void extract_model_from_archive( volume->name = name; } // Set the extruder to the volume. - if (extruder_id != (unsigned int)-1) { - char str_extruder[64]; - sprintf(str_extruder, "%ud", extruder_id); - volume->config.set_deserialize("extruder", str_extruder); - } + if (extruder_id != (unsigned int)-1) + volume->config.set("extruder", int(extruder_id)); } // Load a PrusaControl project file into a provided model. diff --git a/src/libslic3r/Format/SL1.cpp b/src/libslic3r/Format/SL1.cpp index f556b0ead..6d779b94e 100644 --- a/src/libslic3r/Format/SL1.cpp +++ b/src/libslic3r/Format/SL1.cpp @@ -287,13 +287,13 @@ std::vector extract_slices_from_sla_archive( } // namespace -void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) +ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out) { ArchiveData arch = extract_sla_archive(zipfname, "png"); - out.load(arch.profile); + return out.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); } -void import_sla_archive( +ConfigSubstitutions import_sla_archive( const std::string & zipfname, Vec2i windowsize, indexed_triangle_set & out, @@ -305,7 +305,7 @@ void import_sla_archive( windowsize.y() = std::max(2, windowsize.y()); ArchiveData arch = extract_sla_archive(zipfname, "thumbnail"); - profile.load(arch.profile); + ConfigSubstitutions config_substitutions = profile.load(arch.profile, ForwardCompatibilitySubstitutionRule::Enable); RasterParams rstp = get_raster_params(profile); rstp.win = {windowsize.y(), windowsize.x()}; @@ -317,6 +317,8 @@ void import_sla_archive( if (!slices.empty()) out = slices_to_mesh(slices, 0, slicp.layerh, slicp.initial_layerh); + + return config_substitutions; } using ConfMap = std::map; diff --git a/src/libslic3r/Format/SL1.hpp b/src/libslic3r/Format/SL1.hpp index a3e6ce26c..2c7e1edc1 100644 --- a/src/libslic3r/Format/SL1.hpp +++ b/src/libslic3r/Format/SL1.hpp @@ -38,23 +38,23 @@ public: } }; -void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); +ConfigSubstitutions import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out); -void import_sla_archive( +ConfigSubstitutions import_sla_archive( const std::string & zipfname, Vec2i windowsize, indexed_triangle_set & out, DynamicPrintConfig & profile, std::function progr = [](int) { return true; }); -inline void import_sla_archive( +inline ConfigSubstitutions import_sla_archive( const std::string & zipfname, Vec2i windowsize, indexed_triangle_set & out, std::function progr = [](int) { return true; }) { DynamicPrintConfig profile; - import_sla_archive(zipfname, windowsize, out, profile, progr); + return import_sla_archive(zipfname, windowsize, out, profile, progr); } } // namespace Slic3r::sla diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index ca76a2320..24bd4939f 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -523,7 +523,8 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Check that there are extrusions on the very first layer. if (layers_to_print.size() == 1u) { if (!has_extrusions) - throw Slic3r::SlicingError(_(L("There is an object with no extrusions on the first layer."))); + throw Slic3r::SlicingError(_(L("There is an object with no extrusions in the first layer.")) + "\n" + + _(L("Object name")) + ": " + object.model_object()->name); } // In case there are extrusions on this layer, check there is a layer to lay it on. @@ -541,7 +542,7 @@ std::vector GCode::collect_layers_to_print(const PrintObjec if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, - _(L("Empty layers detected. Make sure the object is printable.")) + "\n\n" + + _(L("Empty layers detected. Make sure the object is printable.")) + "\n" + _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " "usually caused by negligibly small extrusions or by a faulty model. Try to repair " diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index a3f2f2219..65679f120 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -1285,7 +1285,10 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr if (m_producer == EProducer::PrusaSlicer || m_producer == EProducer::Slic3rPE || m_producer == EProducer::Slic3r) { DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); - config.load_from_gcode_file(filename, false); + // Silently substitute unknown values by new ones for loading configurations from PrusaSlicer's own G-code. + // Showing substitution log or errors may make sense, but we are not really reading many values from the G-code config, + // thus a probability of incorrect substitution is low and the G-code viewer is a consumer-only anyways. + config.load_from_gcode_file(filename, false, ForwardCompatibilitySubstitutionRule::EnableSilent); apply_config(config); } else if (m_producer == EProducer::Simplify3D) diff --git a/src/libslic3r/LocalesUtils.cpp b/src/libslic3r/LocalesUtils.cpp index 8f4d26642..64ab700ed 100644 --- a/src/libslic3r/LocalesUtils.cpp +++ b/src/libslic3r/LocalesUtils.cpp @@ -19,6 +19,7 @@ CNumericLocalesSetter::CNumericLocalesSetter() #else // APPLE m_original_locale = uselocale((locale_t)0); m_new_locale = newlocale(LC_NUMERIC_MASK, "C", m_original_locale); + uselocale(m_new_locale); #endif } diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 84566f4b1..a382a4258 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -96,13 +96,17 @@ void Model::update_links_bottom_up_recursive() } } -Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) +// Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well. +Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) { Model model; DynamicPrintConfig temp_config; + ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent); if (config == nullptr) config = &temp_config; + if (config_substitutions == nullptr) + config_substitutions = &temp_config_substitutions_context; bool result = false; if (boost::algorithm::iends_with(input_file, ".stl")) @@ -110,9 +114,10 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c else if (boost::algorithm::iends_with(input_file, ".obj")) result = load_obj(input_file.c_str(), &model); else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml")) - result = load_amf(input_file.c_str(), config, &model, check_version); + result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model, false); + //FIXME options & LoadAttribute::CheckVersion ? + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false); else if (boost::algorithm::iends_with(input_file, ".prusa")) result = load_prus(input_file.c_str(), &model); else @@ -127,24 +132,29 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c for (ModelObject *o : model.objects) o->input_file = input_file; - if (add_default_instances) + if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z); + sort_remove_duplicates(config_substitutions->substitutions); return model; } -Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances, bool check_version) +// Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ). +Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options) { + assert(config != nullptr); + assert(config_substitutions != nullptr); + Model model; bool result = false; if (boost::algorithm::iends_with(input_file, ".3mf")) - result = load_3mf(input_file.c_str(), config, &model, check_version); + result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion); else if (boost::algorithm::iends_with(input_file, ".zip.amf")) - result = load_amf(input_file.c_str(), config, &model, check_version); + result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion); else throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension."); @@ -165,7 +175,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig o->input_file = input_file; } - if (add_default_instances) + if (options & LoadAttribute::AddDefaultInstances) model.add_default_instances(); CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config); @@ -398,13 +408,12 @@ bool Model::looks_like_multipart_object() const } // Generate next extruder ID string, in the range of (1, max_extruders). -static inline std::string auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) +static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr) { - char str_extruder[64]; - sprintf(str_extruder, "%ud", cntr + 1); - if (++ cntr == max_extruders) + int out = ++ cntr; + if (cntr == max_extruders) cntr = 0; - return str_extruder; + return out; } void Model::convert_multipart_object(unsigned int max_extruders) @@ -431,7 +440,7 @@ void Model::convert_multipart_object(unsigned int max_extruders) auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) { assert(new_v != nullptr); new_v->name = o->name + "_" + std::to_string(counter++); - new_v->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); return new_v; }; if (o->instances.empty()) { @@ -1134,17 +1143,18 @@ bool ModelObject::needed_repair() const return false; } -ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) +ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes) { - if (!keep_upper && !keep_lower) { return {}; } + if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) + return {}; BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start"; // Clone the object to duplicate instances, materials etc. - ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr; - ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr; + ModelObject* upper = attributes.has(ModelObjectCutAttribute::KeepUpper) ? ModelObject::new_clone(*this) : nullptr; + ModelObject* lower = attributes.has(ModelObjectCutAttribute::KeepLower) ? ModelObject::new_clone(*this) : nullptr; - if (keep_upper) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { upper->set_model(nullptr); upper->sla_support_points.clear(); upper->sla_drain_holes.clear(); @@ -1153,7 +1163,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b upper->input_file.clear(); } - if (keep_lower) { + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { lower->set_model(nullptr); lower->sla_support_points.clear(); lower->sla_drain_holes.clear(); @@ -1193,8 +1203,10 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix)); - if (keep_upper) { upper->add_volume(*volume); } - if (keep_lower) { lower->add_volume(*volume); } + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) + upper->add_volume(*volume); + if (attributes.has(ModelObjectCutAttribute::KeepLower)) + lower->add_volume(*volume); } else if (! volume->mesh().empty()) { @@ -1214,19 +1226,19 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b indexed_triangle_set upper_its, lower_its; mesh.require_shared_vertices(); cut_mesh(mesh.its, float(z), &upper_its, &lower_its); - if (keep_upper) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { upper_mesh = TriangleMesh(upper_its); upper_mesh.repair(); upper_mesh.reset_repair_stats(); } - if (keep_lower) { + if (attributes.has(ModelObjectCutAttribute::KeepLower)) { lower_mesh = TriangleMesh(lower_its); lower_mesh.repair(); lower_mesh.reset_repair_stats(); } } - if (keep_upper && upper_mesh.facets_count() > 0) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper_mesh.facets_count() > 0) { ModelVolume* vol = upper->add_volume(upper_mesh); vol->name = volume->name; // Don't copy the config's ID. @@ -1235,7 +1247,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b assert(vol->config.id() != volume->config.id()); vol->set_material(volume->material_id(), *volume->material()); } - if (keep_lower && lower_mesh.facets_count() > 0) { + if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower_mesh.facets_count() > 0) { ModelVolume* vol = lower->add_volume(lower_mesh); vol->name = volume->name; // Don't copy the config's ID. @@ -1246,7 +1258,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b // Compute the lower part instances' bounding boxes to figure out where to place // the upper part - if (keep_upper) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper)) { for (size_t i = 0; i < instances.size(); i++) { lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true)); } @@ -1257,7 +1269,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b ModelObjectPtrs res; - if (keep_upper && upper->volumes.size() > 0) { + if (attributes.has(ModelObjectCutAttribute::KeepUpper) && upper->volumes.size() > 0) { upper->invalidate_bounding_box(); upper->center_around_origin(); @@ -1277,7 +1289,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b res.push_back(upper); } - if (keep_lower && lower->volumes.size() > 0) { + if (attributes.has(ModelObjectCutAttribute::KeepLower) && lower->volumes.size() > 0) { lower->invalidate_bounding_box(); lower->center_around_origin(); @@ -1288,7 +1300,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b instance->set_transformation(Geometry::Transformation()); instance->set_offset(offset); - instance->set_rotation(Vec3d(rotate_lower ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z)); + instance->set_rotation(Vec3d(attributes.has(ModelObjectCutAttribute::FlipLower) ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z)); } res.push_back(lower); @@ -1628,7 +1640,7 @@ bool ModelVolume::is_splittable() const { // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once if (m_is_splittable == -1) - m_is_splittable = (int)this->mesh().is_splittable(); + m_is_splittable = its_is_splittable(this->mesh().its); return m_is_splittable == 1; } @@ -1738,7 +1750,7 @@ size_t ModelVolume::split(unsigned int max_extruders) this->object->volumes[ivolume]->center_geometry_after_creation(); this->object->volumes[ivolume]->translate(offset); this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1); - this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter)); + this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter)); this->object->volumes[ivolume]->m_is_splittable = 0; delete mesh; ++ idx; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index c6a54d5c6..59ab14b4c 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -2,6 +2,7 @@ #define slic3r_Model_hpp_ #include "libslic3r.h" +#include "enum_bitmask.hpp" #include "Geometry.hpp" #include "ObjectID.hpp" #include "Point.hpp" @@ -12,6 +13,7 @@ #include "TriangleMesh.hpp" #include "Arrange.hpp" #include "CustomGCode.hpp" +#include "enum_bitmask.hpp" #include #include @@ -226,6 +228,10 @@ enum class ModelVolumeType : int { SUPPORT_ENFORCER, }; +enum class ModelObjectCutAttribute : int { KeepUpper, KeepLower, FlipLower }; +using ModelObjectCutAttributes = enum_bitmask; +ENABLE_ENUM_BITMASK_OPERATORS(ModelObjectCutAttribute); + // A printable object, possibly having multiple print volumes (each with its own set of parameters and materials), // and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials. // Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed, @@ -344,7 +350,7 @@ public: size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; - ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates + ModelObjectPtrs cut(size_t instance, coordf_t z, ModelObjectCutAttributes attributes); void split(ModelObjectPtrs* new_objects); void merge(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, @@ -1031,8 +1037,20 @@ public: OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model) - static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false); - static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false); + enum class LoadAttribute : int { + AddDefaultInstances, + CheckVersion + }; + using LoadAttributes = enum_bitmask; + + static Model read_from_file( + const std::string& input_file, + DynamicPrintConfig* config = nullptr, ConfigSubstitutionContext* config_substitutions = nullptr, + LoadAttributes options = LoadAttribute::AddDefaultInstances); + static Model read_from_archive( + const std::string& input_file, + DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, + LoadAttributes options = LoadAttribute::AddDefaultInstances); // Add a new ModelObject to this Model, generate a new ID for this ModelObject. ModelObject* add_object(); @@ -1097,6 +1115,8 @@ private: } }; +ENABLE_ENUM_BITMASK_OPERATORS(Model::LoadAttribute) + #undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE #undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 2fbdf85bc..d9c79a44e 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -4,20 +4,15 @@ #include "Layer.hpp" #include "Print.hpp" #include "VoronoiVisualUtils.hpp" +#include "MutablePolygon.hpp" #include #include #include #include - #include -#include -#include -#include -#include - namespace Slic3r { struct ColoredLine { Line line; @@ -89,28 +84,37 @@ struct PaintedLineVisitor bool operator()(coord_t iy, coord_t ix) { // Called with a row and column of the grid cell, which is intersected by a line. - auto cell_data_range = grid.cell_data_range(iy, ix); - const Vec2d v1 = line_to_test.vector().cast(); + auto cell_data_range = grid.cell_data_range(iy, ix); + const Vec2d v1 = line_to_test.vector().cast(); + const double v1_sqr_norm = v1.squaredNorm(); + const double heuristic_thr_part = line_to_test.length() + append_threshold; for (auto it_contour_and_segment = cell_data_range.first; it_contour_and_segment != cell_data_range.second; ++it_contour_and_segment) { - Line grid_line = grid.line(*it_contour_and_segment); - const Vec2d v2 = grid_line.vector().cast(); + Line grid_line = grid.line(*it_contour_and_segment); + const Vec2d v2 = grid_line.vector().cast(); + double heuristic_thr_sqr = Slic3r::sqr(heuristic_thr_part + grid_line.length()); + + // An inexpensive heuristic to test whether line_to_test and grid_line can be somewhere close enough to each other. + // This helps filter out cases when the following expensive calculations are useless. + if ((grid_line.a - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || + (grid_line.b - line_to_test.a).cast().squaredNorm() > heuristic_thr_sqr || + (grid_line.a - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr || + (grid_line.b - line_to_test.b).cast().squaredNorm() > heuristic_thr_sqr) + continue; + // When lines have too different length, it is necessary to normalize them - if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1.squaredNorm() * v2.squaredNorm()) { + if (Slic3r::sqr(v1.dot(v2)) > cos_threshold2 * v1_sqr_norm * v2.squaredNorm()) { // The two vectors are nearly collinear (their mutual angle is lower than 30 degrees) if (painted_lines_set.find(*it_contour_and_segment) == painted_lines_set.end()) { - double dist_1 = grid_line.distance_to(line_to_test.a); - double dist_2 = grid_line.distance_to(line_to_test.b); - double dist_3 = line_to_test.distance_to(grid_line.a); - double dist_4 = line_to_test.distance_to(grid_line.b); - double total_dist = std::min(std::min(dist_1, dist_2), std::min(dist_3, dist_4)); - - if (total_dist < 50 * SCALED_EPSILON) { + if (grid_line.distance_to_squared(line_to_test.a) < append_threshold2 || + grid_line.distance_to_squared(line_to_test.b) < append_threshold2 || + line_to_test.distance_to_squared(grid_line.a) < append_threshold2 || + line_to_test.distance_to_squared(grid_line.b) < append_threshold2) { Line line_to_test_projected; project_line_on_line(grid_line, line_to_test, &line_to_test_projected); - if (Line(grid_line.a, line_to_test_projected.a).length() > Line(grid_line.a, line_to_test_projected.b).length()) { + if ((line_to_test_projected.a - grid_line.a).cast().squaredNorm() > (line_to_test_projected.b - grid_line.a).cast().squaredNorm()) line_to_test_projected.reverse(); - } + painted_lines.push_back({it_contour_and_segment->first, it_contour_and_segment->second, line_to_test_projected, this->color}); painted_lines_set.insert(*it_contour_and_segment); } @@ -125,9 +129,11 @@ struct PaintedLineVisitor std::vector &painted_lines; Line line_to_test; std::unordered_set, boost::hash>> painted_lines_set; - int color = -1; + int color = -1; - static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.)); + static inline const double cos_threshold2 = Slic3r::sqr(cos(M_PI * 30. / 180.)); + static inline const double append_threshold = 50 * SCALED_EPSILON; + static inline const double append_threshold2 = Slic3r::sqr(append_threshold); }; static std::vector to_colored_lines(const Polygon &polygon, int color) @@ -154,6 +160,7 @@ static Polygon colored_points_to_polygon(const std::vector &lines) static Polygons colored_points_to_polygon(const std::vector> &lines) { Polygons out; + out.reserve(lines.size()); for (const std::vector &l : lines) out.emplace_back(colored_points_to_polygon(l)); return out; @@ -484,6 +491,12 @@ static std::vector> colorize_polygons(const Polygons &p using boost::polygon::voronoi_diagram; +static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); } + +static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } + +static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } + struct MMU_Graph { enum class ARC_TYPE { BORDER, NON_BORDER }; @@ -616,24 +629,63 @@ struct MMU_Graph { return this->is_vertex_on_contour(edge_iterator->vertex0()) && this->is_vertex_on_contour(edge_iterator->vertex1()); } + + // All Voronoi vertices are post-processes to merge very close vertices to single. Witch eliminates issues with intersection edges. + // Also, Voronoi vertices outside of the bounding of input polygons are throw away by marking them. + void append_voronoi_vertices(const Geometry::VoronoiDiagram &vd, const Polygons &color_poly_tmp, BoundingBox bbox) { + bbox.offset(SCALED_EPSILON); + + struct CPoint + { + CPoint() = delete; + CPoint(const Point &point, size_t contour_idx, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(contour_idx) {} + CPoint(const Point &point, size_t point_idx) : m_point(point), m_point_idx(point_idx), m_contour_idx(0) {} + const Point m_point; + size_t m_point_idx; + size_t m_contour_idx; + + [[nodiscard]] const Point &point() const { return m_point; } + bool operator==(const CPoint &rhs) const { return this->m_point == rhs.m_point && this->m_contour_idx == rhs.m_contour_idx && this->m_point_idx == rhs.m_point_idx; } + }; + struct CPointAccessor { const Point* operator()(const CPoint &pt) const { return &pt.point(); }}; + typedef ClosestPointInRadiusLookup CPointLookupType; + + CPointLookupType closest_voronoi_point(3 * coord_t(SCALED_EPSILON)); + CPointLookupType closest_contour_point(3 * coord_t(SCALED_EPSILON)); + for (const Polygon &polygon : color_poly_tmp) + for (const Point &pt : polygon.points) + closest_contour_point.insert(CPoint(pt, &polygon - &color_poly_tmp.front(), &pt - &polygon.points.front())); + + for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { + vertex.color(-1); + Point vertex_point = mk_point(vertex); + + const Point &first_point = this->nodes[this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; + const Point &second_point = this->nodes[this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; + + if (vertex_equal_to_point(&vertex, first_point)) { + assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); + assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); + vertex.color(this->get_arc(vertex.incident_edge()->cell()->source_index()).from_idx); + } else if (vertex_equal_to_point(&vertex, second_point)) { + assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); + assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); + vertex.color(this->get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); + } else if (bbox.contains(vertex_point)) { + if (auto [contour_pt, c_dist_sqr] = closest_contour_point.find(vertex_point); contour_pt != nullptr && c_dist_sqr < 3 * SCALED_EPSILON) { + vertex.color(this->get_global_index(contour_pt->m_contour_idx, contour_pt->m_point_idx)); + } else if (auto [voronoi_pt, v_dist_sqr] = closest_voronoi_point.find(vertex_point); voronoi_pt == nullptr || v_dist_sqr >= 3 * SCALED_EPSILON) { + closest_voronoi_point.insert(CPoint(vertex_point, this->nodes_count())); + vertex.color(this->nodes_count()); + this->nodes.push_back({vertex_point}); + } else { + vertex.color(voronoi_pt->m_point_idx); + } + } + } + } }; -namespace bg = boost::geometry; -namespace bgm = boost::geometry::model; -namespace bgi = boost::geometry::index; - -// float is needed because for coord_t bgi::intersects throws "bad numeric conversion: positive overflow" -using rtree_point_t = bgm::point; -using rtree_t = bgi::rtree, bgi::rstar<16, 4>>; - -static inline rtree_point_t mk_rtree_point(const Point &pt) { return rtree_point_t(float(pt.x()), float(pt.y())); } - -static inline Point mk_point(const Voronoi::VD::vertex_type *point) { return Point(coord_t(point->x()), coord_t(point->y())); } - -static inline Point mk_point(const Voronoi::Internal::point_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } - -static inline Point mk_point(const voronoi_diagram::vertex_type &point) { return Point(coord_t(point.x()), coord_t(point.y())); } - static inline void mark_processed(const voronoi_diagram::const_edge_iterator &edge_iterator) { edge_iterator->color(true); @@ -695,7 +747,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector lines_colored = to_lines(color_poly); - Polygons color_poly_tmp = colored_points_to_polygon(color_poly); + const Polygons color_poly_tmp = colored_points_to_polygon(color_poly); const Points points = to_points(color_poly_tmp); const Lines lines = to_lines(color_poly_tmp); @@ -719,6 +771,7 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector void { - auto is_equal_points = [](const Point &p1, const Point &p2) { return p1 == p2 || (p1 - p2).cast().norm() <= 3 * SCALED_EPSILON; }; - - BoundingBox bbox = get_extents(color_poly_tmp); - bbox.offset(SCALED_EPSILON); - // EdgeGrid is used for vertices near to contour and rtree for other vertices - // FIXME Lukas H.: Get rid of EdgeGrid and rtree. Use only one structure for both cases. - EdgeGrid::Grid grid; - grid.set_bbox(bbox); - grid.create(color_poly_tmp, coord_t(scale_(10.))); - rtree_t rtree; - for (const voronoi_diagram::vertex_type &vertex : vd.vertices()) { - vertex.color(-1); - Point vertex_point = mk_point(vertex); - - const Point &first_point = graph.nodes[graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx].point; - const Point &second_point = graph.nodes[graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx].point; - - if (vertex_equal_to_point(&vertex, first_point)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(graph.get_arc(vertex.incident_edge()->cell()->source_index()).from_idx); - } else if (vertex_equal_to_point(&vertex, second_point)) { - assert(vertex.color() != vertex.incident_edge()->cell()->source_index()); - assert(vertex.color() != vertex.incident_edge()->twin()->cell()->source_index()); - vertex.color(graph.get_arc(vertex.incident_edge()->twin()->cell()->source_index()).from_idx); - } else if (bbox.contains(vertex_point)) { - EdgeGrid::Grid::ClosestPointResult cp = grid.closest_point_signed_distance(vertex_point, coord_t(3 * SCALED_EPSILON)); - if (cp.valid()) { - size_t global_idx = graph.get_global_index(cp.contour_idx, cp.start_point_idx); - size_t global_idx_next = graph.get_global_index(cp.contour_idx, (cp.start_point_idx + 1) % color_poly_tmp[cp.contour_idx].points.size()); - vertex.color(is_equal_points(vertex_point, graph.nodes[global_idx].point) ? global_idx : global_idx_next); - } else { - if (rtree.empty()) { - rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count())); - vertex.color(graph.nodes_count()); - graph.nodes.push_back({vertex_point}); - } else { - std::vector> closest; - rtree.query(bgi::nearest(mk_rtree_point(vertex_point), 1), std::back_inserter(closest)); - assert(!closest.empty()); - rtree_point_t r_point = closest.front().first; - Point closest_p(bg::get<0>(r_point), bg::get<1>(r_point)); - if (Line(vertex_point, closest_p).length() > 3 * SCALED_EPSILON) { - rtree.insert(std::make_pair(mk_rtree_point(vertex_point), graph.nodes_count())); - vertex.color(graph.nodes_count()); - graph.nodes.push_back({vertex_point}); - } else { - vertex.color(closest.front().second); - } - } - } - } - } - }; - - append_voronoi_vertices_to_graph(); + BoundingBox bbox = get_extents(color_poly_tmp); + graph.append_voronoi_vertices(vd, color_poly_tmp, bbox); auto get_prev_contour_line = [&lines_colored, &color_poly, &graph](const voronoi_diagram::const_edge_iterator &edge_it) -> ColoredLine { size_t contour_line_local_idx = lines_colored[edge_it->cell()->source_index()].local_line_idx; @@ -803,7 +798,6 @@ static MMU_Graph build_graph(size_t layer_idx, const std::vector>> multi_material_segmentati // All expolygons are expanded by SCALED_EPSILON, merged, and then shrunk again by SCALED_EPSILON // to ensure that very close polygons will be merged. ex_polygons = union_ex(ex_polygons); - // Remove all expolygons and holes with an area less than 0.01mm^2 + // Remove all expolygons and holes with an area less than 0.1mm^2 remove_small_and_small_holes(ex_polygons, Slic3r::sqr(scale_(0.1f))); // Occasionally, some input polygons contained self-intersections that caused problems with Voronoi diagrams // and consequently with the extraction of colored segments by function extract_colored_segments. @@ -1437,19 +1431,19 @@ std::vector>> multi_material_segmentati // Such close points sometimes caused that the Voronoi diagram has self-intersecting edges around these vertices. // This consequently leads to issues with the extraction of colored segments by function extract_colored_segments. // Calling expolygons_simplify fixed these issues. - input_expolygons[layer_idx] = simplify_polygons_ex(to_polygons(expolygons_simplify(offset_ex(ex_polygons, float(-10 * SCALED_EPSILON)), 5 * SCALED_EPSILON))); - input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]); + input_expolygons[layer_idx] = smooth_outward(expolygons_simplify(offset_ex(ex_polygons, -10.f * float(SCALED_EPSILON)), 5 * SCALED_EPSILON), 10 * coord_t(SCALED_EPSILON)); + input_polygons[layer_idx] = to_polygons(input_expolygons[layer_idx]); } }); // end of parallel_for BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - slices preparation in parallel - end"; for (size_t layer_idx = 0; layer_idx < layers.size(); ++layer_idx) { throw_on_cancel_callback(); - BoundingBox bbox(get_extents(input_expolygons[layer_idx])); + BoundingBox bbox(get_extents(input_polygons[layer_idx])); // Projected triangles may slightly exceed the input polygons. bbox.offset(20 * SCALED_EPSILON); edge_grids[layer_idx].set_bbox(bbox); - edge_grids[layer_idx].create(input_expolygons[layer_idx], coord_t(scale_(10.))); + edge_grids[layer_idx].create(input_polygons[layer_idx], coord_t(scale_(10.))); } BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - projection of painted triangles - begin"; @@ -1498,7 +1492,7 @@ std::vector>> multi_material_segmentati // [P0, P2] a [P0, P1] float t1 = (float(layer->slice_z) - facet[0].z()) / (facet[1].z() - facet[0].z()); line_end_f = facet[0] + t1 * (facet[1] - facet[0]); - } else if (facet[1].z() <= layer->slice_z) { + } else { // [P0, P2] a [P1, P2] float t2 = (float(layer->slice_z) - facet[1].z()) / (facet[2].z() - facet[1].z()); line_end_f = facet[1] + t2 * (facet[2] - facet[1]); diff --git a/src/libslic3r/MutablePolygon.cpp b/src/libslic3r/MutablePolygon.cpp index dc1d47731..403d625bf 100644 --- a/src/libslic3r/MutablePolygon.cpp +++ b/src/libslic3r/MutablePolygon.cpp @@ -166,7 +166,7 @@ static bool clip_narrow_corner( assert(orient1 > 0 == blocked); assert(orient2 > 0 == blocked); } -#endif // _NDEBUG +#endif // NDEBUG if (polygon.size() < 3 || (forward == Far && backward == Far)) { polygon.clear(); } else { diff --git a/src/libslic3r/MutablePolygon.hpp b/src/libslic3r/MutablePolygon.hpp index 14d7787cf..1b2b4e445 100644 --- a/src/libslic3r/MutablePolygon.hpp +++ b/src/libslic3r/MutablePolygon.hpp @@ -3,6 +3,7 @@ #include "Point.hpp" #include "Polygon.hpp" +#include "ExPolygon.hpp" namespace Slic3r { @@ -330,6 +331,24 @@ inline Polygons smooth_outward(Polygons polygons, coord_t clip_dist_scaled) return polygons; } +inline ExPolygons smooth_outward(ExPolygons expolygons, coord_t clip_dist_scaled) +{ + MutablePolygon mp; + for (ExPolygon &expolygon : expolygons) { + mp.assign(expolygon.contour, expolygon.contour.size() * 2); + smooth_outward(mp, clip_dist_scaled); + mp.polygon(expolygon.contour); + for (Polygon &hole : expolygon.holes) { + mp.assign(hole, hole.size() * 2); + smooth_outward(mp, clip_dist_scaled); + mp.polygon(hole); + } + expolygon.holes.erase(std::remove_if(expolygon.holes.begin(), expolygon.holes.end(), [](const auto &p) { return p.empty(); }), expolygon.holes.end()); + } + expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), [](const auto &p) { return p.empty(); }), expolygons.end()); + return expolygons; +} + } #endif // slic3r_MutablePolygon_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index df088935f..97457d63b 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -666,7 +666,9 @@ void PresetCollection::add_default_preset(const std::vector &keys, // Load all presets found in dir_path. // Throws an exception on error. -void PresetCollection::load_presets(const std::string &dir_path, const std::string &subdir) +void PresetCollection::load_presets( + const std::string &dir_path, const std::string &subdir, + PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule substitution_rule) { // Don't use boost::filesystem::canonical() on Windows, it is broken in regard to reparse points, // see https://github.com/prusa3d/PrusaSlicer/issues/732 @@ -693,7 +695,9 @@ void PresetCollection::load_presets(const std::string &dir_path, const std::stri // Load the preset file, apply preset values on top of defaults. try { DynamicPrintConfig config; - config.load_from_ini(preset.file); + ConfigSubstitutions config_substitutions = config.load_from_ini(preset.file, substitution_rule); + if (! config_substitutions.empty()) + substitutions.push_back({ preset.name, m_type, PresetConfigSubstitutions::Source::UserFile, preset.file, std::move(config_substitutions) }); // Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field. const Preset &default_preset = this->default_preset_for(config); preset.config = default_preset.config; @@ -1580,7 +1584,9 @@ PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector; + // Collections of presets of the same type (one of the Print, Filament or Printer type). class PresetCollection { @@ -280,7 +304,7 @@ public: void add_default_preset(const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name); // Load ini files of the particular type from the provided directory path. - void load_presets(const std::string &dir_path, const std::string &subdir); + void load_presets(const std::string &dir_path, const std::string &subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule); // Load a preset from an already parsed config file, insert it into the sorted sequence of presets // and select it, losing previous modifications. @@ -692,7 +716,7 @@ public: const std::deque& operator()() const { return m_printers; } // Load ini files of the particular type from the provided directory path. - void load_printers(const std::string& dir_path, const std::string& subdir); + void load_printers(const std::string& dir_path, const std::string& subdir, PresetsConfigSubstitutions& substitutions, ForwardCompatibilitySubstitutionRule rule); void load_printers_from_presets(PrinterPresetCollection &printer_presets); // Load printer from the loaded configuration void load_printer(const std::string& path, const std::string& name, DynamicPrintConfig&& config, bool select, bool save=false); diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index e7f818d08..6f0b2e4d3 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -187,7 +187,7 @@ void PresetBundle::setup_directories() } } -void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_model_id) +PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id) { // First load the vendor specific system presets. std::string errors_cummulative = this->load_system_presets(); @@ -200,33 +200,35 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ // Store the print/filament/printer presets at the same location as the upstream Slic3r. #endif ; + + PresetsConfigSubstitutions substitutions; try { - this->prints.load_presets(dir_user_presets, "print"); + this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->sla_prints.load_presets(dir_user_presets, "sla_print"); + this->sla_prints.load_presets(dir_user_presets, "sla_print", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->filaments.load_presets(dir_user_presets, "filament"); + this->filaments.load_presets(dir_user_presets, "filament", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->sla_materials.load_presets(dir_user_presets, "sla_material"); + this->sla_materials.load_presets(dir_user_presets, "sla_material", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->printers.load_presets(dir_user_presets, "printer"); + this->printers.load_presets(dir_user_presets, "printer", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } try { - this->physical_printers.load_printers(dir_user_presets, "physical_printer"); + this->physical_printers.load_printers(dir_user_presets, "physical_printer", substitutions, substitution_rule); } catch (const std::runtime_error &err) { errors_cummulative += err.what(); } @@ -236,6 +238,8 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_ throw Slic3r::RuntimeError(errors_cummulative); this->load_selections(config, preferred_model_id); + + return substitutions; } // Load system presets into this PresetBundle. @@ -255,13 +259,13 @@ std::string PresetBundle::load_system_presets() // Load the config bundle, flatten it. if (first) { // Reset this PresetBundle and load the first vendor config. - this->load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem); first = false; } else { // Load the other vendor configs, merge them with this PresetBundle. // Report duplicate profiles. PresetBundle other; - other.load_configbundle(dir_entry.path().string(), LOAD_CFGBNDLE_SYSTEM); + other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem); std::vector duplicates = this->merge_presets(std::move(other)); if (! duplicates.empty()) { errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: "; @@ -690,15 +694,15 @@ DynamicPrintConfig PresetBundle::full_sla_config() const // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. -void PresetBundle::load_config_file(const std::string &path) +ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule) { if (is_gcode_file(path)) { DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); - config.load_from_gcode_file(path); + ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule); Preset::normalize(config); load_config_file_config(path, true, std::move(config)); - return; + return config_substitutions; } // 1) Try to load the config file into a boost property tree. @@ -717,6 +721,7 @@ void PresetBundle::load_config_file(const std::string &path) // 2) Continue based on the type of the configuration file. ConfigFileType config_file_type = guess_config_file_type(tree); + ConfigSubstitutions config_substitutions; switch (config_file_type) { case CONFIG_FILE_TYPE_UNKNOWN: throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path); @@ -727,15 +732,18 @@ void PresetBundle::load_config_file(const std::string &path) // Initialize a config from full defaults. DynamicPrintConfig config; config.apply(FullPrintConfig::defaults()); - config.load(tree); + config_substitutions = config.load(tree, compatibility_rule); Preset::normalize(config); load_config_file_config(path, true, std::move(config)); - break; - } - case CONFIG_FILE_TYPE_CONFIG_BUNDLE: - load_config_file_config_bundle(path, tree); - break; + return config_substitutions; } + case CONFIG_FILE_TYPE_CONFIG_BUNDLE: + return load_config_file_config_bundle(path, tree); + } + + // This shall never happen. Suppres compiler warnings. + assert(false); + return ConfigSubstitutions{}; } // Load a config file from a boost property_tree. This is a private method called from load_config_file. @@ -907,16 +915,25 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool } // Load the active configuration of a config bundle from a boost property_tree. This is a private method called from load_config_file. -void PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree) +ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree) { // 1) Load the config bundle into a temp data. PresetBundle tmp_bundle; - // Load the config bundle, don't save the loaded presets to user profile directory. - tmp_bundle.load_configbundle(path, 0); + // Load the config bundle, but don't save the loaded presets to user profile directory, as only the presets marked as active in the loaded preset bundle + // will be loaded into the master PresetBundle and activated. + auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {}); + UNUSED(presets_imported); + std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string(); // 2) Extract active configs from the config bundle, copy them and activate them in this bundle. - auto load_one = [&path, &bundle_name](PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string { + ConfigSubstitutions config_substitutions; + auto load_one = [&path, &bundle_name, &presets_substitutions = presets_substitutions, &config_substitutions]( + PresetCollection &collection_dst, PresetCollection &collection_src, const std::string &preset_name_src, bool activate) -> std::string { + // If there are substitutions reported for this preset, move them to config_substitutions. + if (auto it = std::find_if(presets_substitutions.begin(), presets_substitutions.end(), [&preset_name_src](const PresetConfigSubstitutions& subs){ return subs.preset_name == preset_name_src; }); + it != presets_substitutions.end() && ! it->substitutions.empty()) + append(config_substitutions, std::move(it->substitutions)); Preset *preset_src = collection_src.find_preset(preset_name_src, false); Preset *preset_dst = collection_dst.find_preset(preset_name_src, false); assert(preset_src != nullptr); @@ -970,6 +987,9 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const this->filament_presets[i] = load_one(this->filaments, tmp_bundle.filaments, tmp_bundle.filament_presets[i], false); this->update_compatible(PresetSelectCompatibleType::Never); + + sort_remove_duplicates(config_substitutions); + return config_substitutions; } // Process the Config Bundle loaded as a Boost property tree. @@ -1114,11 +1134,20 @@ static void flatten_configbundle_hierarchy(boost::property_tree::ptree &tree, co // 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) +std::pair PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags) { - 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); + // Enable substitutions for user config bundle, throw an exception when loading a system profile. + ConfigSubstitutionContext substitution_context { + flags.has(LoadConfigBundleAttribute::LoadSystem) ? + ForwardCompatibilitySubstitutionRule::Disable : + ForwardCompatibilitySubstitutionRule::Enable + }; + + PresetsConfigSubstitutions substitutions; + + if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem)) + // Reset this bundle, delete user profile files if SaveImported. + this->reset(flags.has(LoadConfigBundleAttribute::SaveImported)); // 1) Read the complete config file into a boost::property_tree. namespace pt = boost::property_tree; @@ -1131,25 +1160,24 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla } const VendorProfile *vendor_profile = nullptr; - if (flags & (LOAD_CFGBNDLE_SYSTEM | LOAD_CFGBUNDLE_VENDOR_ONLY)) { + if (flags.has(LoadConfigBundleAttribute::LoadSystem) || flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) { auto vp = VendorProfile::from_ini(tree, path); if (vp.models.size() == 0) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer model defined.") % path; - return 0; + return std::make_pair(PresetsConfigSubstitutions{}, 0); } else if (vp.num_variants() == 0) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No printer variant defined") % path; - return 0; + return std::make_pair(PresetsConfigSubstitutions{}, 0); } vendor_profile = &this->vendors.insert({vp.id, vp}).first->second; } - if (flags & LOAD_CFGBUNDLE_VENDOR_ONLY) { - return 0; - } + if (flags.has(LoadConfigBundleAttribute::LoadVendorOnly)) + return std::make_pair(PresetsConfigSubstitutions{}, 0); // 1.5) Flatten the config bundle by applying the inheritance rules. Internal profiles (with names starting with '*') are removed. // If loading a user config bundle, do not flatten with the system profiles, but keep the "inherits" flag intact. - flatten_configbundle_hierarchy(tree, ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) ? this : nullptr); + flatten_configbundle_hierarchy(tree, flags.has(LoadConfigBundleAttribute::LoadSystem) ? nullptr : this); // 2) Parse the property_tree, extract the active preset names and the profiles, save them into local config files. // Parse the obsolete preset names, to be deleted when upgrading from the old configuration structure. @@ -1246,7 +1274,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla DynamicPrintConfig config; std::string alias_name; std::vector renamed_from; - auto parse_config_section = [§ion, &alias_name, &renamed_from, &path](DynamicPrintConfig &config) { + auto parse_config_section = [§ion, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) { + substitution_context.substitutions.clear(); for (auto &kvp : section.second) { if (kvp.first == "alias") alias_name = kvp.second.data(); @@ -1256,7 +1285,8 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla section.first << "\" contains invalid \"renamed_from\" key, which is being ignored."; } } - config.set_deserialize(kvp.first, kvp.second.data()); + // Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown. + config.set_deserialize(kvp.first, kvp.second.data(), substitution_context); } }; if (presets == &this->printers) { @@ -1277,7 +1307,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla if (! incorrect_keys.empty()) BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The printer preset \"" << section.first << "\" contains the following incorrect keys: " << incorrect_keys << ", which were removed"; - if ((flags & LOAD_CFGBNDLE_SYSTEM) && presets == &printers) { + if (flags.has(LoadConfigBundleAttribute::LoadSystem) && 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"); @@ -1312,7 +1342,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla section.first << "\" has already been loaded from another Confing Bundle."; continue; } - } else if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { + } else if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) { // This is a user config bundle. const Preset *existing = presets->find_preset(preset_name, false); if (existing != nullptr) { @@ -1341,9 +1371,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla / presets->section_name() / file_name).make_preferred(); // Load the preset into the list of presets, save it to disk. Preset &loaded = presets->load_preset(file_path.string(), preset_name, std::move(config), false); - if (flags & LOAD_CFGBNDLE_SAVE) + if (flags.has(LoadConfigBundleAttribute::SaveImported)) loaded.save(); - if (flags & LOAD_CFGBNDLE_SYSTEM) { + if (flags.has(LoadConfigBundleAttribute::LoadSystem)) { loaded.is_system = true; loaded.vendor = vendor_profile; } @@ -1364,7 +1394,10 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla else loaded.alias = std::move(alias_name); loaded.renamed_from = std::move(renamed_from); - + if (! substitution_context.empty()) + substitutions.push_back({ + preset_name, presets->type(), PresetConfigSubstitutions::Source::ConfigBundle, + std::string(), std::move(substitution_context.substitutions) }); ++ presets_loaded; } @@ -1373,8 +1406,9 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla const DynamicPrintConfig& default_config = ph_printers->default_config(); DynamicPrintConfig config = default_config; + substitution_context.substitutions.clear(); for (auto& kvp : section.second) - config.set_deserialize(kvp.first, kvp.second.data()); + config.set_deserialize(kvp.first, kvp.second.data(), substitution_context); // Report configuration fields, which are misplaced into a wrong group. std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config); @@ -1400,14 +1434,17 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla #endif / "physical_printer" / file_name).make_preferred(); // Load the preset into the list of presets, save it to disk. - ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags & LOAD_CFGBNDLE_SAVE); - - ++ph_printers_loaded; + ph_printers->load_printer(file_path.string(), ph_printer_name, std::move(config), false, flags.has(LoadConfigBundleAttribute::SaveImported)); + if (! substitution_context.empty()) + substitutions.push_back({ + ph_printer_name, Preset::TYPE_PHYSICAL_PRINTER, PresetConfigSubstitutions::Source::ConfigBundle, + std::string(), std::move(substitution_context.substitutions) }); + ++ ph_printers_loaded; } } // 3) Activate the presets and physical printer if any exists. - if ((flags & LOAD_CFGBNDLE_SYSTEM) == 0) { + if (! flags.has(LoadConfigBundleAttribute::LoadSystem)) { if (! active_print.empty()) prints.select_preset_by_name(active_print, true); if (! active_sla_print.empty()) @@ -1427,7 +1464,7 @@ size_t PresetBundle::load_configbundle(const std::string &path, unsigned int fla this->update_compatible(PresetSelectCompatibleType::Never); } - return presets_loaded + ph_printers_loaded; + return std::make_pair(std::move(substitutions), presets_loaded + ph_printers_loaded); } void PresetBundle::update_multi_material_filament_presets() diff --git a/src/libslic3r/PresetBundle.hpp b/src/libslic3r/PresetBundle.hpp index 5902d4208..9f75ba6c2 100644 --- a/src/libslic3r/PresetBundle.hpp +++ b/src/libslic3r/PresetBundle.hpp @@ -3,6 +3,7 @@ #include "Preset.hpp" #include "AppConfig.hpp" +#include "enum_bitmask.hpp" #include #include @@ -26,7 +27,7 @@ public: // Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets. // Load selections (current print, current filaments, current printer) from config.ini - void load_presets(AppConfig &config, const std::string &preferred_model_id = std::string()); + PresetsConfigSubstitutions load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule rule, const std::string &preferred_model_id = std::string()); // Export selections (current print, current filaments, current printer) into config.ini void export_selections(AppConfig &config); @@ -82,24 +83,26 @@ public: // Instead of a config file, a G-code may be loaded containing the full set of parameters. // In the future the configuration will likely be read from an AMF file as well. // If the file is loaded successfully, its print / filament / printer profiles will be activated. - void load_config_file(const std::string &path); + ConfigSubstitutions load_config_file(const std::string &path, ForwardCompatibilitySubstitutionRule compatibility_rule); // Load a config bundle file, into presets and store the loaded presets into separate files // of the local configuration directory. // Load settings into the provided settings instance. // Activate the presets stored in the config bundle. // Returns the number of presets loaded successfully. - enum { + enum LoadConfigBundleAttribute { // Save the profiles, which have been loaded. - LOAD_CFGBNDLE_SAVE = 1, + SaveImported, // Delete all old config profiles before loading. - LOAD_CFGBNDLE_RESET_USER_PROFILE = 2, + ResetUserProfile, // Load a system config bundle. - LOAD_CFGBNDLE_SYSTEM = 4, - LOAD_CFGBUNDLE_VENDOR_ONLY = 8, + LoadSystem, + LoadVendorOnly, }; - // 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); + using LoadConfigBundleAttributes = enum_bitmask; + // Load the config bundle based on the flags. + // Don't do any config substitutions when loading a system profile, perform and report substitutions otherwise. + std::pair load_configbundle(const std::string &path, LoadConfigBundleAttributes flags); // Export a config bundle file containing all the presets and the names of the active presets. void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false); @@ -155,12 +158,14 @@ private: // 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. void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config); - void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); + ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree); DynamicPrintConfig full_fff_config() const; DynamicPrintConfig full_sla_config() const; }; +ENABLE_ENUM_BITMASK_OPERATORS(PresetBundle::LoadConfigBundleAttribute) + } // namespace Slic3r #endif /* slic3r_PresetBundle_hpp_ */ diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 8cb997c77..08711a7b2 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -448,9 +448,9 @@ public: // Canceled internally from Print::apply() through the Print/PrintObject::invalidate_step() or ::invalidate_all_steps(). CANCELED_INTERNAL = 2 }; - CancelStatus cancel_status() const { return m_cancel_status; } + CancelStatus cancel_status() const { return m_cancel_status.load(std::memory_order_acquire); } // Has the calculation been canceled? - bool canceled() const { return m_cancel_status != NOT_CANCELED; } + bool canceled() const { return m_cancel_status.load(std::memory_order_acquire) != NOT_CANCELED; } // Cancel the running computation. Stop execution of all the background threads. void cancel() { m_cancel_status = CANCELED_BY_USER; } void cancel_internal() { m_cancel_status = CANCELED_INTERNAL; } @@ -481,7 +481,7 @@ protected: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. - void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); } + void throw_if_canceled() const { if (m_cancel_status.load(std::memory_order_acquire)) throw CanceledException(); } // Wrapper around this->throw_if_canceled(), so that throw_if_canceled() may be passed to a function without making throw_if_canceled() public. PrintTryCancel make_try_cancel() const { return PrintTryCancel(this); } diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 5cd33c1cb..76dbac263 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -50,7 +50,7 @@ static t_config_enum_values s_keys_map_GCodeFlavor { { "teacup", gcfTeacup }, { "makerware", gcfMakerWare }, { "marlin", gcfMarlinLegacy }, - { "marlinfirmware", gcfMarlinFirmware }, + { "marlin2", gcfMarlinFirmware }, { "sailfish", gcfSailfish }, { "smoothie", gcfSmoothie }, { "mach3", gcfMach3 }, @@ -67,6 +67,7 @@ static t_config_enum_values s_keys_map_MachineLimitsUsage { CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(MachineLimitsUsage) static t_config_enum_values s_keys_map_PrintHostType { + { "prusalink", htPrusaLink }, { "octoprint", htOctoPrint }, { "duet", htDuet }, { "flashair", htFlashAir }, @@ -172,6 +173,13 @@ static const t_config_enum_values s_keys_map_BrimType = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType) +static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = { + { "disable", ForwardCompatibilitySubstitutionRule::Disable }, + { "enable", ForwardCompatibilitySubstitutionRule::Enable }, + { "enable_silent", ForwardCompatibilitySubstitutionRule::EnableSilent } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) + static void assign_printer_technology_to_unknown(t_optiondef_map &options, PrinterTechnology printer_technology) { for (std::pair &kvp : options) @@ -1245,7 +1253,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("teacup"); def->enum_values.push_back("makerware"); def->enum_values.push_back("marlin"); - def->enum_values.push_back("marlinfirmware"); + def->enum_values.push_back("marlin2"); def->enum_values.push_back("sailfish"); def->enum_values.push_back("mach3"); def->enum_values.push_back("machinekit"); @@ -1772,11 +1780,13 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Slic3r can upload G-code files to a printer host. This field must contain " "the kind of the host."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("prusalink"); def->enum_values.push_back("octoprint"); def->enum_values.push_back("duet"); def->enum_values.push_back("flashair"); def->enum_values.push_back("astrobox"); def->enum_values.push_back("repetier"); + def->enum_labels.push_back("PrusaLink"); def->enum_labels.push_back("OctoPrint"); def->enum_labels.push_back("Duet"); def->enum_labels.push_back("FlashAir"); @@ -3608,8 +3618,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } catch (boost::bad_lexical_cast &) { value = "0"; } - } else if (opt_key == "gcode_flavor" && value == "makerbot") { - value = "makerware"; + } else if (opt_key == "gcode_flavor") { + if (value == "makerbot") + value = "makerware"; + else if (value == "marlinfirmware") + // the "new" marlin firmware flavor used to be called "marlinfirmware" for some time during PrusaSlicer 2.4.0-alpha development. + value = "marlin2"; } else if (opt_key == "fill_density" && value.find("%") == std::string::npos) { try { // fill_density was turned into a percent value @@ -3622,7 +3636,7 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va } else if (opt_key == "bed_size" && !value.empty()) { opt_key = "bed_shape"; ConfigOptionPoint p; - p.deserialize(value); + p.deserialize(value, ForwardCompatibilitySubstitutionRule::Disable); std::ostringstream oss; oss << "0x0," << p.value(0) << "x0," << p.value(0) << "x" << p.value(1) << ",0x" << p.value(1); value = oss.str(); @@ -4171,6 +4185,20 @@ CLIMiscConfigDef::CLIMiscConfigDef() def->label = L("Ignore non-existent config files"); def->tooltip = L("Do not fail if a file supplied to --load does not exist."); + def = this->add("config_compatibility", coEnum); + def->label = L("Forward-compatibility rule when loading configurations from config files and project files (3MF, AMF)."); + def->tooltip = L("This version of PrusaSlicer may not understand configurations produced by newest PrusaSlicer versions. " + "For example, newer PrusaSlicer may extend the list of supported firmware flavors. One may decide to " + "bail out or to substitute an unknown value with a default silently or verbosely."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("disable"); + def->enum_values.push_back("enable"); + def->enum_values.push_back("enable_silent"); + def->enum_labels.push_back(L("Bail out on unknown configuration values")); + def->enum_labels.push_back(L("Enable reading unknown configuration values by verbosely substituting them with defaults.")); + def->enum_labels.push_back(L("Enable reading unknown configuration values by silently substituting them with defaults.")); + def->set_default_value(new ConfigOptionEnum(ForwardCompatibilitySubstitutionRule::Enable)); + def = this->add("load", coStrings); def->label = L("Load config file"); def->tooltip = L("Load configuration from the specified file. It can be used more than once to load options from multiple files."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f2b9901d6..efe71822b 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -44,7 +44,7 @@ enum class MachineLimitsUsage { }; enum PrintHostType { - htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier + htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier }; enum AuthorizationType { @@ -141,6 +141,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule) #undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS @@ -1086,8 +1087,8 @@ public: bool set_key_value(const std::string &opt_key, ConfigOption *opt) { bool out = m_data.set_key_value(opt_key, opt); this->touch(); return out; } template void set(const std::string &opt_key, T value) { m_data.set(opt_key, value, true); this->touch(); } - void set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false) - { m_data.set_deserialize(opt_key, str, append); this->touch(); } + void set_deserialize(const t_config_option_key &opt_key, const std::string &str, ConfigSubstitutionContext &substitution_context, bool append = false) + { m_data.set_deserialize(opt_key, str, substitution_context, append); this->touch(); } bool erase(const t_config_option_key &opt_key) { bool out = m_data.erase(opt_key); if (out) this->touch(); return out; } // Getters are thread safe. diff --git a/src/libslic3r/Thread.cpp b/src/libslic3r/Thread.cpp index 51dec618e..25c29d273 100644 --- a/src/libslic3r/Thread.cpp +++ b/src/libslic3r/Thread.cpp @@ -10,11 +10,11 @@ #include #include #include -#include #include #include #include "Thread.hpp" +#include "Utils.hpp" namespace Slic3r { @@ -199,16 +199,14 @@ void name_tbb_thread_pool_threads() // TBB will respect the task affinity mask on Linux and spawn less threads than std::thread::hardware_concurrency(). // const size_t nthreads_hw = std::thread::hardware_concurrency(); const size_t nthreads_hw = tbb::this_task_arena::max_concurrency(); - size_t nthreads = nthreads_hw; + size_t nthreads = nthreads_hw; #ifdef SLIC3R_PROFILE // Shiny profiler is not thread safe, thus disable parallelization. + disable_multi_threading(); nthreads = 1; #endif - if (nthreads != nthreads_hw) - tbb::global_control(tbb::global_control::max_allowed_parallelism, nthreads); - std::atomic nthreads_running(0); std::condition_variable cv; std::mutex cv_m; diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp index d7efd09b1..a3f342319 100644 --- a/src/libslic3r/TriangleSelector.cpp +++ b/src/libslic3r/TriangleSelector.cpp @@ -3,9 +3,9 @@ #include -#ifndef _NDEBUG +#ifndef NDEBUG #define EXPENSIVE_DEBUG_CHECKS -#endif // _NDEBUG +#endif // NDEBUG namespace Slic3r { @@ -19,7 +19,7 @@ static inline Vec3i root_neighbors(const TriangleMesh &mesh, int triangle_id) return neighbors; } -#ifndef _NDEBUG +#ifndef NDEBUG bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const { for (int i = 0; i < 3; ++ i) { @@ -57,7 +57,7 @@ bool TriangleSelector::verify_triangle_neighbors(const Triangle &tr, const Vec3i } return true; } -#endif // _NDEBUG +#endif // NDEBUG // sides_to_split==-1 : just restore previous split void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) @@ -308,12 +308,12 @@ int TriangleSelector::triangle_midpoint_or_allocate(int itriangle, int vertexi, } assert(m_vertices[midpoint].ref_cnt == 0); } else { -#ifndef _NDEBUG +#ifndef NDEBUG Vec3f c1 = 0.5f * (m_vertices[vertexi].v + m_vertices[vertexj].v); Vec3f c2 = m_vertices[midpoint].v; float d = (c2 - c1).norm(); assert(std::abs(d) < EPSILON); -#endif // _NDEBUG +#endif // NDEBUG assert(m_vertices[midpoint].ref_cnt > 0); } return midpoint; @@ -816,13 +816,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo assert(tr.is_split()); // indices of triangle vertices -#ifdef _NDEBUG +#ifdef NDEBUG boost::container::small_vector verts_idxs; -#else // _NDEBUG +#else // NDEBUG // For easier debugging. std::vector verts_idxs; verts_idxs.reserve(6); -#endif // _NDEBUG +#endif // NDEBUG for (int j=0, idx = tr.special_side(); j<3; ++j, idx = next_idx_modulo(idx, 3)) verts_idxs.push_back(tr.verts_idxs[idx]); @@ -861,13 +861,13 @@ void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, Enfo break; } -#ifndef _NDEBUG +#ifndef NDEBUG assert(this->verify_triangle_neighbors(tr, neighbors)); for (int i = 0; i <= tr.number_of_split_sides(); ++i) { Vec3i n = this->child_neighbors(tr, neighbors, i); assert(this->verify_triangle_neighbors(m_triangles[tr.children[i]], n)); } -#endif // _NDEBUG +#endif // NDEBUG } bool TriangleSelector::has_facets(EnforcerBlockerType state) const diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp index 5115bb02a..6efae2150 100644 --- a/src/libslic3r/TriangleSelector.hpp +++ b/src/libslic3r/TriangleSelector.hpp @@ -204,10 +204,10 @@ private: int triangle_midpoint(int itriangle, int vertexi, int vertexj) const; int triangle_midpoint_or_allocate(int itriangle, int vertexi, int vertexj); -#ifndef _NDEBUG +#ifndef NDEBUG bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const; bool verify_triangle_midpoints(const Triangle& tr) const; -#endif // _NDEBUG +#endif // NDEBUG void get_facets_strict_recursive( const Triangle &tr, diff --git a/src/libslic3r/enum_bitmask.hpp b/src/libslic3r/enum_bitmask.hpp new file mode 100644 index 000000000..4c2076313 --- /dev/null +++ b/src/libslic3r/enum_bitmask.hpp @@ -0,0 +1,80 @@ +#ifndef slic3r_enum_bitmask_hpp_ +#define slic3r_enum_bitmask_hpp_ + +// enum_bitmask for passing a set of attributes to a function in a type safe way. +// Adapted from https://gpfault.net/posts/typesafe-bitmasks.txt.html +// with hints from https://www.strikerx3.dev/cpp/2019/02/27/typesafe-enum-class-bitmasks-in-cpp.html + +#include + +namespace Slic3r { + +// enum_bitmasks can only be used with enums. +template::value>::type> +class enum_bitmask { + // The type we'll use for storing the value of our bitmask should be the same as the enum's underlying type. + using underlying_type = typename std::underlying_type::type; + + // This method helps us avoid having to explicitly set enum values to powers of two. + static constexpr underlying_type mask_value(option_type o) { return 1 << static_cast(o); } + + // Private ctor to be used internally. + explicit constexpr enum_bitmask(underlying_type o) : m_bits(o) {} + +public: + // Default ctor creates a bitmask with no options selected. + constexpr enum_bitmask() : m_bits(0) {} + + // Creates a enum_bitmask with just one bit set. + // This ctor is intentionally non-explicit, to allow passing an options to a function: + // FunctionExpectingBitmask(Options::Opt1) + constexpr enum_bitmask(option_type o) : m_bits(mask_value(o)) {} + + // Set the bit corresponding to the given option. + constexpr enum_bitmask operator|(option_type t) { return enum_bitmask(m_bits | mask_value(t)); } + + // Combine with another enum_bitmask of the same type. + constexpr enum_bitmask operator|(enum_bitmask t) { return enum_bitmask(m_bits | t.m_bits); } + + // Get the value of the bit corresponding to the given option. + constexpr bool operator&(option_type t) { return m_bits & mask_value(t); } + constexpr bool has(option_type t) { return m_bits & mask_value(t); } + +private: + underlying_type m_bits = 0; +}; + +// For enabling free functions producing enum_bitmask<> type from bit operations on enums. +template struct is_enum_bitmask_type { static const bool enable = false; }; +#define ENABLE_ENUM_BITMASK_OPERATORS(x) template<> struct is_enum_bitmask_type { static const bool enable = true; }; +template inline constexpr bool is_enum_bitmask_type_v = is_enum_bitmask_type::enable; + +// Creates an enum_bitmask from two options, convenient for passing of options to a function: +// FunctionExpectingBitmask(Options::Opt1 | Options::Opt2 | Options::Opt3) +template +constexpr std::enable_if_t, enum_bitmask> operator|(option_type lhs, option_type rhs) { + static_assert(std::is_enum_v); + return enum_bitmask{lhs} | rhs; +} + +template +constexpr std::enable_if_t, enum_bitmask> operator|(option_type lhs, enum_bitmask rhs) { + static_assert(std::is_enum_v); + return enum_bitmask{lhs} | rhs; +} + +template +constexpr std::enable_if_t, enum_bitmask> only_if(bool condition, option_type opt) { + static_assert(std::is_enum_v); + return condition ? enum_bitmask{opt} : enum_bitmask{}; +} + +template +constexpr std::enable_if_t, enum_bitmask> only_if(bool condition, enum_bitmask opt) { + static_assert(std::is_enum_v); + return condition ? opt : enum_bitmask{}; +} + +} // namespace Slic3r + +#endif // slic3r_enum_bitmask_hpp_ diff --git a/src/libslic3r/pchheader.hpp b/src/libslic3r/pchheader.hpp index ad22b855a..e6591f574 100644 --- a/src/libslic3r/pchheader.hpp +++ b/src/libslic3r/pchheader.hpp @@ -115,6 +115,7 @@ #include "BoundingBox.hpp" #include "ClipperUtils.hpp" #include "Config.hpp" +#include "enum_bitmask.hpp" #include "format.hpp" #include "I18N.hpp" #include "MultiPoint.hpp" diff --git a/src/libslic3r/utils.cpp b/src/libslic3r/utils.cpp index 763be8af7..dfc4f5a3e 100644 --- a/src/libslic3r/utils.cpp +++ b/src/libslic3r/utils.cpp @@ -8,6 +8,7 @@ #include "Platform.hpp" #include "Time.hpp" +#include "libslic3r.h" #ifdef WIN32 #include @@ -43,7 +44,13 @@ #include #include -#include +// We are using quite an old TBB 2017 U7, which does not support global control API officially. +// Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. +#ifdef TBB_HAS_GLOBAL_CONTROL + #include +#else + #include +#endif #if defined(__linux__) || defined(__GNUC__ ) #include @@ -118,7 +125,12 @@ void trace(unsigned int level, const char *message) void disable_multi_threading() { // Disable parallelization so the Shiny profiler works +#ifdef TBB_HAS_GLOBAL_CONTROL tbb::global_control(tbb::global_control::max_allowed_parallelism, 1); +#else // TBB_HAS_GLOBAL_CONTROL + static tbb::task_scheduler_init *tbb_init = new tbb::task_scheduler_init(1); + UNUSED(tbb_init); +#endif // TBB_HAS_GLOBAL_CONTROL } static std::string g_var_dir; diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index f6739b0d8..76fd8d989 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -181,6 +181,8 @@ set(SLIC3R_GUI_SOURCES GUI/Mouse3DController.hpp GUI/DoubleSlider.cpp GUI/DoubleSlider.hpp + GUI/Notebook.cpp + GUI/Notebook.hpp GUI/ObjectDataViewModel.cpp GUI/ObjectDataViewModel.hpp GUI/InstanceCheck.cpp diff --git a/src/slic3r/Config/Snapshot.cpp b/src/slic3r/Config/Snapshot.cpp index ecfc32b58..56722173b 100644 --- a/src/slic3r/Config/Snapshot.cpp +++ b/src/slic3r/Config/Snapshot.cpp @@ -413,7 +413,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot: ++ it; // Read the active config bundle, parse the config version. PresetBundle bundle; - bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LOAD_CFGBUNDLE_VENDOR_ONLY); + bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly); for (const auto &vp : bundle.vendors) if (vp.second.id == cfg.name) cfg.version.config_version = vp.second.config_version; diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index 6f5cd8852..cf9b07249 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -255,7 +255,6 @@ private: std::shared_ptr m_ui_task; PrintState m_step_state; - mutable tbb::mutex m_step_state_mutex; bool set_step_started(BackgroundSlicingProcessStep step); void set_step_done(BackgroundSlicingProcessStep step); bool is_step_done(BackgroundSlicingProcessStep step) const; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index bc66532a0..084af8b14 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -64,7 +64,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu this->is_prusa_bundle = ais_prusa_bundle; std::string path_string = source_path.string(); - size_t presets_loaded = preset_bundle->load_configbundle(path_string, PresetBundle::LOAD_CFGBNDLE_SYSTEM); + auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem); + // No substitutions shall be reported when loading a system config bundle, no substitutions are allowed. + assert(config_substitutions.empty()); auto first_vendor = preset_bundle->vendors.begin(); if (first_vendor == preset_bundle->vendors.end()) { BOOST_LOG_TRIVIAL(error) << boost::format("Vendor bundle: `%1%`: No vendor information defined, cannot install.") % path_string; @@ -2592,7 +2594,10 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese } } - preset_bundle->load_presets(*app_config, preferred_model); + // Reloading the configs after some modifications were done to PrusaSlicer.ini. + // Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up + // and the Wizard shall not create any new values that would require substitution. + PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model); if (page_custom->custom_wanted()) { page_firmware->apply_custom_config(*custom_config); diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 75a0617c6..e39b40ce4 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2010,11 +2010,11 @@ void Control::show_cog_icon_context_menu() []() { return true; }, [this]() { return m_extra_style == 0; }, GUI::wxGetApp().plater()); append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show object height"), _L("Show object height on the ruler"), - [this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style &= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu, + [this](wxCommandEvent&) { m_extra_style & wxSL_AUTOTICKS ? m_extra_style ^= wxSL_AUTOTICKS : m_extra_style |= wxSL_AUTOTICKS; }, ruler_mode_menu, []() { return true; }, [this]() { return m_extra_style & wxSL_AUTOTICKS; }, GUI::wxGetApp().plater()); append_menu_check_item(ruler_mode_menu, wxID_ANY, _L("Show estimated print time"), _L("Show estimated print time on the ruler"), - [this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style &= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu, + [this](wxCommandEvent&) { m_extra_style & wxSL_VALUE_LABEL ? m_extra_style ^= wxSL_VALUE_LABEL : m_extra_style |= wxSL_VALUE_LABEL; }, ruler_mode_menu, []() { return true; }, [this]() { return m_extra_style & wxSL_VALUE_LABEL; }, GUI::wxGetApp().plater()); append_submenu(&menu, ruler_mode_menu, wxID_ANY, _L("Ruler mode"), _L("Set ruler mode"), "", diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 532e44e70..e194898ee 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -1141,6 +1141,10 @@ void Choice::set_value(const boost::any& value, bool change_event) } case coEnum: { int val = boost::any_cast(value); + if (m_opt_id.compare("host_type") == 0 && val != 0 && + m_opt.enum_values.size() > field->GetCount()) // for case, when PrusaLink isn't used as a HostType + val--; + if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { std::string key; @@ -1197,7 +1201,7 @@ void Choice::set_values(const wxArrayString &values) auto ww = dynamic_cast(window); auto value = ww->GetValue(); ww->Clear(); - ww->Append(""); +// ww->Append(""); for (const auto &el : values) ww->Append(el); ww->SetValue(value); @@ -1219,7 +1223,10 @@ boost::any& Choice::get_value() if (m_opt.type == coEnum) { - if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { + if (m_opt_id.compare("host_type") == 0 && m_opt.enum_values.size() > field->GetCount()) { + // for case, when PrusaLink isn't used as a HostType + m_value = field->GetSelection()+1; + } else if (m_opt_id == "top_fill_pattern" || m_opt_id == "bottom_fill_pattern" || m_opt_id == "fill_pattern") { const std::string& key = m_opt.enum_values[field->GetSelection()]; m_value = int(ConfigOptionEnum::get_enum_values().at(key)); } diff --git a/src/slic3r/GUI/GUI.hpp b/src/slic3r/GUI/GUI.hpp index a90115933..c70dffcc3 100644 --- a/src/slic3r/GUI/GUI.hpp +++ b/src/slic3r/GUI/GUI.hpp @@ -10,7 +10,6 @@ namespace boost::filesystem { class path; } class wxWindow; class wxMenuBar; -class wxNotebook; class wxComboCtrl; class wxFileDialog; class wxTopLevelWindow; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9cf71e07d..73f41e449 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -72,6 +72,7 @@ #include "DesktopIntegrationDialog.hpp" #include "BitmapCache.hpp" +#include "Notebook.hpp" #ifdef __WXMSW__ #include @@ -624,6 +625,13 @@ void GUI_App::post_init() this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str())); } else { + if (! this->init_params->preset_substitutions.empty()) { + // TODO: Add list of changes from all_substitutions + show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities." + " To recover these files, incompatible values were changed to default values." + " But data in files won't be changed until you save them in PrusaSlicer."))); + } + #if 0 // Load the cummulative config over the currently active profiles. //FIXME if multiple configs are loaded, only the last one will have an effect. @@ -652,6 +660,24 @@ void GUI_App::post_init() if (! this->init_params->extra_config.empty()) this->mainframe->load_config(this->init_params->extra_config); } + + // The extra CallAfter() is needed because of Mac, where this is the only way + // to popup a modal dialog on start without screwing combo boxes. + // This is ugly but I honestly found no better way to do it. + // Neither wxShowEvent nor wxWindowCreateEvent work reliably. + if (this->preset_updater) { + this->check_updates(false); + CallAfter([this] { + this->config_wizard_startup(); + this->preset_updater->slic3r_update_notify(); + this->preset_updater->sync(preset_bundle); + }); + } + +#ifdef _WIN32 + // Sets window property to mainframe so other instances can indentify it. + OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); +#endif //WIN32 } IMPLEMENT_APP(GUI_App) @@ -885,7 +911,7 @@ bool GUI_App::on_init_inner() // Suppress the '- default -' presets. preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1"); try { - preset_bundle->load_presets(*app_config); + init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); } catch (const std::exception &ex) { show_error(nullptr, ex.what()); } @@ -904,8 +930,6 @@ bool GUI_App::on_init_inner() if (scrn && is_editor()) scrn->SetText(_L("Preparing settings tabs") + dots); - m_tabs_as_menu = dark_mode() || app_config->get("tabs_as_menu") == "1"; - mainframe = new MainFrame(); // hide settings tabs after first Layout if (is_editor()) @@ -948,7 +972,6 @@ bool GUI_App::on_init_inner() if (! plater_) return; - if (app_config->dirty() && app_config->get("autosave") == "1") app_config->save(); @@ -969,33 +992,6 @@ bool GUI_App::on_init_inner() #endif this->post_init(); } - - // Preset updating & Configwizard are done after the above initializations, - // and after MainFrame is created & shown. - // The extra CallAfter() is needed because of Mac, where this is the only way - // to popup a modal dialog on start without screwing combo boxes. - // This is ugly but I honestly found no better way to do it. - // Neither wxShowEvent nor wxWindowCreateEvent work reliably. - - static bool once = true; - if (once) { - once = false; - - if (preset_updater != nullptr) { - check_updates(false); - - CallAfter([this] { - config_wizard_startup(); - preset_updater->slic3r_update_notify(); - preset_updater->sync(preset_bundle); - }); - } - -#ifdef _WIN32 - //sets window property to mainframe so other instances can indentify it - OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); -#endif //WIN32 - } }); m_initialized = true; @@ -1046,6 +1042,7 @@ void GUI_App::init_label_colours() m_color_highlight_label_default = is_dark_mode ? wxColour(230, 230, 230): wxSystemSettings::GetColour(/*wxSYS_COLOUR_HIGHLIGHTTEXT*/wxSYS_COLOUR_WINDOWTEXT); m_color_highlight_default = is_dark_mode ? wxColour(78, 78, 78) : wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); m_color_hovered_btn_label = is_dark_mode ? wxColour(253, 111, 40) : wxColour(252, 77, 1); + m_color_selected_btn_bg = is_dark_mode ? wxColour(95, 73, 62) : wxColour(228, 220, 216); #else m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); #endif @@ -1093,24 +1090,22 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju btn->Bind(wxEVT_LEAVE_WINDOW, [focus_button](wxMouseEvent& event) { focus_button(false); event.Skip(); }); } } - else if (dark_mode()) { - if (wxTextCtrl* text = dynamic_cast(window)) { - if (text->GetBorder() != wxBORDER_SIMPLE) - text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); - } - else if (wxCheckListBox* list = dynamic_cast(window)) { - list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); - list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - for (size_t i = 0; i < list->GetCount(); i++) - if (wxOwnerDrawn* item = list->GetItem(i)) { - item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); - item->SetTextColour(m_color_label_default); - } - return; - } - else if (dynamic_cast(window)) - window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); + else if (wxTextCtrl* text = dynamic_cast(window)) { + if (text->GetBorder() != wxBORDER_SIMPLE) + text->SetWindowStyle(text->GetWindowStyle() | wxBORDER_SIMPLE); } + else if (wxCheckListBox* list = dynamic_cast(window)) { + list->SetWindowStyle(list->GetWindowStyle() | wxBORDER_SIMPLE); + list->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + for (size_t i = 0; i < list->GetCount(); i++) + if (wxOwnerDrawn* item = list->GetItem(i)) { + item->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); + item->SetTextColour(m_color_label_default); + } + return; + } + else if (dynamic_cast(window)) + window->SetWindowStyle(window->GetWindowStyle() | wxBORDER_SIMPLE); if (!just_font) window->SetBackgroundColour(highlited ? m_color_highlight_default : m_color_window_default); @@ -1141,8 +1136,6 @@ void GUI_App::UpdateDlgDarkUI(wxDialog* dlg) void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) { #ifdef _WIN32 - if (!dark_mode()) - return; UpdateDarkUI(dvc, highlited); wxItemAttr attr(dark_mode() ? m_color_highlight_default : m_color_label_default, m_color_window_default, @@ -1158,8 +1151,6 @@ void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/) void GUI_App::UpdateAllStaticTextDarkUI(wxWindow* parent) { #ifdef _WIN32 - if (!dark_mode()) - return; wxGetApp().UpdateDarkUI(parent); auto children = parent->GetChildren(); @@ -1225,6 +1216,11 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) app_config->save(); } +bool GUI_App::tabs_as_menu() const +{ + return app_config->get("tabs_as_menu") == "1"; // || dark_mode(); +} + wxSize GUI_App::get_min_size() const { return wxSize(76*m_em_unit, 49 * m_em_unit); @@ -1369,6 +1365,14 @@ void fatal_error(wxWindow* parent) // exit 1; // #ys_FIXME } +#ifdef _WIN32 +void GUI_App::force_colors_update() +{ + NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); + m_force_colors_update = true; +} +#endif + // Called after the Preferences dialog is closed and the program settings are saved. // Update the UI based on the current preferences. void GUI_App::update_ui_from_settings() @@ -1376,13 +1380,13 @@ void GUI_App::update_ui_from_settings() update_label_colours(); mainframe->update_ui_from_settings(); -#if 0 //#ifdef _WIN32 // #ysDarkMSW - Use to force dark colors for SystemLightMode - if (m_force_sys_colors_update) { - m_force_sys_colors_update = false; - mainframe->force_sys_color_changed(); - mainframe->diff_dialog.force_sys_color_changed(); +#ifdef _WIN32 + if (m_force_colors_update) { + m_force_colors_update = false; + mainframe->force_color_changed(); + mainframe->diff_dialog.force_color_changed(); if (m_wizard) - m_wizard->force_sys_color_changed(); + m_wizard->force_color_changed(); } #endif } @@ -1766,6 +1770,11 @@ void GUI_App::update_mode() { sidebar().update_mode(); +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(mainframe->m_tabpanel)->UpdateMode(); +#endif + for (auto tab : tabs_list) tab->update_mode(); @@ -1872,7 +1881,13 @@ void GUI_App::add_config_menu(wxMenuBar *menu) Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK); try { app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id); - preset_bundle->load_presets(*app_config); + if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable); + ! all_substitutions.empty()) { + // TODO: + show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities." + " To recover these files, incompatible values were changed to default values." + " But data in files won't be changed until you save them in PrusaSlicer."))); + } // Load the currently selected preset into the GUI, update the preset selection box. load_current_presets(); } catch (std::exception &ex) { @@ -1899,13 +1914,6 @@ void GUI_App::add_config_menu(wxMenuBar *menu) this->plater_->refresh_print(); if (dlg.recreate_GUI()) { -#ifdef _MSW_DARK_MODE - if (dlg.color_mode_changed()) { - NppDarkMode::SetDarkMode(app_config->get("dark_color_mode") == "1"); - init_label_colours(); - } -#endif - m_tabs_as_menu = dark_mode() || app_config->get("tabs_as_menu") == "1"; recreate_GUI(_L("Restart application") + dots); return; } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 9579a030d..d88085ce7 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -124,11 +124,10 @@ private: wxColour m_color_highlight_label_default; wxColour m_color_hovered_btn_label; wxColour m_color_highlight_default; - //bool m_force_sys_colors_update { false }; // #ysDarkMSW - Use to force dark colors for SystemLightMode + wxColour m_color_selected_btn_bg; + bool m_force_colors_update { false }; #endif - bool m_tabs_as_menu{ false }; - wxFont m_small_font; wxFont m_bold_font; wxFont m_normal_font; @@ -202,7 +201,9 @@ public: #ifdef _WIN32 const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; } const wxColour& get_highlight_default_clr() { return m_color_highlight_default; } -// void force_sys_colors_update() { m_force_sys_colors_update = true; } // #ysDarkMSW - Use to force dark colors for SystemLightMode + const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; } + const wxColour& get_color_selected_btn_bg() { return m_color_selected_btn_bg; } + void force_colors_update(); #endif const wxFont& small_font() { return m_small_font; } @@ -210,7 +211,7 @@ public: const wxFont& normal_font() { return m_normal_font; } const wxFont& code_font() { return m_code_font; } int em_unit() const { return m_em_unit; } - bool tabs_as_menu() const { return m_tabs_as_menu;} + bool tabs_as_menu() const; wxSize get_min_size() const; float toolbar_icon_scale(const bool is_limited = false) const; void set_auto_toolbar_icon_scale(float scale) const; @@ -273,7 +274,7 @@ public: NotificationManager* notification_manager(); // Parameters extracted from the command line to be passed to GUI after initialization. - const GUI_InitParams* init_params { nullptr }; + GUI_InitParams* init_params { nullptr }; AppConfig* app_config{ nullptr }; PresetBundle* preset_bundle{ nullptr }; @@ -281,7 +282,7 @@ public: MainFrame* mainframe{ nullptr }; Plater* plater_{ nullptr }; - PresetUpdater* get_preset_updater() { return preset_updater; } + PresetUpdater* get_preset_updater() { return preset_updater; } wxBookCtrlBase* tab_panel() const ; int extruders_cnt() const; diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index aeb2ab7e0..c4782615c 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -1115,5 +1115,20 @@ void MenuFactory::sys_color_changed() } } +void MenuFactory::sys_color_changed(wxMenuBar* menubar) +{ + for (size_t id = 0; id < menubar->GetMenuCount(); id++) { + wxMenu* menu = menubar->GetMenu(id); + msw_rescale_menu(menu); +#ifdef _WIN32 + // but under MSW we have to update item's bachground color + for (wxMenuItem* item : menu->GetMenuItems()) + update_menu_item_def_colors(item); +#endif + } + menubar->Refresh(); +} + + } //namespace GUI } //namespace Slic3r diff --git a/src/slic3r/GUI/GUI_Factories.hpp b/src/slic3r/GUI/GUI_Factories.hpp index 2c3e03521..e8928d3ff 100644 --- a/src/slic3r/GUI/GUI_Factories.hpp +++ b/src/slic3r/GUI/GUI_Factories.hpp @@ -44,6 +44,8 @@ public: void msw_rescale(); void sys_color_changed(); + static void sys_color_changed(wxMenuBar* menu_bar); + wxMenu* default_menu(); wxMenu* object_menu(); wxMenu* sla_object_menu(); diff --git a/src/slic3r/GUI/GUI_Init.cpp b/src/slic3r/GUI/GUI_Init.cpp index 839782741..92223a767 100644 --- a/src/slic3r/GUI/GUI_Init.cpp +++ b/src/slic3r/GUI/GUI_Init.cpp @@ -50,39 +50,8 @@ int GUI_Run(GUI_InitParams ¶ms) // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); gui->init_params = ¶ms; -/* - gui->CallAfter([gui, this, &load_configs, params.start_as_gcodeviewer] { - if (!gui->initialized()) { - return; - } - if (params.start_as_gcodeviewer) { - if (!m_input_files.empty()) - gui->plater()->load_gcode(wxString::FromUTF8(m_input_files[0].c_str())); - } else { -#if 0 - // Load the cummulative config over the currently active profiles. - //FIXME if multiple configs are loaded, only the last one will have an effect. - // We need to decide what to do about loading of separate presets (just print preset, just filament preset etc). - // As of now only the full configs are supported here. - if (!m_print_config.empty()) - gui->mainframe->load_config(m_print_config); -#endif - if (!load_configs.empty()) - // Load the last config to give it a name at the UI. The name of the preset may be later - // changed by loading an AMF or 3MF. - //FIXME this is not strictly correct, as one may pass a print/filament/printer profile here instead of a full config. - gui->mainframe->load_config_file(load_configs.back()); - // If loading a 3MF file, the config is loaded from the last one. - if (!m_input_files.empty()) - gui->plater()->load_files(m_input_files, true, true); - if (!m_extra_config.empty()) - gui->mainframe->load_config(m_extra_config); - } - }); -*/ - int result = wxEntry(params.argc, params.argv); - return result; + return wxEntry(params.argc, params.argv); } catch (const Slic3r::Exception &ex) { boost::nowide::cerr << ex.what() << std::endl; wxMessageBox(boost::nowide::widen(ex.what()), _L("PrusaSlicer GUI initialization failed"), wxICON_STOP); diff --git a/src/slic3r/GUI/GUI_Init.hpp b/src/slic3r/GUI/GUI_Init.hpp index c420c9554..2adf618a4 100644 --- a/src/slic3r/GUI/GUI_Init.hpp +++ b/src/slic3r/GUI/GUI_Init.hpp @@ -1,6 +1,7 @@ #ifndef slic3r_GUI_Init_hpp_ #define slic3r_GUI_Init_hpp_ +#include #include namespace Slic3r { @@ -12,6 +13,9 @@ struct GUI_InitParams int argc; char **argv; + // Substitutions of unknown configuration values done during loading of user presets. + PresetsConfigSubstitutions preset_substitutions; + std::vector load_configs; DynamicPrintConfig extra_config; std::vector input_files; diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 7b1fcad19..2165b11c0 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -9,7 +9,6 @@ #include #include "libslic3r/GCode/GCodeProcessor.hpp" -class wxNotebook; class wxGLCanvas; class wxBoxSizer; class wxStaticText; diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 78548e111..73bfb3bec 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -1,15 +1,15 @@ #include "GUI_Utils.hpp" +#include "GUI_App.hpp" #include #include #include #ifdef _WIN32 -#include -#include "GUI_App.hpp" -#include "libslic3r/AppConfig.hpp" -#include -#endif + #include + #include "libslic3r/AppConfig.hpp" + #include +#endif // _WIN32 #include #include @@ -174,7 +174,7 @@ bool check_dark_mode() { #ifdef _WIN32 void update_dark_ui(wxWindow* window) { - bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1" ? true : check_dark_mode(); + bool is_dark = wxGetApp().app_config->get("dark_color_mode") == "1";// ? true : check_dark_mode();// #ysDarkMSW - Allow it when we deside to support the sustem colors for application window->SetBackgroundColour(is_dark ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); window->SetForegroundColour(is_dark ? wxColour(250, 250, 250) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); } diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 66e0ae2aa..a5ba218b5 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -175,8 +175,8 @@ public: const wxFont& normal_font() const { return m_normal_font; } void enable_force_rescale() { m_force_rescale = true; } -#if 0 //#ifdef _WIN32 // #ysDarkMSW - Use to force dark colors for SystemLightMode - void force_sys_color_changed() +#ifdef _WIN32 + void force_color_changed() { update_dark_ui(this); on_sys_color_changed(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index f0813732f..9499bd6de 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -15,6 +15,7 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "libslic3r/AppConfig.hpp" +#include "libslic3r/Model.hpp" namespace Slic3r { @@ -231,7 +232,10 @@ void GLGizmoCut::perform_cut(const Selection& selection) coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); if (object_cut_z > 0.) - wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); + wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, + only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) | + only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) | + only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower)); else { // the object is SLA-elevated and the plane is under it. } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index 3274f000c..5fa4ab51d 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -21,6 +21,7 @@ void GLGizmoFdmSupports::on_shutdown() { m_angle_threshold_deg = 0.f; m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp index c6dced670..2abc35344 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/BitmapCache.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp" +#include "slic3r/GUI/NotificationManager.hpp" #include "libslic3r/PresetBundle.hpp" #include "libslic3r/Model.hpp" @@ -16,9 +17,26 @@ namespace Slic3r::GUI { +static inline void show_notification_extruders_limit_exceeded() +{ + wxGetApp() + .plater() + ->get_notification_manager() + ->push_notification(NotificationType::MmSegmentationExceededExtrudersLimit, NotificationManager::NotificationLevel::RegularNotification, + GUI::format(_L("Your printer has more extruders than the multi-material painting gizmo supports. For this reason, only the " + "first %1% extruders will be able to be used for painting."), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)); +} + +void GLGizmoMmuSegmentation::on_opening() +{ + if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)) + show_notification_extruders_limit_exceeded(); +} + void GLGizmoMmuSegmentation::on_shutdown() { m_parent.use_slope(false); + m_parent.toggle_model_objects_visibility(true); } std::string GLGizmoMmuSegmentation::on_get_name() const @@ -131,6 +149,9 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection) ModelObject *model_object = m_c->selection_info()->model_object(); int prev_extruders_count = int(m_original_extruders_colors.size()); if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) { + if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT)) + show_notification_extruders_limit_exceeded(); + this->init_extruders_data(); // Reinitialize triangle selectors because of change of extruder count need also change the size of GLIndexedVertexArray if (prev_extruders_count != wxGetApp().extruders_edited_cnt()) @@ -157,7 +178,7 @@ static void render_extruders_combo(const std::string &labe ImGui::BeginGroup(); ImVec2 combo_pos = ImGui::GetCursorScreenPos(); if (ImGui::BeginCombo(label.c_str(), "")) { - for (size_t extruder_idx = 0; extruder_idx < extruders.size(); ++extruder_idx) { + for (size_t extruder_idx = 0; extruder_idx < std::min(extruders.size(), GLGizmoMmuSegmentation::EXTRUDERS_LIMIT); ++extruder_idx) { ImGui::PushID(int(extruder_idx)); ImVec2 start_position = ImGui::GetCursorScreenPos(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp index f5c97801b..5ece0ec2a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoMmuSegmentation.hpp @@ -36,6 +36,12 @@ public: void set_painter_gizmo_data(const Selection& selection) override; + // TriangleSelector::serialization/deserialization has a limit to store 19 different states. + // EXTRUDER_LIMIT + 1 states are used to storing the painting because also uncolored triangles are stored. + // When increasing EXTRUDER_LIMIT, it needs to ensure that TriangleSelector::serialization/deserialization + // will be also extended to support additional states, requiring at least one state to remain free out of 19 states. + static const constexpr size_t EXTRUDERS_LIMIT = 16; + protected: std::array get_cursor_sphere_left_button_color() const override; std::array get_cursor_sphere_right_button_color() const override; @@ -63,7 +69,7 @@ private: void update_model_object() const override; void update_from_model_object() override; - void on_opening() override {} + void on_opening() override; void on_shutdown() override; PainterGizmoType get_painter_type() const override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp index d3c0c7d04..6b28e8ca7 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.cpp @@ -16,6 +16,13 @@ namespace Slic3r::GUI { +void GLGizmoSeam::on_shutdown() +{ + m_parent.toggle_model_objects_visibility(true); +} + + + bool GLGizmoSeam::on_init() { m_shortcut_key = WXK_CONTROL_P; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp index d97bad10f..196fe5023 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSeam.hpp @@ -27,7 +27,7 @@ private: void update_from_model_object() override; void on_opening() override {} - void on_shutdown() override {} + void on_shutdown() override; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index 9998f42c7..f6e3976ea 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -140,22 +140,27 @@ void SLAImportJob::process() if (p->path.empty()) return; std::string path = p->path.ToUTF8().data(); + ConfigSubstitutions config_substitutions; try { switch (p->sel) { case Sel::modelAndProfile: - import_sla_archive(path, p->win, p->mesh, p->profile, progr); + config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr); break; case Sel::modelOnly: - import_sla_archive(path, p->win, p->mesh, progr); + config_substitutions = import_sla_archive(path, p->win, p->mesh, progr); break; case Sel::profileOnly: - import_sla_archive(path, p->profile); + config_substitutions = import_sla_archive(path, p->profile); break; } } catch (std::exception &ex) { p->err = ex.what(); } + + if (! config_substitutions.empty()) { + //FIXME Add reporting here "Loading profiles found following incompatibilities." + } update_status(100, was_canceled() ? _(L("Importing canceled.")) : _(L("Importing done."))); diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 2c7822a9e..23df99bd9 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -3,13 +3,13 @@ #include "I18N.hpp" #include "libslic3r/Utils.hpp" #include "GUI.hpp" +#include "Notebook.hpp" #include #include #include "GUI_App.hpp" #include "wxExtensions.hpp" #include "MainFrame.hpp" #include -#include namespace Slic3r { namespace GUI { @@ -18,8 +18,6 @@ KBShortcutsDialog::KBShortcutsDialog() : DPIDialog(static_cast(wxGetApp().mainframe), wxID_ANY, wxString(wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME) + " - " + _L("Keyboard Shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) { -// SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - // fonts const wxFont& font = wxGetApp().normal_font(); const wxFont& bold_font = wxGetApp().bold_font(); @@ -31,13 +29,10 @@ KBShortcutsDialog::KBShortcutsDialog() #ifdef _MSW_DARK_MODE wxBookCtrlBase* book; - if (wxGetApp().dark_mode()) { - book = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP); - wxGetApp().UpdateDarkUI(book); - wxGetApp().UpdateDarkUI(dynamic_cast(book)->GetListView()); - } - else - book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP); +// if (wxGetApp().dark_mode()) + book = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP); +/* else + book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP);*/ #else wxNotebook* book = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP); #endif diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 9c7533acd..561a03f44 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -41,6 +41,8 @@ #include "GUI_App.hpp" #include "UnsavedChangesDialog.hpp" #include "MsgDialog.hpp" +#include "Notebook.hpp" +#include "GUI_Factories.hpp" #ifdef _WIN32 #include @@ -427,8 +429,13 @@ void MainFrame::update_layout() case ESettingsLayout::Old: { m_plater->Reparent(m_tabpanel); +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->InsertPage(0, m_plater, _L("Plater"), std::string("plater")); + else +#endif m_tabpanel->InsertPage(0, m_plater, _L("Plater")); - m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 1); m_plater->Show(); m_tabpanel->Show(); // update Tabs @@ -447,6 +454,11 @@ void MainFrame::update_layout() m_tabpanel->Hide(); m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); m_plater_page = new wxPanel(m_tabpanel); +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->InsertPage(0, m_plater_page, _L("Plater"), std::string("plater")); + else +#endif m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ m_plater->Show(); break; @@ -455,7 +467,7 @@ void MainFrame::update_layout() { m_main_sizer->Add(m_plater, 1, wxEXPAND); m_tabpanel->Reparent(&m_settings_dialog); - m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 2); m_tabpanel->Show(); m_plater->Show(); @@ -476,6 +488,11 @@ void MainFrame::update_layout() } } +#ifdef _MSW_DARK_MODE + // Sizer with buttons for mode changing + m_plater->sidebar().show_mode_sizer(wxGetApp().tabs_as_menu() || m_layout != ESettingsLayout::Old); +#endif + #ifdef __WXMSW__ if (update_scaling_state != State::noUpdate) { @@ -640,7 +657,7 @@ void MainFrame::init_tabpanel() wxGetApp().UpdateDarkUI(m_tabpanel); } else - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); + m_tabpanel = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME, true); #else m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); #endif @@ -652,7 +669,7 @@ void MainFrame::init_tabpanel() m_settings_dialog.set_tabpanel(m_tabpanel); #ifdef __WXMSW__ - m_tabpanel->Bind(/*wxEVT_LISTBOOK_PAGE_CHANGED*/wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { + m_tabpanel->Bind(wxEVT_BOOKCTRL_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { #else m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { #endif @@ -763,20 +780,25 @@ void MainFrame::register_win32_callbacks() void MainFrame::create_preset_tabs() { wxGetApp().update_label_colours_from_appconfig(); - add_created_tab(new TabPrint(m_tabpanel)); - add_created_tab(new TabFilament(m_tabpanel)); - add_created_tab(new TabSLAPrint(m_tabpanel)); - add_created_tab(new TabSLAMaterial(m_tabpanel)); - add_created_tab(new TabPrinter(m_tabpanel)); + add_created_tab(new TabPrint(m_tabpanel), "cog"); + add_created_tab(new TabFilament(m_tabpanel), "spool"); + add_created_tab(new TabSLAPrint(m_tabpanel), "cog"); + add_created_tab(new TabSLAMaterial(m_tabpanel), "resin"); + add_created_tab(new TabPrinter(m_tabpanel), wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF ? "printer" : "sla_printer"); } -void MainFrame::add_created_tab(Tab* panel) +void MainFrame::add_created_tab(Tab* panel, const std::string& bmp_name /*= ""*/) { panel->create_preset_tab(); const auto printer_tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology(); if (panel->supports_printer_technology(printer_tech)) +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->AddPage(panel, panel->title(), bmp_name); + else +#endif m_tabpanel->AddPage(panel, panel->title()); } @@ -960,6 +982,12 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) wxGetApp().update_fonts(this); this->SetFont(this->normal_font()); +#ifdef _MSW_DARK_MODE + // update common mode sizer + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->Rescale(); +#endif + // update Plater wxGetApp().plater()->msw_rescale(); @@ -968,9 +996,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect) for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); - wxMenuBar* menu_bar = this->GetMenuBar(); - for (size_t id = 0; id < menu_bar->GetMenuCount(); id++) - msw_rescale_menu(menu_bar->GetMenu(id)); + for (size_t id = 0; id < m_menubar->GetMenuCount(); id++) + msw_rescale_menu(m_menubar->GetMenu(id)); // Workarounds for correct Window rendering after rescale @@ -1003,6 +1030,11 @@ void MainFrame::on_sys_color_changed() #ifdef __WXMSW__ wxGetApp().UpdateDarkUI(m_tabpanel); m_statusbar->update_dark_ui(); +#ifdef _MSW_DARK_MODE + // update common mode sizer + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->Rescale(); +#endif #endif // update Plater @@ -1012,10 +1044,7 @@ void MainFrame::on_sys_color_changed() for (auto tab : wxGetApp().tabs_list) tab->sys_color_changed(); - // msw_rescale_menu updates just icons, so use it - wxMenuBar* menu_bar = this->GetMenuBar(); - for (size_t id = 0; id < menu_bar->GetMenuCount(); id++) - msw_rescale_menu(menu_bar->GetMenu(id)); + MenuFactory::sys_color_changed(m_menubar); this->Refresh(); } @@ -1518,6 +1547,7 @@ void MainFrame::update_menubar() m_changeable_menu_items[miPrinterTab] ->SetBitmap(create_menu_bitmap(is_fff ? "printer" : "sla_printer")); } +#if 0 // To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG". void MainFrame::quick_slice(const int qs) { @@ -1643,6 +1673,7 @@ void MainFrame::quick_slice(const int qs) // }; // Slic3r::GUI::catch_error(this, []() { if (m_progress_dialog) m_progress_dialog->Destroy(); }); } +#endif void MainFrame::reslice_now() { @@ -1729,7 +1760,13 @@ void MainFrame::load_config_file() bool MainFrame::load_config_file(const std::string &path) { try { - wxGetApp().preset_bundle->load_config_file(path); + ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable); + if (! config_substitutions.empty()) { + // TODO: Add list of changes from all_substitutions + show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities." + " To recover these files, incompatible values were changed to default values." + " But data in files won't be changed until you save them in PrusaSlicer."))); + } } catch (const std::exception &ex) { show_error(this, ex.what()); return false; @@ -1793,14 +1830,22 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re wxGetApp().app_config->update_config_dir(get_dir_name(file)); - auto presets_imported = 0; + size_t presets_imported = 0; + PresetsConfigSubstitutions config_substitutions; try { - presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data()); + std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported); } catch (const std::exception &ex) { show_error(this, ex.what()); return; } + if (! config_substitutions.empty()) { + // TODO: Add list of changes from all_substitutions + show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities." + " To recover these files, incompatible values were changed to default values." + " But data in files won't be changed until you save them in PrusaSlicer."))); + } + // Load the currently selected preset into the GUI, update the preset selection box. wxGetApp().load_current_presets(); @@ -2087,8 +2132,8 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); #else this->SetFont(wxGetApp().normal_font()); -#endif // __WXMSW__ this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // __WXMSW__ // Load the icon either from the exe, or from the ico file. #if _WIN32 @@ -2169,6 +2214,12 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) const int& em = em_unit(); const wxSize& size = wxSize(85 * em, 50 * em); +#ifdef _MSW_DARK_MODE + // update common mode sizer + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(m_tabpanel)->Rescale(); +#endif + // update Tabs for (auto tab : wxGetApp().tabs_list) tab->msw_rescale(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 40fff23da..a5c6b57ca 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -18,7 +18,6 @@ #include "Event.hpp" #include "UnsavedChangesDialog.hpp" -class wxNotebook; class wxBookCtrlBase; class wxProgressDialog; @@ -154,7 +153,7 @@ public: void init_tabpanel(); void create_preset_tabs(); - void add_created_tab(Tab* panel); + void add_created_tab(Tab* panel, const std::string& bmp_name = ""); bool is_active_and_shown_tab(Tab* tab); // Register Win32 RawInput callbacks (3DConnexion) and removable media insert / remove callbacks. // Called from wxEVT_ACTIVATE, as wxEVT_CREATE was not reliable (bug in wxWidgets?). @@ -170,7 +169,7 @@ public: bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); } bool is_dlg_layout() const { return m_layout == ESettingsLayout::Dlg; } - void quick_slice(const int qs = qsUndef); +// void quick_slice(const int qs = qsUndef); void reslice_now(); void repair_stl(); void export_config(); diff --git a/src/slic3r/GUI/MsgDialog.cpp b/src/slic3r/GUI/MsgDialog.cpp index 0c52d8b58..9af642a25 100644 --- a/src/slic3r/GUI/MsgDialog.cpp +++ b/src/slic3r/GUI/MsgDialog.cpp @@ -17,6 +17,7 @@ #include "I18N.hpp" #include "ConfigWizard.hpp" #include "wxExtensions.hpp" +#include "slic3r/GUI/MainFrame.hpp" #include "GUI_App.hpp" namespace Slic3r { @@ -24,7 +25,7 @@ namespace GUI { MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id, wxBitmap bitmap) - : wxDialog(parent, wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + : wxDialog(parent ? parent : dynamic_cast(wxGetApp().mainframe), wxID_ANY, title, wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) , boldfont(wxGetApp().normal_font()/*wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)*/) , content_sizer(new wxBoxSizer(wxVERTICAL)) , btn_sizer(new wxBoxSizer(wxHORIZONTAL)) @@ -32,6 +33,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he boldfont.SetWeight(wxFONTWEIGHT_BOLD); this->SetFont(wxGetApp().normal_font()); + this->CenterOnParent(); auto *topsizer = new wxBoxSizer(wxHORIZONTAL); auto *rightsizer = new wxBoxSizer(wxVERTICAL); @@ -135,6 +137,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_ SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT*wxGetApp().em_unit())); Fit(); + this->CenterOnParent(); } // WarningDialog @@ -156,9 +159,10 @@ WarningDialog::WarningDialog(wxWindow *parent, wxGetApp().UpdateDlgDarkUI(this); Fit(); + this->CenterOnParent(); } - +#ifdef _WIN32 // MessageDialog MessageDialog::MessageDialog(wxWindow* parent, @@ -180,7 +184,9 @@ MessageDialog::MessageDialog(wxWindow* parent, wxGetApp().UpdateDlgDarkUI(this); Fit(); + this->CenterOnParent(); } +#endif } } diff --git a/src/slic3r/GUI/MsgDialog.hpp b/src/slic3r/GUI/MsgDialog.hpp index 8c49107d9..ac4fa44e6 100644 --- a/src/slic3r/GUI/MsgDialog.hpp +++ b/src/slic3r/GUI/MsgDialog.hpp @@ -7,6 +7,7 @@ #include #include #include +#include class wxBoxSizer; class wxCheckBox; @@ -83,7 +84,7 @@ public: virtual ~WarningDialog() = default; }; - +#ifdef _WIN32 // Generic message dialog, used intead of wxMessageDialog class MessageDialog : public MsgDialog { @@ -98,6 +99,19 @@ public: MessageDialog &operator=(const MessageDialog&) = delete; virtual ~MessageDialog() = default; }; +#else +// just a wrapper to wxMessageBox to use the same code on all platforms +class MessageDialog : public wxMessageDialog +{ +public: + MessageDialog(wxWindow* parent, + const wxString& message, + const wxString& caption = wxEmptyString, + long style = wxOK) + : wxMessageDialog(parent, message, caption, style) {} + ~MessageDialog() {} +}; +#endif } diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp new file mode 100644 index 000000000..25a0b565f --- /dev/null +++ b/src/slic3r/GUI/Notebook.cpp @@ -0,0 +1,134 @@ +#include "Notebook.hpp" + +#ifdef _WIN32 + +#include "GUI_App.hpp" +#include "wxExtensions.hpp" + +#include +#include + +wxDEFINE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent); + +ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = false*/) : + wxControl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE | wxTAB_TRAVERSAL) +{ +#ifdef __WINDOWS__ + SetDoubleBuffered(true); +#endif //__WINDOWS__ + + m_sizer = new wxBoxSizer(wxHORIZONTAL); + this->SetSizer(m_sizer); + + if (add_mode_buttons) { + m_mode_sizer = new ModeSizer(this, int(0.5 * em_unit(this))); + m_sizer->AddStretchSpacer(20); + m_sizer->Add(m_mode_sizer, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); + } + + this->Bind(wxEVT_PAINT, &ButtonsListCtrl::OnPaint, this); +} + +void ButtonsListCtrl::OnPaint(wxPaintEvent&) +{ + Slic3r::GUI::wxGetApp().UpdateDarkUI(this); + const wxSize sz = GetSize(); + wxPaintDC dc(this); + + if (m_selection < 0 || m_selection >= (int)m_pageButtons.size()) + return; + + // highlight selected button + + const wxColour& selected_btn_bg = Slic3r::GUI::wxGetApp().get_color_selected_btn_bg(); + const wxColour& default_btn_bg = Slic3r::GUI::wxGetApp().get_highlight_default_clr(); + const wxColour& btn_marker_color = Slic3r::GUI::wxGetApp().get_color_hovered_btn_label(); + for (int idx = 0; idx < m_pageButtons.size(); idx++) { + wxButton* btn = m_pageButtons[idx]; + + btn->SetBackgroundColour(idx == m_selection ? selected_btn_bg : default_btn_bg); + + wxPoint pos = btn->GetPosition(); + wxSize size = btn->GetSize(); + const wxColour& clr = idx == m_selection ? btn_marker_color : default_btn_bg; + dc.SetPen(clr); + dc.SetBrush(clr); + dc.DrawRectangle(pos.x, sz.y - 3, size.x, 3); + } + + dc.SetPen(btn_marker_color); + dc.SetBrush(btn_marker_color); + dc.DrawRectangle(1, sz.y - 1, sz.x, 1); +} + +void ButtonsListCtrl::UpdateMode() +{ + m_mode_sizer->SetMode(Slic3r::GUI::wxGetApp().get_mode()); +} + +void ButtonsListCtrl::Rescale() +{ + m_mode_sizer->msw_rescale(); + for (ScalableButton* btn : m_pageButtons) + btn->msw_rescale(); +} + +void ButtonsListCtrl::SetSelection(int sel) +{ + if (m_selection == sel) + return; + m_selection = sel; + Refresh(); +} + +bool ButtonsListCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/* = false*/, const std::string& bmp_name/* = ""*/) +{ + ScalableButton* btn = new ScalableButton(this, wxID_ANY, bmp_name, text, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | (bmp_name.empty() ? 0 : wxBU_LEFT)); + btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) { + if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) { + m_selection = it - m_pageButtons.begin(); + wxCommandEvent evt = wxCommandEvent(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED); + evt.SetId(m_selection); + wxPostEvent(this->GetParent(), evt); + Refresh(); + } + }); + Slic3r::GUI::wxGetApp().UpdateDarkUI(btn); + m_pageButtons.insert(m_pageButtons.begin() + n, btn); + m_sizer->Insert(n, new wxSizerItem(btn, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3)); + m_sizer->Layout(); + return true; +} + +void ButtonsListCtrl::RemovePage(size_t n) +{ + ScalableButton* btn = m_pageButtons[n]; + m_pageButtons.erase(m_pageButtons.begin() + n); + m_sizer->Remove(n); + btn->Reparent(nullptr); + btn->Destroy(); + m_sizer->Layout(); +} + +bool ButtonsListCtrl::SetPageImage(size_t n, const std::string& bmp_name) const +{ + if (n >= m_pageButtons.size()) + return false; + return m_pageButtons[n]->SetBitmap_(bmp_name); +} + +void ButtonsListCtrl::SetPageText(size_t n, const wxString& strText) +{ + ScalableButton* btn = m_pageButtons[n]; + btn->SetLabel(strText); +} + +wxString ButtonsListCtrl::GetPageText(size_t n) const +{ + ScalableButton* btn = m_pageButtons[n]; + return btn->GetLabel(); +} + +#endif // _WIN32 + + diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp new file mode 100644 index 000000000..ef42679e9 --- /dev/null +++ b/src/slic3r/GUI/Notebook.hpp @@ -0,0 +1,305 @@ +#ifndef slic3r_Notebook_hpp_ +#define slic3r_Notebook_hpp_ + +#ifdef _WIN32 + +#include + +class ModeSizer; +class ScalableButton; + +// custom message the ButtonsListCtrl sends to its parent (Notebook) to notify a selection change: +wxDECLARE_EVENT(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, wxCommandEvent); + +class ButtonsListCtrl : public wxControl +{ +public: + ButtonsListCtrl(wxWindow* parent, bool add_mode_buttons = false); + ~ButtonsListCtrl() {} + + void OnPaint(wxPaintEvent&); + void SetSelection(int sel); + void UpdateMode(); + void Rescale(); + bool InsertPage(size_t n, const wxString& text, bool bSelect = false, const std::string& bmp_name = ""); + void RemovePage(size_t n); + bool SetPageImage(size_t n, const std::string& bmp_name) const; + void SetPageText(size_t n, const wxString& strText); + wxString GetPageText(size_t n) const; + +private: + wxWindow* m_parent; + wxBoxSizer* m_sizer; + std::vector m_pageButtons; + int m_selection {-1}; + ModeSizer* m_mode_sizer {nullptr}; +}; + +class Notebook: public wxBookCtrlBase +{ +public: + Notebook(wxWindow * parent, + wxWindowID winid = wxID_ANY, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0, + bool add_mode_buttons = false) + { + Init(); + Create(parent, winid, pos, size, style, add_mode_buttons); + } + + bool Create(wxWindow * parent, + wxWindowID winid = wxID_ANY, + const wxPoint & pos = wxDefaultPosition, + const wxSize & size = wxDefaultSize, + long style = 0, + bool add_mode_buttons = false) + { + if (!wxBookCtrlBase::Create(parent, winid, pos, size, style | wxBK_TOP)) + return false; + + m_bookctrl = new ButtonsListCtrl(this, add_mode_buttons); + + wxSizer* mainSizer = new wxBoxSizer(IsVertical() ? wxVERTICAL : wxHORIZONTAL); + + if (style & wxBK_RIGHT || style & wxBK_BOTTOM) + mainSizer->Add(0, 0, 1, wxEXPAND, 0); + + m_controlSizer = new wxBoxSizer(IsVertical() ? wxHORIZONTAL : wxVERTICAL); + m_controlSizer->Add(m_bookctrl, wxSizerFlags(1).Expand()); + wxSizerFlags flags; + if (IsVertical()) + flags.Expand(); + else + flags.CentreVertical(); + mainSizer->Add(m_controlSizer, flags.Border(wxALL, m_controlMargin)); + SetSizer(mainSizer); + + this->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt) + { + if (int page_idx = evt.GetId(); page_idx >= 0) + SetSelection(page_idx); + }); + return true; + } + + + // Methods specific to this class. + + // A method allowing to add a new page without any label (which is unused + // by this control) and show it immediately. + bool ShowNewPage(wxWindow * page) + { + return AddPage(page, wxString(), ""/*true *//* select it */); + } + + + // Set effect to use for showing/hiding pages. + void SetEffects(wxShowEffect showEffect, wxShowEffect hideEffect) + { + m_showEffect = showEffect; + m_hideEffect = hideEffect; + } + + // Or the same effect for both of them. + void SetEffect(wxShowEffect effect) + { + SetEffects(effect, effect); + } + + // And the same for time outs. + void SetEffectsTimeouts(unsigned showTimeout, unsigned hideTimeout) + { + m_showTimeout = showTimeout; + m_hideTimeout = hideTimeout; + } + + void SetEffectTimeout(unsigned timeout) + { + SetEffectsTimeouts(timeout, timeout); + } + + + // Implement base class pure virtual methods. + + // adds a new page to the control + bool AddPage(wxWindow* page, + const wxString& text, + const std::string& bmp_name, + bool bSelect = false) + { + DoInvalidateBestSize(); + return InsertPage(GetPageCount(), page, text, bmp_name, bSelect); + } + + // Page management + virtual bool InsertPage(size_t n, + wxWindow * page, + const wxString & text, + bool bSelect = false, + int imageId = NO_IMAGE) override + { + if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId)) + return false; + + GetBtnsListCtrl()->InsertPage(n, text, bSelect); + + if (!DoSetSelectionAfterInsertion(n, bSelect)) + page->Hide(); + + return true; + } + + bool InsertPage(size_t n, + wxWindow * page, + const wxString & text, + const std::string& bmp_name = "", + bool bSelect = false) + { + if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect)) + return false; + + GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name); + + if (!DoSetSelectionAfterInsertion(n, bSelect)) + page->Hide(); + + return true; + } + + virtual int SetSelection(size_t n) override + { + GetBtnsListCtrl()->SetSelection(n); + return DoSetSelection(n, SetSelection_SendEvent); + } + + virtual int ChangeSelection(size_t n) override + { + GetBtnsListCtrl()->SetSelection(n); + return DoSetSelection(n); + } + + // Neither labels nor images are supported but we still store the labels + // just in case the user code attaches some importance to them. + virtual bool SetPageText(size_t n, const wxString & strText) override + { + wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page")); + + GetBtnsListCtrl()->SetPageText(n, strText); + + return true; + } + + virtual wxString GetPageText(size_t n) const override + { + wxCHECK_MSG(n < GetPageCount(), wxString(), wxS("Invalid page")); + return GetBtnsListCtrl()->GetPageText(n); + } + + virtual bool SetPageImage(size_t WXUNUSED(n), int WXUNUSED(imageId)) override + { + return false; + } + + virtual int GetPageImage(size_t WXUNUSED(n)) const override + { + return NO_IMAGE; + } + + bool SetPageImage(size_t n, const std::string& bmp_name) + { + return GetBtnsListCtrl()->SetPageImage(n, bmp_name); + } + + // Override some wxWindow methods too. + virtual void SetFocus() override + { + wxWindow* const page = GetCurrentPage(); + if (page) + page->SetFocus(); + } + + ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast(m_bookctrl); } + + void UpdateMode() + { + GetBtnsListCtrl()->UpdateMode(); + } + + void Rescale() + { + GetBtnsListCtrl()->Rescale(); + } + +protected: + virtual void UpdateSelectedPage(size_t WXUNUSED(newsel)) override + { + // Nothing to do here, but must be overridden to avoid the assert in + // the base class version. + } + + virtual wxBookCtrlEvent * CreatePageChangingEvent() const override + { + return new wxBookCtrlEvent(wxEVT_BOOKCTRL_PAGE_CHANGING, + GetId()); + } + + virtual void MakeChangedEvent(wxBookCtrlEvent & event) override + { + event.SetEventType(wxEVT_BOOKCTRL_PAGE_CHANGED); + } + + virtual wxWindow * DoRemovePage(size_t page) override + { + wxWindow* const win = wxBookCtrlBase::DoRemovePage(page); + if (win) + { + GetBtnsListCtrl()->RemovePage(page); + DoSetSelectionAfterRemoval(page); + } + + return win; + } + + virtual void DoSize() override + { + wxWindow* const page = GetCurrentPage(); + if (page) + page->SetSize(GetPageRect()); + } + + virtual void DoShowPage(wxWindow * page, bool show) override + { + if (show) + page->ShowWithEffect(m_showEffect, m_showTimeout); + else + page->HideWithEffect(m_hideEffect, m_hideTimeout); + } + +private: + void Init() + { + // We don't need any border as we don't have anything to separate the + // page contents from. + SetInternalBorder(0); + + // No effects by default. + m_showEffect = + m_hideEffect = wxSHOW_EFFECT_NONE; + + m_showTimeout = + m_hideTimeout = 0; + } + + wxShowEffect m_showEffect, + m_hideEffect; + + unsigned m_showTimeout, + m_hideTimeout; + + ButtonsListCtrl* m_ctrl{ nullptr }; + +}; +#endif // _WIN32 +#endif // slic3r_Notebook_hpp_ diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 921f9f2b1..6f51f0cf8 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -272,27 +272,28 @@ void NotificationManager::PopNotification::count_lines() // find next suitable endline if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - m_window_width_offset) { // more than one line till end - size_t next_space = text.find_first_of(' ', last_end); - if (next_space > 0) { - size_t next_space_candidate = text.find_first_of(' ', next_space + 1); + int next_space = text.find_first_of(' ', last_end); + if (next_space > 0 && next_space < text.length()) { + int next_space_candidate = text.find_first_of(' ', next_space + 1); while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) { next_space = next_space_candidate; next_space_candidate = text.find_first_of(' ', next_space + 1); } - // when one word longer than line. - if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) { - float width_of_a = ImGui::CalcTextSize("a").x; - int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); - while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { - letter_count++; - } - m_endlines.push_back(last_end + letter_count); - last_end += letter_count; - } - else { - m_endlines.push_back(next_space); - last_end = next_space + 1; + } else { + next_space = text.length(); + } + // when one word longer than line. + if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset) { + float width_of_a = ImGui::CalcTextSize("a").x; + int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a); + while (last_end + letter_count < text.size() && ImGui::CalcTextSize(text.substr(last_end, letter_count).c_str()).x < m_window_width - m_window_width_offset) { + letter_count++; } + m_endlines.push_back(last_end + letter_count); + last_end += letter_count; + } else { + m_endlines.push_back(next_space); + last_end = next_space + 1; } } else { diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 1bcb93de0..17db606c0 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -87,7 +87,9 @@ enum class NotificationType DesktopIntegrationSuccess, DesktopIntegrationFail, UndoDesktopIntegrationSuccess, - UndoDesktopIntegrationFail + UndoDesktopIntegrationFail, + // Notification that a printer has more extruders than are supported by MM Gizmo/segmentation. + MmSegmentationExceededExtrudersLimit }; diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index bb0191b4d..7d20b15e5 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -68,7 +68,8 @@ PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::str // update Print Host upload from the selected preset m_parent->get_printer()->update_from_preset(*preset); // update values in parent (PhysicalPrinterDialog) - m_parent->update(); + m_parent->update(true); + } // update PrinterTechnology if it was changed @@ -154,7 +155,8 @@ void PresetForPrinter::msw_rescale() PhysicalPrinterDialog::PhysicalPrinterDialog(wxWindow* parent, wxString printer_name) : DPIDialog(parent, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), - m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()) + m_printer("", wxGetApp().preset_bundle->physical_printers.default_config()), + had_all_mk3(!printer_name.empty()) { SetFont(wxGetApp().normal_font()); #ifndef _WIN32 @@ -455,7 +457,7 @@ void PhysicalPrinterDialog::update_printhost_buttons() m_printhost_browse_btn->Enable(host->has_auto_discovery()); } -void PhysicalPrinterDialog::update() +void PhysicalPrinterDialog::update(bool printer_change) { m_optgroup->reload_config(); @@ -463,13 +465,24 @@ void PhysicalPrinterDialog::update() // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) bool supports_multiple_printers = false; if (tech == ptFFF) { - m_optgroup->show_field("host_type"); - m_optgroup->hide_field("printhost_authorization_type"); - m_optgroup->show_field("printhost_apikey", true); - for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) - m_optgroup->hide_field(opt_key); + update_host_type(printer_change); const auto opt = m_config->option>("host_type"); - supports_multiple_printers = opt && opt->value == htRepetier; + m_optgroup->show_field("host_type"); + if (opt->value == htPrusaLink) + { + m_optgroup->show_field("printhost_authorization_type"); + AuthorizationType auth_type = m_config->option>("printhost_authorization_type")->value; + m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); + for (const char* opt_key : { "printhost_user", "printhost_password" }) + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + } else { + m_optgroup->hide_field("printhost_authorization_type"); + m_optgroup->show_field("printhost_apikey", true); + for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) + m_optgroup->hide_field(opt_key); + supports_multiple_printers = opt && opt->value == htRepetier; + } + } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); @@ -493,6 +506,57 @@ void PhysicalPrinterDialog::update() this->Layout(); } +void PhysicalPrinterDialog::update_host_type(bool printer_change) +{ + if (m_presets.empty()) + return; + bool all_presets_are_from_mk3_family = true; + + for (PresetForPrinter* prstft : m_presets) { + std::string preset_name = prstft->get_preset_name(); + if (Preset* preset = wxGetApp().preset_bundle->printers.find_preset(preset_name)) { + std::string model_id = preset->config.opt_string("printer_model"); + if (preset->vendor && preset->vendor->name == "Prusa Research") { + const std::vector& models = preset->vendor->models; + auto it = std::find_if(models.begin(), models.end(), + [model_id](const VendorProfile::PrinterModel& model) { return model.id == model_id; }); + if (it != models.end() && it->family == "MK3") + continue; + } else if (!preset->vendor && model_id.rfind("MK3", 0) == 0) { + continue; + } + + } + all_presets_are_from_mk3_family = false; + break; + } + + Field* ht = m_optgroup->get_field("host_type"); + + wxArrayString types; + // Append localized enum_labels + assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size()); + for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) { + if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family) + continue; + types.Add(_(ht->m_opt.enum_labels[i])); + } + + Choice* choice = dynamic_cast(ht); + choice->set_values(types); + auto set_to_choice_and_config = [this, choice](PrintHostType type) { + choice->set_value(static_cast(type)); + m_config->set_key_value("host_type", new ConfigOptionEnum(type)); + }; + if ((printer_change && all_presets_are_from_mk3_family) || (!had_all_mk3 && all_presets_are_from_mk3_family)) + set_to_choice_and_config(htPrusaLink); + else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option>("host_type")->value == htPrusaLink)) + set_to_choice_and_config(htOctoPrint); + else + choice->set_value(m_config->option("host_type")->getInt()); + had_all_mk3 = all_presets_are_from_mk3_family; +} + wxString PhysicalPrinterDialog::get_printer_name() { @@ -628,8 +692,9 @@ void PhysicalPrinterDialog::AddPreset(wxEvent& event) m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W); update_full_printer_names(); - this->Fit(); + + update_host_type(true); } void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) @@ -657,7 +722,8 @@ void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer) this->Layout(); this->Fit(); + + update_host_type(true); } - }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.hpp b/src/slic3r/GUI/PhysicalPrinterDialog.hpp index 7ee1f7d92..cb9a48b3e 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.hpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.hpp @@ -85,7 +85,8 @@ public: PhysicalPrinterDialog(wxWindow* parent, wxString printer_name); ~PhysicalPrinterDialog(); - void update(); + void update(bool printer_change = false); + void update_host_type(bool printer_change); void update_printhost_buttons(); void update_printers(); wxString get_printer_name(); @@ -95,10 +96,11 @@ public: PrinterTechnology get_printer_technology(); void DeletePreset(PresetForPrinter* preset_for_printer); - protected: void on_dpi_changed(const wxRect& suggested_rect) override; void on_sys_color_changed() override {}; + + bool had_all_mk3; }; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 13e866965..064ac1b67 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -583,7 +583,7 @@ struct Sidebar::priv wxScrolledWindow *scrolled; wxPanel* presets_panel; // Used for MSW better layouts - ModeSizer *mode_sizer; + ModeSizer *mode_sizer {nullptr}; wxFlexGridSizer *sizer_presets; PlaterPresetComboBox *combo_print; std::vector combos_filament; @@ -754,7 +754,8 @@ Sidebar::Sidebar(Plater *parent) p->sliced_info = new SlicedInfo(p->scrolled); // Sizer in the scrolled area - scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL/*RIGHT | wxBOTTOM | wxRIGHT, 5*/); + if (p->mode_sizer) + scrolled_sizer->Add(p->mode_sizer, 0, wxALIGN_CENTER_HORIZONTAL); is_msw ? scrolled_sizer->Add(p->presets_panel, 0, wxEXPAND | wxLEFT, margin_5) : scrolled_sizer->Add(p->sizer_presets, 0, wxEXPAND | wxLEFT, margin_5); @@ -944,13 +945,16 @@ void Sidebar::update_presets(Preset::Type preset_type) void Sidebar::update_mode_sizer() const { - p->mode_sizer->SetMode(m_mode); + if (p->mode_sizer) + p->mode_sizer->SetMode(m_mode); } void Sidebar::change_top_border_for_mode_sizer(bool increase_border) { - p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0); - p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0); + if (p->mode_sizer) { + p->mode_sizer->set_items_flag(increase_border ? wxTOP : 0); + p->mode_sizer->set_items_border(increase_border ? int(0.5 * wxGetApp().em_unit()) : 0); + } } void Sidebar::update_reslice_btn_tooltip() const @@ -965,7 +969,8 @@ void Sidebar::msw_rescale() { SetMinSize(wxSize(40 * wxGetApp().em_unit(), -1)); - p->mode_sizer->msw_rescale(); + if (p->mode_sizer) + p->mode_sizer->msw_rescale(); for (PlaterPresetComboBox* combo : std::vector { p->combo_print, p->combo_sla_print, @@ -1009,7 +1014,8 @@ void Sidebar::sys_color_changed() for (wxWindow* btn : std::vector{ p->btn_reslice, p->btn_export_gcode }) wxGetApp().UpdateDarkUI(btn, true); - p->mode_sizer->msw_rescale(); + if (p->mode_sizer) + p->mode_sizer->msw_rescale(); p->frequently_changed_parameters->sys_color_changed(); p->object_settings->sys_color_changed(); #endif @@ -1394,6 +1400,12 @@ void Sidebar::collapse(bool collapse) wxGetApp().app_config->set("collapsed_sidebar", collapse ? "1" : "0"); } +#ifdef _MSW_DARK_MODE +void Sidebar::show_mode_sizer(bool show) +{ + p->mode_sizer->Show(show); +} +#endif void Sidebar::update_ui_from_settings() { @@ -2237,7 +2249,8 @@ std::vector Plater::priv::load_files(const std::vector& input_ DynamicPrintConfig config; { DynamicPrintConfig config_loaded; - model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, false, load_config); + ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::Enable }; + model = Slic3r::Model::read_from_archive(path.string(), &config_loaded, &config_substitutions, only_if(load_config, Model::LoadAttribute::CheckVersion)); if (load_config && !config_loaded.empty()) { // Based on the printer technology field found in the loaded config, select the base for the config, PrinterTechnology printer_technology = Preset::printer_technology(config_loaded); @@ -2261,6 +2274,12 @@ std::vector Plater::priv::load_files(const std::vector& input_ // and place the loaded config over the base. config += std::move(config_loaded); } + if (! config_substitutions.empty()) { + // TODO: + show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities." + " To recover these files, incompatible values were changed to default values." + " But data in files won't be changed until you save them in PrusaSlicer."))); + } this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z; } @@ -2330,7 +2349,7 @@ std::vector Plater::priv::load_files(const std::vector& input_ } } else { - model = Slic3r::Model::read_from_file(path.string(), nullptr, false, load_config); + model = Slic3r::Model::read_from_file(path.string(), nullptr, nullptr, only_if(load_config, Model::LoadAttribute::CheckVersion)); for (auto obj : model.objects) if (obj->name.empty()) obj->name = fs::path(obj->input_file).filename().string(); @@ -3215,7 +3234,7 @@ void Plater::priv::replace_with_stl() Model new_model; try { - new_model = Model::read_from_file(path, nullptr, true, false); + new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances); for (ModelObject* model_object : new_model.objects) { model_object->center_around_origin(); model_object->ensure_on_bed(); @@ -3388,7 +3407,7 @@ void Plater::priv::reload_from_disk() Model new_model; try { - new_model = Model::read_from_file(path, nullptr, true, false); + new_model = Model::read_from_file(path, nullptr, nullptr, Model::LoadAttribute::AddDefaultInstances); for (ModelObject* model_object : new_model.objects) { model_object->center_around_origin(); model_object->ensure_on_bed(); @@ -4599,7 +4618,9 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator // Switch to the other printer technology. Switch to the last printer active for that particular technology. AppConfig *app_config = wxGetApp().app_config; app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name); - wxGetApp().preset_bundle->load_presets(*app_config); + //FIXME Why are we reloading the whole preset bundle here? Please document. This is fishy and it is unnecessarily expensive. + // Anyways, don't report any config value substitutions, they have been already reported to the user at application start up. + wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent); // load_current_presets() calls Tab::load_current_preset() -> TabPrint::update() -> Object_list::update_and_show_object_settings_item(), // but the Object list still keeps pointer to the old Model. Avoid a crash by removing selection first. this->sidebar->obj_list()->unselect_objects(); @@ -5306,21 +5327,20 @@ void Plater::toggle_layers_editing(bool enable) wxPostEvent(canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); } -void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) +void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); auto *object = p->model.objects[obj_idx]; wxCHECK_RET(instance_idx < object->instances.size(), "instance_idx out of bounds"); - if (!keep_upper && !keep_lower) { + if (! attributes.has(ModelObjectCutAttribute::KeepUpper) && ! attributes.has(ModelObjectCutAttribute::KeepLower)) return; - } Plater::TakeSnapshot snapshot(this, _L("Cut by Plane")); wxBusyCursor wait; - const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower); + const auto new_objects = object->cut(instance_idx, z, attributes); remove(obj_idx); p->load_model_objects(new_objects); @@ -5328,9 +5348,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe Selection& selection = p->get_selection(); size_t last_id = p->model.objects.size() - 1; for (size_t i = 0; i < new_objects.size(); ++i) - { selection.add_object((unsigned int)(last_id - i), i == 0); - } } void Plater::export_gcode(bool prefer_removable) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 49bc952b8..fc4001ba5 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -9,6 +9,7 @@ #include "Selection.hpp" +#include "libslic3r/enum_bitmask.hpp" #include "libslic3r/Preset.hpp" #include "libslic3r/BoundingBox.hpp" #include "libslic3r/GCode/GCodeProcessor.hpp" @@ -24,6 +25,8 @@ namespace Slic3r { class Model; class ModelObject; +enum class ModelObjectCutAttribute : int; +using ModelObjectCutAttributes = enum_bitmask; class ModelInstance; class Print; class SLAPrint; @@ -109,6 +112,10 @@ public: void update_searcher(); void update_ui_from_settings(); +#ifdef _MSW_DARK_MODE + void show_mode_sizer(bool show); +#endif + std::vector& combos_filament(); Search::OptionsSearcher& get_searcher(); std::string& get_search_line(); @@ -204,7 +211,7 @@ public: void convert_unit(ConversionType conv_type); void toggle_layers_editing(bool enable); - void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); + void cut(size_t obj_idx, size_t instance_idx, coordf_t z, ModelObjectCutAttributes attributes); void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 6626ce9a5..7b1558599 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -6,7 +6,7 @@ #include "I18N.hpp" #include "libslic3r/AppConfig.hpp" #include -#include +#include "Notebook.hpp" namespace Slic3r { namespace GUI { @@ -58,15 +58,12 @@ void PreferencesDialog::build() #ifdef _MSW_DARK_MODE wxBookCtrlBase* tabs; - if (wxGetApp().dark_mode()) { - tabs = new wxListbook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNO_BORDER); - wxGetApp().UpdateDarkUI(tabs); - wxGetApp().UpdateDarkUI(dynamic_cast(tabs)->GetListView()); - } - else { +// if (wxGetApp().dark_mode()) + tabs = new Notebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); +/* else { tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME | wxNB_DEFAULT); tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); - } + }*/ #else wxNotebook* tabs = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL |wxNB_NOPAGETHEME | wxNB_DEFAULT ); tabs->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); @@ -345,7 +342,6 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("dark_color_mode") == "1" }); option = Option(def, "dark_color_mode"); m_optgroup_gui->append_single_option_line(option); -#endif def.label = L("Set settings tabs as menu items (experimental)"); def.type = coBool; @@ -354,6 +350,7 @@ void PreferencesDialog::build() def.set_default_value(new ConfigOptionBool{ app_config->get("tabs_as_menu") == "1" }); option = Option(def, "tabs_as_menu"); m_optgroup_gui->append_single_option_line(option); +#endif def.label = L("Use custom size for toolbar icons"); def.type = coBool; @@ -414,11 +411,7 @@ void PreferencesDialog::accept() // if (m_values.find("no_defaults") != m_values.end() // warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME)); - std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu" -#ifdef _MSW_DARK_MODE - ,"dark_color_mode" -#endif - }; + std::vector options_to_recreate_GUI = { "no_defaults", "tabs_as_menu" }; for (const std::string& option : options_to_recreate_GUI) { if (m_values.find(option) != m_values.end()) { @@ -432,9 +425,6 @@ void PreferencesDialog::accept() wxICON_QUESTION | wxYES | wxNO); if (dialog.ShowModal() == wxID_YES) { m_recreate_GUI = true; -#ifdef _MSW_DARK_MODE - m_color_mode_changed = m_values.find("dark_color_mode") != m_values.end(); -#endif } else { for (const std::string& option : options_to_recreate_GUI) @@ -495,6 +485,11 @@ void PreferencesDialog::accept() EndModal(wxID_OK); +#ifdef _MSW_DARK_MODE + if (m_values.find("dark_color_mode") != m_values.end()) + wxGetApp().force_colors_update(); +#endif + if (m_settings_layout_changed) ;// application will be recreated after Preference dialog will be destroyed else @@ -585,7 +580,9 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { - bool dark_mode = wxGetApp().dark_mode(); +#ifdef _MSW_DARK_MODE + bool disable_new_layout = wxGetApp().tabs_as_menu(); +#endif std::vector choices = { _L("Old regular layout with the tab bar"), _L("New layout, access via settings button in the top menu"), _L("Settings in non-modal window") }; @@ -596,7 +593,7 @@ void PreferencesDialog::create_settings_mode_widget() app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; #ifdef _MSW_DARK_MODE - if (dark_mode) { + if (disable_new_layout) { choices = { _L("Old regular layout with the tab bar"), _L("Settings in non-modal window") }; selection = app_config->get("dlg_settings_layout_mode") == "1" ? 1 : 0; @@ -621,14 +618,18 @@ void PreferencesDialog::create_settings_mode_widget() int dlg_id = 2; #ifdef _MSW_DARK_MODE - if (dark_mode) + if (disable_new_layout) dlg_id = 1; #endif - btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id, dark_mode](wxCommandEvent& ) { + btn->Bind(wxEVT_RADIOBUTTON, [this, id, dlg_id +#ifdef _MSW_DARK_MODE + , disable_new_layout +#endif + ](wxCommandEvent& ) { m_values["old_settings_layout_mode"] = (id == 0) ? "1" : "0"; #ifdef _MSW_DARK_MODE - if (!dark_mode) + if (!disable_new_layout) m_values["new_settings_layout_mode"] = (id == 1) ? "1" : "0"; #endif m_values["dlg_settings_layout_mode"] = (id == dlg_id) ? "1" : "0"; @@ -637,7 +638,7 @@ void PreferencesDialog::create_settings_mode_widget() } auto sizer = new wxBoxSizer(wxHORIZONTAL); - sizer->Add(/*m_layout_mode_box*/stb_sizer, 1, wxALIGN_CENTER_VERTICAL); + sizer->Add(stb_sizer, 1, wxALIGN_CENTER_VERTICAL); m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND | wxTOP, em_unit()); } diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 2ad3858ad..bcfafff5d 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -25,7 +25,6 @@ class PreferencesDialog : public DPIDialog std::shared_ptr m_optgroup_render; #endif // ENABLE_ENVIRONMENT_MAP wxSizer* m_icon_size_sizer; - wxRadioBox* m_layout_mode_box; wxColourPickerCtrl* m_sys_colour {nullptr}; wxColourPickerCtrl* m_mod_colour {nullptr}; bool isOSX {false}; @@ -35,9 +34,7 @@ class PreferencesDialog : public DPIDialog bool m_seq_top_gcode_indices_changed{ false }; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER bool m_recreate_GUI{false}; -#ifdef _MSW_DARK_MODE - bool m_color_mode_changed {false}; -#endif + public: explicit PreferencesDialog(wxWindow* parent); ~PreferencesDialog() = default; @@ -48,9 +45,6 @@ public: bool seq_seq_top_gcode_indices_changed() const { return m_seq_top_gcode_indices_changed; } #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER bool recreate_GUI() const { return m_recreate_GUI; } -#ifdef _MSW_DARK_MODE - bool color_mode_changed() const { return m_color_mode_changed; } -#endif void build(); void accept(); diff --git a/src/slic3r/GUI/SavePresetDialog.cpp b/src/slic3r/GUI/SavePresetDialog.cpp index 9c5e72fde..8fe10c882 100644 --- a/src/slic3r/GUI/SavePresetDialog.cpp +++ b/src/slic3r/GUI/SavePresetDialog.cpp @@ -208,12 +208,13 @@ SavePresetDialog::~SavePresetDialog() void SavePresetDialog::build(std::vector types, std::string suffix) { - SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #if defined(__WXMSW__) // ys_FIXME! temporary workaround for correct font scaling // Because of from wxWidgets 3.1.3 auto rescaling is implemented for the Fonts, // From the very beginning set dialog font to the wxSYS_DEFAULT_GUI_FONT this->SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); +#else + SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); #endif // __WXMSW__ if (suffix.empty()) @@ -240,6 +241,10 @@ void SavePresetDialog::build(std::vector types, std::string suffix topSizer->SetSizeHints(this); this->CenterOnScreen(); + +#ifdef _WIN32 + wxGetApp().UpdateDlgDarkUI(this); +#endif } void SavePresetDialog::AddItem(Preset::Type type, const std::string& suffix) diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8311525ec..8aaab1ed5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -44,6 +44,7 @@ #include "UnsavedChangesDialog.hpp" #include "SavePresetDialog.hpp" #include "MsgDialog.hpp" +#include "Notebook.hpp" #ifdef WIN32 #include @@ -255,8 +256,11 @@ void Tab::create_preset_tab() m_modified_label_clr = wxGetApp().get_label_clr_modified(); m_default_text_clr = wxGetApp().get_label_clr_default(); +#ifdef _MSW_DARK_MODE // Sizer with buttons for mode changing - m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this))); + if (wxGetApp().tabs_as_menu()) +#endif + m_mode_sizer = new ModeSizer(panel, int (0.5*em_unit(this))); const float scale_factor = /*wxGetApp().*/em_unit(this)*0.1;// GetContentScaleFactor(); m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -285,9 +289,11 @@ void Tab::create_preset_tab() // m_hsizer->AddStretchSpacer(32); // StretchSpacer has a strange behavior under OSX, so // There is used just additional sizer for m_mode_sizer with right alignment - auto mode_sizer = new wxBoxSizer(wxVERTICAL); - mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT); - m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); + if (m_mode_sizer) { + auto mode_sizer = new wxBoxSizer(wxVERTICAL); + mode_sizer->Add(m_mode_sizer, 1, wxALIGN_RIGHT); + m_hsizer->Add(mode_sizer, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, wxOSX ? 15 : 10); + } //Horizontal sizer to hold the tree and the selected page. m_hsizer = new wxBoxSizer(wxHORIZONTAL); @@ -490,7 +496,8 @@ void Tab::OnActivate() // Workaroud for Menu instead of NoteBook #ifdef _MSW_DARK_MODE - if (wxGetApp().tabs_as_menu()) { +// if (wxGetApp().tabs_as_menu()) + { wxSize sz = m_presets_choice->GetSize(); wxSize ok_sz = wxSize(35 * m_em_unit, m_presets_choice->GetBestSize().y+1); if (sz != ok_sz) { @@ -943,7 +950,8 @@ void Tab::update_mode() m_mode = wxGetApp().get_mode(); // update mode for ModeSizer - m_mode_sizer->SetMode(m_mode); + if (m_mode_sizer) + m_mode_sizer->SetMode(m_mode); update_visibility(); @@ -969,7 +977,8 @@ void Tab::msw_rescale() { m_em_unit = em_unit(m_parent); - m_mode_sizer->msw_rescale(); + if (m_mode_sizer) + m_mode_sizer->msw_rescale(); m_presets_choice->msw_rescale(); m_treectrl->SetMinSize(wxSize(20 * m_em_unit, -1)); @@ -1026,7 +1035,8 @@ void Tab::sys_color_changed() update_label_colours(); #ifdef _WIN32 wxWindowUpdateLocker noUpdates(this); - m_mode_sizer->msw_rescale(); + if (m_mode_sizer) + m_mode_sizer->msw_rescale(); wxGetApp().UpdateDarkUI(this); wxGetApp().UpdateDarkUI(m_treectrl); #endif @@ -3085,7 +3095,15 @@ void Tab::load_current_preset() } if (tab->supports_printer_technology(printer_technology)) { - wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title()); +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) { + std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" : + tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "resin" : "cog"; + dynamic_cast(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name); + } + else +#endif + wxGetApp().tab_panel()->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title()); #ifdef __linux__ // the tabs apparently need to be explicitly shown on Linux (pull request #1563) int page_id = wxGetApp().tab_panel()->FindPage(tab); wxGetApp().tab_panel()->GetPage(page_id)->Show(true); @@ -3099,6 +3117,10 @@ void Tab::load_current_preset() } static_cast(this)->m_printer_technology = printer_technology; m_active_page = tmp_page; +#ifdef _MSW_DARK_MODE + if (!wxGetApp().tabs_as_menu()) + dynamic_cast(wxGetApp().tab_panel())->SetPageImage(wxGetApp().tab_panel()->FindPage(this), printer_technology == ptFFF ? "printer" : "sla_printer"); +#endif } on_presets_changed(); if (printer_technology == ptFFF) { diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 762123e60..65c817cb6 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -129,7 +129,7 @@ protected: wxScrolledWindow* m_page_view {nullptr}; wxBoxSizer* m_page_sizer {nullptr}; - ModeSizer* m_mode_sizer; + ModeSizer* m_mode_sizer {nullptr}; struct PresetDependencies { Preset::Type type = Preset::TYPE_INVALID; diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index 377f50b9b..f3be89ef9 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -838,9 +838,13 @@ ScalableButton::ScalableButton( wxWindow * parent, Create(parent, id, label, pos, size, style); Slic3r::GUI::wxGetApp().UpdateDarkUI(this); - SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); - if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + if (!icon_name.empty()) { + SetBitmap(create_scaled_bitmap(icon_name, parent, m_px_cnt)); + if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + if (!label.empty()) + SetBitmapMargins(int(0.5* em_unit(parent)), 0); + } if (size != wxDefaultSize) { @@ -873,6 +877,20 @@ void ScalableButton::SetBitmap_(const ScalableBitmap& bmp) m_current_icon_name = bmp.name(); } +bool ScalableButton::SetBitmap_(const std::string& bmp_name) +{ + m_current_icon_name = bmp_name; + if (m_current_icon_name.empty()) + return false; + + wxBitmap bmp = create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt); + SetBitmap(bmp); + SetBitmapCurrent(bmp); + if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + return true; +} + void ScalableButton::SetBitmapDisabled_(const ScalableBitmap& bmp) { SetBitmapDisabled(bmp.bmp()); @@ -898,11 +916,13 @@ void ScalableButton::msw_rescale() { Slic3r::GUI::wxGetApp().UpdateDarkUI(this, m_has_border); - SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt)); - if (!m_disabled_icon_name.empty()) - SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); - else if (m_use_default_disabled_bitmap) - SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + if (!m_current_icon_name.empty()) { + SetBitmap(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt)); + if (!m_disabled_icon_name.empty()) + SetBitmapDisabled(create_scaled_bitmap(m_disabled_icon_name, m_parent, m_px_cnt)); + else if (m_use_default_disabled_bitmap) + SetBitmapDisabled(create_scaled_bitmap(m_current_icon_name, m_parent, m_px_cnt, true)); + } if (m_width > 0 || m_height>0) { diff --git a/src/slic3r/GUI/wxExtensions.hpp b/src/slic3r/GUI/wxExtensions.hpp index 1664a51f3..2b8965ba2 100644 --- a/src/slic3r/GUI/wxExtensions.hpp +++ b/src/slic3r/GUI/wxExtensions.hpp @@ -231,6 +231,7 @@ public: ~ScalableButton() {} void SetBitmap_(const ScalableBitmap& bmp); + bool SetBitmap_(const std::string& bmp_name); void SetBitmapDisabled_(const ScalableBitmap &bmp); int GetBitmapHeight(); void UseDefaultBitmapDisabled(); diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 92eab9307..e2970acf1 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -379,7 +379,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // PresetBundle bundle; on_progress(L("Loading repaired model"), 80); DynamicPrintConfig config; - bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), &config, &model, false); + ConfigSubstitutionContext config_substitutions{ ForwardCompatibilitySubstitutionRule::EnableSilent }; + bool loaded = Slic3r::load_3mf(path_dst.string().c_str(), config, config_substitutions, &model, false); boost::filesystem::remove(path_dst); if (! loaded) throw Slic3r::RuntimeError(L("Import of the repaired 3mf file failed")); diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index fad45f822..f01e3ad41 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -213,4 +213,48 @@ void SL1Host::set_auth(Http &http) const } } +// PrusaLink +PrusaLink::PrusaLink(DynamicPrintConfig* config) : + OctoPrint(config), + authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value), + username(config->opt_string("printhost_user")), + password(config->opt_string("printhost_password")) +{ +} + +const char* PrusaLink::get_name() const { return "PrusaLink"; } + +wxString PrusaLink::get_test_ok_msg() const +{ + return _(L("Connection to PrusaLink works correctly.")); +} + +wxString PrusaLink::get_test_failed_msg(wxString& msg) const +{ + return GUI::from_u8((boost::format("%s: %s") + % _utf8(L("Could not connect to PrusaLink")) + % std::string(msg.ToUTF8())).str()); +} + +bool PrusaLink::validate_version_text(const boost::optional& version_text) const +{ + return version_text ? (boost::starts_with(*version_text, "PrusaLink") || boost::starts_with(*version_text, "OctoPrint")) : false; +} + +void PrusaLink::set_auth(Http& http) const +{ + switch (authorization_type) { + case atKeyPassword: + http.header("X-Api-Key", get_apikey()); + break; + case atUserPassword: + http.auth_digest(username, password); + break; + } + + if (!get_cafile().empty()) { + http.ca_file(get_cafile()); + } +} + } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index f1b36096c..62bdfb6fa 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -70,6 +70,31 @@ private: std::string password; }; +class PrusaLink : public OctoPrint +{ +public: + PrusaLink(DynamicPrintConfig* config); + ~PrusaLink() override = default; + + const char* get_name() const override; + + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + bool can_start_print() const override { return true; } + +protected: + bool validate_version_text(const boost::optional& version_text) const override; + +private: + void set_auth(Http& http) const override; + + // Host authorization type. + AuthorizationType authorization_type; + // username and password for HTTP Digest Authentization (RFC RFC2617) + std::string username; + std::string password; +}; + } #endif diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index f0310073f..078c2fe20 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -56,16 +56,15 @@ static const char *TMP_EXTENSION = ".download"; void copy_file_fix(const fs::path &source, const fs::path &target) { - static const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read; // aka 644 - BOOST_LOG_TRIVIAL(debug) << format("PresetUpdater: Copying %1% -> %2%", source, target); - - // Make sure the file has correct permission both before and after we copy over it - if (fs::exists(target)) { - fs::permissions(target, perms); + std::string error_message; + CopyFileResult cfr = copy_file(source.string(), target.string(), error_message, false); + if (cfr != CopyFileResult::SUCCESS) { + BOOST_LOG_TRIVIAL(error) << "Copying failed(" << cfr << "): " << error_message; + throw Slic3r::CriticalException(GUI::format( + _L("Copying of file %1% to %2% failed: %3%"), + source, target, error_message)); } - fs::copy_file(source, target, fs::copy_option::overwrite_if_exists); - fs::permissions(target, perms); } struct Update @@ -612,7 +611,7 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons update.install(); PresetBundle bundle; - bundle.load_configbundle(update.source.string(), PresetBundle::LOAD_CFGBNDLE_SYSTEM); + bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem); BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size()); @@ -710,6 +709,17 @@ void PresetUpdater::slic3r_update_notify() } } +static void reload_configs_update_gui() +{ + // Reload global configuration + auto* app_config = GUI::wxGetApp().app_config; + // System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions + // were already presented to the user on application start up. Just do substitutions now and keep quiet about it. + GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent); + GUI::wxGetApp().load_current_presets(); + GUI::wxGetApp().plater()->set_bed_shape(); +} + PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const { if (! p->enabled_config_update) { return R_NOOP; } @@ -767,7 +777,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 } //forced update - if(incompatible_version) + if (incompatible_version) { BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. At least one requires higher version of Slicer.", updates.updates.size()); @@ -782,14 +792,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 const auto res = dlg.ShowModal(); if (res == wxID_OK) { BOOST_LOG_TRIVIAL(info) << "User wants to update..."; - p->perform_updates(std::move(updates)); - - // Reload global configuration - auto* app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); - GUI::wxGetApp().plater()->set_bed_shape(); + reload_configs_update_gui(); return R_UPDATE_INSTALLED; } else { @@ -814,11 +818,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3 if (res == wxID_OK) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; p->perform_updates(std::move(updates)); - - // Reload global configuration - auto* app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); + reload_configs_update_gui(); return R_UPDATE_INSTALLED; } else { @@ -871,11 +871,7 @@ void PresetUpdater::on_update_notification_confirm() if (res == wxID_OK) { BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update"; p->perform_updates(std::move(p->waiting_updates)); - - // Reload global configuration - auto* app_config = GUI::wxGetApp().app_config; - GUI::wxGetApp().preset_bundle->load_presets(*app_config); - GUI::wxGetApp().load_current_presets(); + reload_configs_update_gui(); p->has_waiting_updates = false; //return R_UPDATE_INSTALLED; } diff --git a/src/slic3r/Utils/PrintHost.cpp b/src/slic3r/Utils/PrintHost.cpp index 589679e47..53200a4c9 100644 --- a/src/slic3r/Utils/PrintHost.cpp +++ b/src/slic3r/Utils/PrintHost.cpp @@ -50,6 +50,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config) case htFlashAir: return new FlashAir(config); case htAstroBox: return new AstroBox(config); case htRepetier: return new Repetier(config); + case htPrusaLink: return new PrusaLink(config); default: return nullptr; } } else { diff --git a/tests/fff_print/test_data.cpp b/tests/fff_print/test_data.cpp index d55f9f061..f5424dfd9 100644 --- a/tests/fff_print/test_data.cpp +++ b/tests/fff_print/test_data.cpp @@ -200,14 +200,14 @@ void init_print(std::initializer_list input_meshes, Slic3r::Print void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list config_items, bool comments) { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize(config_items); + config.set_deserialize_strict(config_items); init_print(meshes, print, model, config, comments); } void init_print(std::initializer_list meshes, Slic3r::Print &print, Slic3r::Model &model, std::initializer_list config_items, bool comments) { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize(config_items); + config.set_deserialize_strict(config_items); init_print(meshes, print, model, config, comments); } diff --git a/tests/fff_print/test_flow.cpp b/tests/fff_print/test_flow.cpp index dc73f4b6e..81f748e19 100644 --- a/tests/fff_print/test_flow.cpp +++ b/tests/fff_print/test_flow.cpp @@ -19,7 +19,7 @@ SCENARIO("Extrusion width specifics", "[Flow]") { GIVEN("A config with a skirt, brim, some fill density, 3 perimeters, and 1 bottom solid layer and a 20mm cube mesh") { // this is a sharedptr DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize({ + config.set_deserialize_strict({ { "brim_width", 2 }, { "skirts", 1 }, { "perimeters", 3 }, diff --git a/tests/fff_print/test_gcodewriter.cpp b/tests/fff_print/test_gcodewriter.cpp index 9ee319c94..a4c8aa09a 100644 --- a/tests/fff_print/test_gcodewriter.cpp +++ b/tests/fff_print/test_gcodewriter.cpp @@ -10,7 +10,7 @@ SCENARIO("lift() is not ignored after unlift() at normal values of Z", "[GCodeWr GIVEN("A config from a file and a single extruder.") { GCodeWriter writer; GCodeConfig &config = writer.config; - config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini"); + config.load(std::string(TEST_DATA_DIR) + "/fff_print_tests/test_gcodewriter/config_lift_unlift.ini", ForwardCompatibilitySubstitutionRule::Disable); std::vector extruder_ids {0}; writer.set_extruders(extruder_ids); diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index fc2ac3dee..a139e4c2b 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -50,7 +50,7 @@ SCENARIO("Print: Skirt generation", "[Print]") { SCENARIO("Print: Changing number of solid surfaces does not cause all surfaces to become internal.", "[Print]") { GIVEN("sliced 20mm cube and config with top_solid_surfaces = 2 and bottom_solid_surfaces = 1") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize({ + config.set_deserialize_strict({ { "top_solid_layers", 2 }, { "bottom_solid_layers", 1 }, { "layer_height", 0.25 }, // get a known number of layers diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp index d6f6eca58..2a45bd200 100644 --- a/tests/fff_print/test_printgcode.cpp +++ b/tests/fff_print/test_printgcode.cpp @@ -224,7 +224,7 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") { { DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); config.set_num_extruders(4); - config.set_deserialize({ + config.set_deserialize_strict({ { "start_gcode", "; Extruder [current_extruder]" }, { "infill_extruder", 2 }, { "solid_infill_extruder", 2 }, diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 097f72dcc..8f508f323 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -31,7 +31,7 @@ static int get_brim_tool(const std::string &gcode) TEST_CASE("Skirt height is honored", "[Skirt]") { DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 1 }, { "skirt_height", 5 }, { "perimeters", 0 }, @@ -64,7 +64,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { GIVEN("A default configuration") { DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); config.set_num_extruders(4); - config.set_deserialize({ + config.set_deserialize_strict({ { "support_material_speed", 99 }, { "first_layer_height", 0.3 }, { "gcode_comments", true }, @@ -78,7 +78,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { }); WHEN("Brim width is set to 5") { - config.set_deserialize({ + config.set_deserialize_strict({ { "perimeters", 0 }, { "skirts", 0 }, { "brim_width", 5 } @@ -100,7 +100,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("Skirt area is smaller than the brim") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 1 }, { "brim_width", 10} }); @@ -110,7 +110,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("Skirt height is 0 and skirts > 0") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 2 }, { "skirt_height", 0 } }); @@ -123,7 +123,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { // This is a real error! One shall print the brim with the external perimeter extruder! WHEN("Perimeter extruder = 2 and support extruders = 3") { THEN("Brim is printed with the extruder used for the perimeters of first object") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "brim_width", 5 }, { "perimeter_extruder", 2 }, @@ -137,7 +137,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("Perimeter extruder = 2, support extruders = 3, raft is enabled") { THEN("brim is printed with same extruder as skirt") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "brim_width", 5 }, { "perimeter_extruder", 2 }, @@ -153,7 +153,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { #endif WHEN("brim width to 1 with layer_width of 0.5") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "first_layer_extrusion_width", 0.5 }, { "brim_width", 1 } @@ -167,7 +167,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { #if 0 WHEN("brim ears on a square") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "first_layer_extrusion_width", 0.5 }, { "brim_width", 1 }, @@ -182,7 +182,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { } WHEN("brim ears on a square but with a too small max angle") { - config.set_deserialize({ + config.set_deserialize_strict({ { "skirts", 0 }, { "first_layer_extrusion_width", 0.5 }, { "brim_width", 1 }, @@ -198,7 +198,7 @@ SCENARIO("Original Slic3r Skirt/Brim tests", "[SkirtBrim]") { #endif WHEN("Object is plated with overhang support and a brim") { - config.set_deserialize({ + config.set_deserialize_strict({ { "layer_height", 0.4 }, { "first_layer_height", 0.4 }, { "skirts", 1 }, diff --git a/tests/libslic3r/test_3mf.cpp b/tests/libslic3r/test_3mf.cpp index d0f459e4d..5ab000d04 100644 --- a/tests/libslic3r/test_3mf.cpp +++ b/tests/libslic3r/test_3mf.cpp @@ -14,7 +14,8 @@ SCENARIO("Reading 3mf file", "[3mf]") { WHEN("3mf model is read") { std::string path = std::string(TEST_DATA_DIR) + "/test_3mf/Geräte/Büchse.3mf"; DynamicPrintConfig config; - bool ret = load_3mf(path.c_str(), &config, &model, false); + ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; + bool ret = load_3mf(path.c_str(), config, ctxt, &model, false); THEN("load should succeed") { REQUIRE(ret); } @@ -56,7 +57,10 @@ SCENARIO("Export+Import geometry to/from 3mf file cycle", "[3mf]") { // load back the model from the 3mf file Model dst_model; DynamicPrintConfig dst_config; - load_3mf(test_file.c_str(), &dst_config, &dst_model, false); + { + ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; + load_3mf(test_file.c_str(), dst_config, ctxt, &dst_model, false); + } boost::filesystem::remove(test_file); // compare meshes diff --git a/tests/libslic3r/test_config.cpp b/tests/libslic3r/test_config.cpp index 417a29dca..7fbf31b11 100644 --- a/tests/libslic3r/test_config.cpp +++ b/tests/libslic3r/test_config.cpp @@ -9,7 +9,7 @@ SCENARIO("Generic config validation performs as expected.", "[Config]") { GIVEN("A config generated from default options") { Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config(); WHEN( "perimeter_extrusion_width is set to 250%, a valid value") { - config.set_deserialize("perimeter_extrusion_width", "250%"); + config.set_deserialize_strict("perimeter_extrusion_width", "250%"); THEN( "The config is read as valid.") { REQUIRE(config.validate().empty()); } @@ -40,7 +40,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A boolean option is set to a string value representing a 0 or 1") { - CHECK_NOTHROW(config.set_deserialize("gcode_comments", "1")); + CHECK_NOTHROW(config.set_deserialize_strict("gcode_comments", "1")); THEN("The underlying value is set correctly.") { REQUIRE(config.opt("gcode_comments")->getBool() == true); } @@ -59,7 +59,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A numeric option is set from serialized string") { - config.set_deserialize("bed_temperature", "100"); + config.set_deserialize_strict("bed_temperature", "100"); THEN("The underlying value is set correctly.") { REQUIRE(config.opt("bed_temperature")->get_at(0) == 100); } @@ -92,7 +92,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } WHEN("A numeric option is set to a non-numeric value.") { THEN("A BadOptionTypeException exception is thown.") { - REQUIRE_THROWS_AS(config.set_deserialize("perimeter_speed", "zzzz"), BadOptionTypeException); + REQUIRE_THROWS_AS(config.set_deserialize_strict("perimeter_speed", "zzzz"), BadOptionTypeException); } THEN("The value does not change.") { REQUIRE(config.opt("perimeter_speed")->getFloat() == 60.0); @@ -117,7 +117,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A float or percent is set as a percent through the string interface.") { - config.set_deserialize("first_layer_extrusion_width", "100%"); + config.set_deserialize_strict("first_layer_extrusion_width", "100%"); THEN("Value and percent flag are 100/true") { auto tmp = config.opt("first_layer_extrusion_width"); REQUIRE(tmp->percent == true); @@ -125,7 +125,7 @@ SCENARIO("Config accessor functions perform as expected.", "[Config]") { } } WHEN("A float or percent is set as a float through the string interface.") { - config.set_deserialize("first_layer_extrusion_width", "100"); + config.set_deserialize_strict("first_layer_extrusion_width", "100"); THEN("Value and percent flag are 100/false") { auto tmp = config.opt("first_layer_extrusion_width"); REQUIRE(tmp->percent == false); @@ -195,7 +195,7 @@ SCENARIO("Config ini load/save interface", "[Config]") { WHEN("new_from_ini is called") { Slic3r::DynamicPrintConfig config; std::string path = std::string(TEST_DATA_DIR) + "/test_config/new_from_ini.ini"; - config.load_from_ini(path); + config.load_from_ini(path, ForwardCompatibilitySubstitutionRule::Disable); THEN("Config object contains ini file options.") { REQUIRE(config.option_throw("filament_colour", false)->values.size() == 1); REQUIRE(config.option_throw("filament_colour", false)->values.front() == "#ABCD"); diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 8c56afc6d..59784e940 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -9,7 +9,7 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { PlaceholderParser parser; auto config = DynamicPrintConfig::full_print_config(); - config.set_deserialize( { + config.set_deserialize_strict( { { "printer_notes", " PRINTER_VENDOR_PRUSA3D PRINTER_MODEL_MK2 " }, { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, { "temperature", "357;359;363;378" } diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index aec6ceb6a..20288243e 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -282,7 +282,7 @@ bool ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* v break; } default: - if (! opt->deserialize(std::string(SvPV_nolen(value)))) + if (! opt->deserialize(std::string(SvPV_nolen(value)), ForwardCompatibilitySubstitutionRule::Disable)) return false; } return true; @@ -295,7 +295,8 @@ bool ConfigBase__set_deserialize(ConfigBase* THIS, const t_config_option_key &op size_t len; const char * c = SvPV(str, len); std::string value(c, len); - return THIS->set_deserialize_nothrow(opt_key, value); + ConfigSubstitutionContext ctxt{ ForwardCompatibilitySubstitutionRule::Disable }; + return THIS->set_deserialize_nothrow(opt_key, value, ctxt); } void ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value, bool deserialize) diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 52a5e7d83..703427035 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -55,7 +55,7 @@ %code%{ auto config = new DynamicPrintConfig(); try { - config->load(path); + config->load(path, ForwardCompatibilitySubstitutionRule::Disable); RETVAL = config; } catch (std::exception& e) { delete config; @@ -119,7 +119,7 @@ %code%{ auto config = new FullPrintConfig(); try { - config->load(path); + config->load(path, ForwardCompatibilitySubstitutionRule::Disable); RETVAL = static_cast(config); } catch (std::exception& e) { delete config; diff --git a/xs/xsp/Flow.xsp b/xs/xsp/Flow.xsp index 019af16f2..3056b4001 100644 --- a/xs/xsp/Flow.xsp +++ b/xs/xsp/Flow.xsp @@ -30,7 +30,7 @@ _new_from_width(CLASS, role, width, nozzle_diameter, height) float height; CODE: ConfigOptionFloatOrPercent optwidth; - optwidth.deserialize(width); + optwidth.deserialize(width, ForwardCompatibilitySubstitutionRule::Disable); RETVAL = new Flow(Flow::new_from_config_width(role, optwidth, nozzle_diameter, height)); OUTPUT: RETVAL diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 93067ebe3..e7a171efd 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -22,7 +22,7 @@ %name{read_from_file} Model(std::string input_file, bool add_default_instances = true) %code%{ try { - RETVAL = new Model(Model::read_from_file(input_file, nullptr, add_default_instances)); + RETVAL = new Model(Model::read_from_file(input_file, nullptr, nullptr, only_if(add_default_instances, Model::LoadAttribute::AddDefaultInstances))); } catch (std::exception& e) { croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); }