Browse Source

Merge remote-tracking branch 'remotes/prusa/stable' into dev

need to merge profiles
need to test & iron out the PresetConfigSubstitutions thingy
remi durand 3 years ago
parent
commit
f87b5fccde

+ 71 - 0
resources/icons/info.svg

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   id="error"
+   x="0px"
+   y="0px"
+   viewBox="0 0 200 200"
+   enable-background="new 0 0 100 100"
+   xml:space="preserve"
+   sodipodi:docname="notification_error.svg"
+   width="200"
+   height="200"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata
+   id="metadata19"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs17" /><sodipodi:namedview
+   inkscape:document-rotation="0"
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="2560"
+   inkscape:window-height="1377"
+   id="namedview15"
+   showgrid="false"
+   inkscape:zoom="5.04"
+   inkscape:cx="117.17146"
+   inkscape:cy="98.609664"
+   inkscape:window-x="-8"
+   inkscape:window-y="-8"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="error" />
+<g
+   id="g4"
+   transform="matrix(2.52,0,0,2.52,-26,-26)">
+	<path
+   fill="#808080"
+   d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
+   id="path2" />
+</g>
+<g
+   id="g8"
+   transform="matrix(2.52,0,0,2.52,-26,-26)">
+	<circle
+   fill="#808080"
+   cx="50"
+   cy="65"
+   r="5"
+   id="circle6" />
+</g>
+<g
+   id="g12"
+   transform="matrix(2.52,0,0,2.52,-26,-26)">
+	<path
+   fill="#808080"
+   d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z"
+   id="path10" />
+</g>
+</svg>

+ 71 - 0
resources/icons/white/info.svg

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.0"
+   id="error"
+   x="0px"
+   y="0px"
+   viewBox="0 0 200 200"
+   enable-background="new 0 0 100 100"
+   xml:space="preserve"
+   sodipodi:docname="notification_error.svg"
+   width="200"
+   height="200"
+   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"><metadata
+   id="metadata19"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs17" /><sodipodi:namedview
+   inkscape:document-rotation="0"
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="2560"
+   inkscape:window-height="1377"
+   id="namedview15"
+   showgrid="false"
+   inkscape:zoom="5.04"
+   inkscape:cx="117.17146"
+   inkscape:cy="98.609664"
+   inkscape:window-x="-8"
+   inkscape:window-y="-8"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="error" />
+<g
+   id="g4"
+   transform="matrix(2.52,0,0,2.52,-26,-26)">
+	<path
+   fill="#FFFFFF"
+   d="m 50,54.25 c -2.35,0 -4.25,-1.9 -4.25,-4.25 V 35 c 0,-2.35 1.9,-4.25 4.25,-4.25 2.35,0 4.25,1.9 4.25,4.25 v 15 c 0,2.35 -1.9,4.25 -4.25,4.25 z"
+   id="path2" />
+</g>
+<g
+   id="g8"
+   transform="matrix(2.52,0,0,2.52,-26,-26)">
+	<circle
+   fill="#FFFFFF"
+   cx="50"
+   cy="65"
+   r="5"
+   id="circle6" />
+</g>
+<g
+   id="g12"
+   transform="matrix(2.52,0,0,2.52,-26,-26)">
+	<path
+   fill="#FFFFFF"
+   d="M 50,89.25 C 28.36,89.25 10.75,71.64 10.75,50 10.75,28.36 28.36,10.75 50,10.75 71.64,10.75 89.25,28.36 89.25,50 89.25,71.64 71.64,89.25 50,89.25 Z m 0,-70 C 33.05,19.25 19.25,33.04 19.25,50 19.25,66.95 33.04,80.75 50,80.75 66.95,80.75 80.75,66.96 80.75,50 80.75,33.05 66.95,19.25 50,19.25 Z"
+   id="path10" />
+</g>
+</svg>

+ 19 - 5
src/PrusaSlicer.cpp

@@ -110,7 +110,8 @@ int CLI::run(int argc, char **argv)
             boost::algorithm::iends_with(boost::filesystem::path(argv[0]).filename().string(), GCODEVIEWER_APP_CMD);
 #endif // _WIN32
 
-    const std::vector<std::string> &load_configs		= m_config.option<ConfigOptionStrings>("load", true)->values;
+    const std::vector<std::string>              &load_configs		      = m_config.option<ConfigOptionStrings>("load", true)->values;
+    const ForwardCompatibilitySubstitutionRule   config_substitution_rule = m_config.option<ConfigOptionEnum<ForwardCompatibilitySubstitutionRule>>("config_compatibility", true)->value;
 
     // load config files supplied via --load
     for (auto const &file : load_configs) {
@@ -122,13 +123,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 = Slic3r::printer_technology(config);
         if (printer_technology == ptUnknown) {
@@ -166,7 +173,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 = Slic3r::printer_technology(config);
                 if (printer_technology == ptUnknown) {
                     printer_technology = other_printer_technology;
@@ -175,6 +184,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);

+ 133 - 15
src/libslic3r/AppConfig.cpp

@@ -3,6 +3,7 @@
 #include "AppConfig.hpp"
 #include "Exception.hpp"
 #include "Thread.hpp"
+#include "format.hpp"
 
 #include <utility>
 #include <vector>
@@ -16,9 +17,13 @@
 #include <boost/property_tree/ptree_fwd.hpp>
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/format/format_fwd.hpp>
+#include <boost/log/trivial.hpp>
 
-//#include <wx/string.h>
-//#include "I18N.hpp"
+#ifdef WIN32
+//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
+#include <boost/uuid/detail/md5.hpp>
+#include <boost/algorithm/hex.hpp>
+#endif
 
 namespace Slic3r {
 
@@ -263,15 +268,103 @@ 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 <boost/md5.hpp> 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) {
+#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
@@ -283,6 +376,7 @@ std::string AppConfig::load()
         */
         return ex.what();
     }
+    }
 
     // 2) Parse the property_tree, extract the sections and key / value pairs.
     for (const auto &section : tree) {
@@ -358,22 +452,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 std::pair<std::string, std::string> &kvp : m_storage[""])
-        c << kvp.first << " = " << kvp.second << std::endl;
+    for (const auto& kvp : m_storage[""])
+        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;
-    	for (const std::pair<std::string, std::string> &kvp : category.second)
-	        c << kvp.first << " = " << kvp.second << std::endl;
+        config_ss << std::endl << "[" << category.first << "]" << std::endl;
+        for (const auto& kvp : category.second)
+            config_ss << kvp.first << " = " << kvp.second << std::endl;
 	}
     // Write vendor sections
     for (const auto &vendor : m_vendors) {
@@ -381,17 +474,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<std::string> 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;
         }
     }
+    // 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;
 }

+ 1 - 1
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();
 

+ 1 - 0
src/libslic3r/CMakeLists.txt

@@ -34,6 +34,7 @@ add_library(libslic3r STATIC
     EdgeGrid.hpp
     ElephantFootCompensation.cpp
     ElephantFootCompensation.hpp
+    enum_bitmask.hpp
     ExPolygon.cpp
     ExPolygon.hpp
     ExPolygonCollection.cpp

+ 118 - 53
src/libslic3r/Config.cpp

@@ -21,6 +21,10 @@
 #include <boost/format.hpp>
 #include <string.h>
 
+//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 {
 
 
@@ -244,6 +248,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<std::string> ConfigOptionDef::cli_args(const std::string &key) const
 {
 	std::vector<std::string> args;
@@ -270,7 +278,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
 	    case coPercents:        return new ConfigOptionPercentsNullable();
         case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
 	    case coBools:           return new ConfigOptionBoolsNullable();
-	    default:                throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
+	    default:                throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
 	    }
 	} else {
 	    switch (this->type) {
@@ -291,7 +299,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
 	    case coBool:            return new ConfigOptionBool();
 	    case coBools:           return new ConfigOptionBools();
 	    case coEnum:            return new ConfigOptionEnumGeneric(this->enum_keys_map);
-	    default:                throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
+	    default:                throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
 	    }
 	}
 }
@@ -394,7 +402,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()) {
@@ -525,7 +534,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;
@@ -535,23 +544,22 @@ 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)) {
-        throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
-    }
+    if (! this->set_deserialize_nothrow(opt_key_src, value_src, substitutions_ctxt, append))
+        throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src,  value_src));
 }
 
-void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items)
+void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
 {
-	for (const SetDeserializeItem &item : items)
-		this->set_deserialize(item.opt_key, item.opt_value, item.append);
+    for (const SetDeserializeItem &item : items)
+        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;
     // Try to deserialize the option by its name.
@@ -580,7 +588,7 @@ 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;
     }
@@ -588,10 +596,56 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
     ConfigOption *opt = this->option(opt_key, true);
     if (opt == nullptr)
         throw new UnknownOptionException(opt_key);
+    bool success     = true;
+    if (!optdef->can_phony || !value.empty()) {
+        success = true;
+        bool substituted = false;
+        if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
+            //FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
+            bool nullable = opt->nullable();
+            ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
+            if (optdef->default_value) {
+                // Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
+                assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
+                auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
+                if (values.size() == 1 && values.front() == 1)
+                    default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
+            }
+            auto result = nullable ?
+                static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
+                static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
+            success     = result != ConfigHelpers::DeserializationResult::Failed;
+            substituted = result == ConfigHelpers::DeserializationResult::Substituted;
+        } else {
+            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) && ConfigHelpers::looks_like_enum_value(value)) {
+                // Deserialize failed, try to substitute with a default value.
+                assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
+                if (optdef->type == coEnum && opt_key == "gcode_flavor" && (value == "marlin2" || value == "marlinfirmware"))
+                    static_cast<ConfigOptionEnum<GCodeFlavor>*>(opt)->value = gcfMarlin;
+                else if (optdef->type == coBool)
+                    static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
+                else
+                    // Just use the default of the option.
+                    opt->set(optdef->default_value.get());
+                success     = true;
+                substituted = true;
+            }
+        }
 
-    bool ok = true;
-    if (!optdef->can_phony || !value.empty())
-        ok = opt->deserialize(value, append);
+        if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
+                            substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
+            // Log the substitution.
+            ConfigSubstitution config_substitution;
+            config_substitution.opt_def   = optdef;
+            config_substitution.old_value = value;
+            config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
+            substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
+        }
+    }
     //set phony status
     if (optdef->can_phony)
         if(value.empty())
@@ -600,8 +654,7 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
             opt->set_phony(false);
     else
         opt->set_phony(false);
-
-    return ok;
+    return success;
 }
 
 // Return an absolute value of a possibly relative config variable.
@@ -662,7 +715,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
             cast_opt->get_abs_value(this->get_abs_value(opt_def->ratio_over));
     }
     std::stringstream ss; ss << "ConfigBase::get_abs_value(): "<< opt_key<<" has not a valid option type for get_abs_value()";
-    throw Slic3r::RuntimeError(ss.str());
+    throw ConfigurationError(ss.str());
 }
 
 // Return an absolute value of a possibly relative config variable.
@@ -673,7 +726,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
     const ConfigOption *raw_opt = this->option(opt_key);
     assert(raw_opt != nullptr);
     if (raw_opt->type() != coFloatOrPercent)
-        throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
+        throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
     // Compute absolute value.
     return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
 }
@@ -696,67 +749,78 @@ 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, 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)
 {
+    try {
     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);
+    } catch (const ConfigurationError &e) {
+        throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
+}
 }
 
-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<std::string>());
+            this->set_deserialize(opt_key, v.second.get_value<std::string>(), 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)
+ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
 {
+    try {
     // Read a 64k block from the end of the G-code.
-    boost::nowide::ifstream ifs(file);
-    {
-        const char slic3r_gcode_header[] = "; generated by Slic3r ";
-        const char slic3rpp_gcode_header[] = "; generated by Slic3r++ ";
-        const char superslicer_gcode_header[] = "; generated by SuperSlicer ";
-        const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
-        std::string firstline;
-        std::getline(ifs, firstline);
-        if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
+        boost::nowide::ifstream ifs(file);
+        {
+            const char slic3r_gcode_header[] = "; generated by Slic3r ";
+            const char slic3rpp_gcode_header[] = "; generated by Slic3r++ ";
+            const char superslicer_gcode_header[] = "; generated by SuperSlicer ";
+            const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
+            std::string firstline;
+            std::getline(ifs, firstline);
+            if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
             strncmp(slic3rpp_gcode_header, firstline.c_str(), strlen(slic3rpp_gcode_header)) != 0 &&
             strncmp(superslicer_gcode_header, firstline.c_str(), strlen(superslicer_gcode_header)) != 0 &&
             strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
-			throw Slic3r::RuntimeError("Not a g-code recognized for configuration import.");
-    }
+                throw ConfigurationError("Not a g-code recognized for configuration import.");
+        }
     ifs.seekg(0, ifs.end);
-	auto file_length = ifs.tellg();
-	auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
-	ifs.seekg(file_length - data_length, ifs.beg);
+    auto file_length = ifs.tellg();
+    auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
+    ifs.seekg(file_length - data_length, ifs.beg);
     std::vector<char> data(size_t(data_length) + 1, 0);
     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));
+            throw ConfigurationError(format("Suspiciously low number of configuration values extracted from %1%: %2%", file, key_value_pairs));
+        return std::move(substitutions_ctxt.substitutions);
+    } catch (const ConfigurationError &e) {
+        throw ConfigurationError(format("Failed loading configuration from G-code \"%1%\": %2%", file, e.what()));
+}
 }
 
 // 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;
@@ -801,8 +865,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str)
         if (key == nullptr)
             break;
         try {
-            //change it from set_deserialize to set_deserialize_nothrow to allow bad/old config to swtch to default value.
-            if(this->set_deserialize_nothrow(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 */) {
@@ -839,7 +902,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);
     }
 }
 
@@ -890,7 +953,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
         throw NoDefinitionException(opt_key);
     const ConfigOptionDef *optdef = def->get(opt_key);
     if (optdef == nullptr)
-//        throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
+//        throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
         // Let the parent decide what to do if the opt_key is not defined by this->def().
         return nullptr;
     ConfigOption *opt = optdef->create_default_option();
@@ -1010,8 +1073,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<ConfigOptionString*>(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;
 			}

+ 189 - 68
src/libslic3r/Config.hpp

@@ -16,6 +16,7 @@
 #include "Exception.hpp"
 #include "Point.hpp"
 
+#include <boost/algorithm/string/predicate.hpp>
 #include <boost/algorithm/string/trim.hpp>
 #include <boost/format/format_fwd.hpp>
 #include <boost/property_tree/ptree_fwd.hpp>
@@ -75,23 +76,59 @@ enum OptionCategory : int
 };
 std::string toString(OptionCategory opt);
 
-/// Specialization of std::exception to indicate that an unknown config option has been encountered.
-class UnknownOptionException : public Slic3r::RuntimeError {
+namespace ConfigHelpers {
+	inline bool looks_like_enum_value(std::string value)
+	{
+		boost::trim(value);
+		if (value.empty() || value.size() > 64 || ! isalpha(value.front()))
+			return false;
+		for (const char c : value)
+			if (! (isalnum(c) || c == '_' || c == '-'))
+				return false;
+		return true;
+	}
+
+	inline bool enum_looks_like_true_value(std::string value) {
+		boost::trim(value);
+		return boost::iequals(value, "enabled") || boost::iequals(value, "on");
+	}
+
+	enum class DeserializationSubstitution {
+		Disabled,
+		DefaultsToFalse,
+		DefaultsToTrue
+	};
+
+    enum class DeserializationResult {
+    	Loaded,
+    	Substituted,
+    	Failed,
+    };
+};
+
+// Base for all exceptions thrown by the configuration layer.
+class ConfigurationError : public Slic3r::RuntimeError {
+public:
+    using RuntimeError::RuntimeError;
+};
+
+// Specialization of std::exception to indicate that an unknown config option has been encountered.
+class UnknownOptionException : public ConfigurationError {
 public:
     UnknownOptionException() :
-        Slic3r::RuntimeError("Unknown option exception") {}
+        ConfigurationError("Unknown option exception") {}
     UnknownOptionException(const std::string &opt_key) :
-        Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
+        ConfigurationError(std::string("Unknown option exception: ") + opt_key) {}
 };
 
-/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
-class NoDefinitionException : public Slic3r::RuntimeError
+// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
+class NoDefinitionException : public ConfigurationError
 {
 public:
     NoDefinitionException() :
-        Slic3r::RuntimeError("No definition exception") {}
+        ConfigurationError("No definition exception") {}
     NoDefinitionException(const std::string &opt_key) :
-        Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
+        ConfigurationError(std::string("No definition exception: ") + opt_key) {}
 };
 // a bit more specific than a runtime_error
 class ConfigurationException : public std::runtime_error
@@ -103,13 +140,22 @@ public:
         std::runtime_error(std::string("Configuration exception: ") + opt_key) {}
 };
 
-/// Indicate that an unsupported accessor was called on a config option.
-class BadOptionTypeException : public Slic3r::RuntimeError
+// Indicate that an unsupported accessor was called on a config option.
+class BadOptionTypeException : public ConfigurationError
 {
 public:
-	BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
-	BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
-    BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
+	BadOptionTypeException() : ConfigurationError("Bad option type exception") {}
+	BadOptionTypeException(const std::string &message) : ConfigurationError(message) {}
+    BadOptionTypeException(const char* message) : ConfigurationError(message) {}
+};
+
+// Indicate that an option has been deserialized from an invalid value.
+class BadOptionValueException : public ConfigurationError
+{
+public:
+    BadOptionValueException() : ConfigurationError("Bad option value exception") {}
+    BadOptionValueException(const std::string &message) : ConfigurationError(message) {}
+    BadOptionValueException(const char* message) : ConfigurationError(message) {}
 };
 
 // Type of a configuration value.
@@ -208,8 +254,47 @@ inline OutputFormat operator&=(OutputFormat& a, OutputFormat b) {
     a = a & b; return a;
 }
 
+enum ForwardCompatibilitySubstitutionRule
+{
+    // Disable susbtitution, throw exception if an option value is not recognized.
+    Disable,
+    // Enable substitution of an unknown option value with default. Log the substitution.
+    Enable,
+    // Enable substitution of an unknown option value with default. Don't log the substitution.
+    EnableSilent,
+    // Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles.
+    EnableSystemSilent,
+    // Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile.
+    EnableSilentDisableSystem,
+};
+
+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<ConfigOption, ConfigOptionDeleter>;
+
+// 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<ConfigSubstitution>;
+
+// 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 {
@@ -277,7 +362,7 @@ public:
     void set(const ConfigOption *rhs) override
     {
         if (rhs->type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
+            throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
         assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
         this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
         this->phony = rhs->phony;
@@ -286,7 +371,7 @@ public:
     bool operator==(const ConfigOption &rhs) const override
     {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
         assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
         return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
     }
@@ -352,7 +437,7 @@ public:
     void set(const ConfigOption *rhs) override
     {
         if (rhs->type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
+            throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type");
         assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
         this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
         this->phony = rhs->phony;
@@ -370,12 +455,12 @@ public:
             if (opt->type() == this->type()) {
                 auto other = static_cast<const ConfigOptionVector<T>*>(opt);
                 if (other->values.empty())
-                    throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
+                    throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector");
                 this->values.emplace_back(other->values.front());
             } else if (opt->type() == this->scalar_type())
                 this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
             else
-                throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
+                throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type");
         }
     }
 
@@ -394,12 +479,12 @@ public:
             // Assign the first value of the rhs vector.
             auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
             if (other->values.empty())
-                throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
+                throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
             this->values[i] = other->get_at(j);
         } else if (rhs->type() == this->scalar_type())
             this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
         else
-            throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
+            throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
     }
 
     const T& get_at(size_t i) const
@@ -426,7 +511,7 @@ public:
                 if (opt_default == nullptr)
                     this->values.resize(n, this->default_value);
                 if (opt_default->type() != this->type())
-                    throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
+                    throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type.");
                 if(static_cast<const ConfigOptionVector<T>*>(opt_default)->values.empty())
                     this->values.resize(n, this->default_value);
                 else
@@ -446,7 +531,7 @@ public:
     bool operator==(const ConfigOption &rhs) const override
     {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionVector: Comparing incompatible types");
         assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
         return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
     }
@@ -458,9 +543,9 @@ public:
     // An option overrides another option if it is not nil and not equal.
     bool overriden_by(const ConfigOption *rhs) const override {
         if (this->nullable())
-        	throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
+        	throw ConfigurationError("Cannot override a nullable ConfigOption.");
         if (rhs->type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
+            throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
     	auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
     	if (! rhs->nullable())
     		// Overridding a non-nullable object with another non-nullable object.
@@ -478,9 +563,9 @@ public:
     // Apply an override option, possibly a nullable one.
     bool apply_override(const ConfigOption *rhs) override {
         if (this->nullable())
-        	throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
+        	throw ConfigurationError("Cannot override a nullable ConfigOption.");
         if (rhs->type() != this->type())
-			throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
+			throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
 		auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
 		if (! rhs->nullable()) {
     		// Overridding a non-nullable object with another non-nullable object.
@@ -571,7 +656,7 @@ public:
     bool                    operator==(const ConfigOptionFloatsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
     bool 					operator==(const ConfigOption &rhs) const override {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types");
         assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
         return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
     }
@@ -618,7 +703,7 @@ public:
         		if (NULLABLE)
         			this->values.push_back(nil_value());
         		else
-        			throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+        			throw ConfigurationError("Deserializing nil into a non-nullable object");
         	} else {
 	            std::istringstream iss(item_str);
 	            double value;
@@ -643,9 +728,9 @@ protected:
         		if (NULLABLE)
         			ss << "nil";
         		else
-                    throw Slic3r::RuntimeError("Serializing NaN");
+                    throw ConfigurationError("Serializing NaN");
         	} else
-                throw Slic3r::RuntimeError("Serializing invalid number");
+                throw ConfigurationError("Serializing invalid number");
 	}
     static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
     	if (NULLABLE) {
@@ -764,7 +849,7 @@ public:
         		if (NULLABLE)
         			this->values.push_back(nil_value());
         		else
-                    throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+                    throw ConfigurationError("Deserializing nil into a non-nullable object");
         	} else {
 	            std::istringstream iss(item_str);
 	            int32_t value;
@@ -781,7 +866,7 @@ private:
         		if (NULLABLE)
         			ss << "nil";
         		else
-                    throw Slic3r::RuntimeError("Serializing NaN");
+                    throw ConfigurationError("Serializing NaN");
         	} else
         		ss << v;
 	}
@@ -811,7 +896,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);
@@ -970,7 +1055,7 @@ public:
     bool                        operator==(const ConfigOption &rhs) const override
     {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types");
         assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
         return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
     }
@@ -981,7 +1066,7 @@ public:
 
     void set(const ConfigOption *rhs) override {
         if (rhs->type() != this->type())
-            throw ConfigurationException("ConfigOptionFloatOrPercent: Assigning an incompatible type");
+            throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
         assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
         *this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
     }
@@ -1046,7 +1131,7 @@ public:
     bool                    operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const { return vectors_equal(this->values, rhs.values); }
     bool                    operator==(const ConfigOption &rhs) const override {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
         assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
         return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
     }
@@ -1093,7 +1178,7 @@ public:
                 if (NULLABLE)
                     this->values.push_back(nil_value());
                 else
-                    throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+                    throw ConfigurationError("Deserializing nil into a non-nullable object");
             } else {
                 bool percent = item_str.find_first_of("%") != std::string::npos;
                 std::istringstream iss(item_str);
@@ -1121,9 +1206,9 @@ protected:
                 if (NULLABLE)
                     ss << "nil";
                 else
-                    throw Slic3r::RuntimeError("Serializing NaN");
+                    throw ConfigurationError("Serializing NaN");
             } else
-                throw Slic3r::RuntimeError("Serializing invalid number");
+                throw ConfigurationError("Serializing invalid number");
     }
     static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
         if (NULLABLE) {
@@ -1312,8 +1397,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") {
+            this->value = true;
+            return true;
+        }
+        if (str == "0") {
+            this->value = false;
+            return true;
+        }
+        return false;
     }
 
 private:
@@ -1374,24 +1466,39 @@ public:
         }
         return vv;
     }
-    
-    bool deserialize(const std::string &str, bool append = false) override
+
+    ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution)
     {
         if (! append)
             this->values.clear();
         std::istringstream is(str);
         std::string item_str;
+        bool substituted = false;
         while (std::getline(is, item_str, ',')) {
         	boost::trim(item_str);
+        	unsigned char new_value = 0;
         	if (item_str == "nil") {
         		if (NULLABLE)
         			this->values.push_back(nil_value());
         		else
-                    throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
+                    throw ConfigurationError("Deserializing nil into a non-nullable object");
+        	} else if (item_str == "1") {
+        		new_value = true;
+        	} else if (item_str == "0") {
+        		new_value = false;
+        	} else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) {
+        		new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
+        		substituted = true;
         	} else
-        		this->values.push_back(item_str.compare("1") == 0);	
+        		return ConfigHelpers::DeserializationResult::Failed;
+            this->values.push_back(new_value);
         }
-        return true;
+        return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded;
+    }
+
+    bool deserialize(const std::string &str, bool append = false) override
+    {
+    	return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded;
     }
 
 protected:
@@ -1400,7 +1507,7 @@ protected:
         		if (NULLABLE)
         			ss << "nil";
         		else
-                    throw Slic3r::RuntimeError("Serializing NaN");
+                    throw ConfigurationError("Serializing NaN");
         	} else
         		ss << (v ? "1" : "0");
 	}
@@ -1436,14 +1543,14 @@ public:
     bool operator==(const ConfigOption &rhs) const override
     {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types");
         // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
         return this->value == (T)rhs.getInt();
     }
 
     void set(const ConfigOption *rhs) override {
         if (rhs->type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
+            throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type");
         // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
         this->value = (T)rhs->getInt();
         this->phony = rhs->phony;
@@ -1522,14 +1629,14 @@ public:
     bool operator==(const ConfigOption &rhs) const override
     {
         if (rhs.type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
+            throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types");
         // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
         return this->value == rhs.getInt();
     }
 
     void set(const ConfigOption *rhs) override {
         if (rhs->type() != this->type())
-            throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
+            throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
         // rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
         this->value = rhs->getInt();
         this->phony = rhs->phony;
@@ -1585,7 +1692,7 @@ public:
 		    case coInts:            { auto opt = new ConfigOptionIntsNullable();	archive(*opt); return opt; }
 		    case coPercents:        { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
 		    case coBools:           { auto opt = new ConfigOptionBoolsNullable();	archive(*opt); return opt; }
-		    default:                throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
+		    default:                throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
 		    }
     	} else {
 		    switch (this->type) {
@@ -1604,7 +1711,7 @@ public:
 		    case coBool:            { auto opt = new ConfigOptionBool(); 			archive(*opt); return opt; }
 		    case coBools:           { auto opt = new ConfigOptionBools(); 			archive(*opt); return opt; }
 		    case coEnum:            { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
-		    default:                throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
+		    default:                throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
 		    }
 		}
 	}
@@ -1616,7 +1723,7 @@ public:
 		    case coInts:            archive(*static_cast<const ConfigOptionIntsNullable*>(opt));    break;
 		    case coPercents:        archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
 		    case coBools:           archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); 	break;
-		    default:                throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
+		    default:                throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
 		    }
 		} else {
 		    switch (this->type) {
@@ -1635,7 +1742,7 @@ public:
 		    case coBool:            archive(*static_cast<const ConfigOptionBool*>(opt)); 			break;
 		    case coBools:           archive(*static_cast<const ConfigOptionBools*>(opt)); 			break;
 		    case coEnum:            archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); 	break;
-		    default:                throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
+		    default:                throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
 		    }
 		}
 		// Make the compiler happy, shut up the warnings.
@@ -1731,6 +1838,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
@@ -1758,7 +1873,7 @@ public:
         return out;
     }
 
-    /// Iterate through all of the CLI options and write them to a stream.
+    // Iterate through all of the CLI options and write them to a stream.
     std::ostream&           print_cli_help(
         std::ostream& out, bool show_defaults, 
         std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
@@ -1809,6 +1924,8 @@ public:
     }
 };
 
+
+
 // An abstract configuration store.
 class ConfigBase : public ConfigOptionResolver
 {
@@ -1905,9 +2022,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) {}
@@ -1922,17 +2041,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<SetDeserializeItem> items);
+    void set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions);
+    void set_deserialize_strict(std::initializer_list<SetDeserializeItem> 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);
+    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, 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, bool to_prusa = false) const;
 
 	// Set all the nullable values to nils.
@@ -1940,7 +2061,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.
@@ -2110,9 +2231,9 @@ private:
 	template<class Archive> void serialize(Archive &ar) { ar(options); }
 };
 
-/// Configuration store with a static definition of configuration values.
-/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
-/// because the configuration values could be accessed directly.
+// Configuration store with a static definition of configuration values.
+// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
+// because the configuration values could be accessed directly.
 class StaticConfig : public virtual ConfigBase
 {
 public:

+ 29 - 24
src/libslic3r/Format/3mf.cpp

@@ -427,7 +427,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();
@@ -442,16 +442,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
@@ -518,7 +518,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);
@@ -547,7 +547,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;
@@ -568,7 +568,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()
@@ -590,7 +590,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);
@@ -653,7 +653,7 @@ namespace Slic3r {
                 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
@@ -665,7 +665,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);
                     print_config_parsed = true;
                 }
                 if (boost::algorithm::iequals(name, CUSTOM_GCODE_PER_PRINT_Z_FILE))
@@ -687,7 +687,8 @@ namespace Slic3r {
         }
         //parsed superslicer/prusa files if slic3r not found
         //note that is we successfully read one of the config file, then the other ones should also have the same name
-        auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename](const std::string &print_config_name, const std::string& model_config_name) -> bool {
+        auto read_from_other_storage = [this, &print_config_parsed, num_entries, &archive, &stat, &config, &model, &filename, &config_substitutions]
+                (const std::string &print_config_name, const std::string& model_config_name) -> bool {
             for (mz_uint i = 0; i < num_entries; ++i)
             {
                 if (mz_zip_reader_file_stat(&archive, i, &stat))
@@ -699,7 +700,7 @@ namespace Slic3r {
                     if (boost::algorithm::iequals(name, print_config_name))
                     {
                         // 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);
                         print_config_parsed = true;
                     } else if (boost::algorithm::iequals(name, model_config_name))
                     {
@@ -774,7 +775,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
@@ -791,7 +792,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;
         }
 
@@ -868,7 +869,12 @@ 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)
         {
@@ -879,7 +885,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);
         }
     }
 
@@ -954,7 +960,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)
         {
@@ -1003,8 +1009,7 @@ namespace Slic3r {
                             continue;
                         std::string opt_key = option.second.get<std::string>("<xmlattr>.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));
@@ -1895,7 +1900,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())
         {
@@ -1997,7 +2002,7 @@ namespace Slic3r {
                 else if (metadata.key == SOURCE_IN_INCHES)
                     volume->source.is_converted_from_inches = metadata.value == "1";
                 else
-                    volume->config.set_deserialize(metadata.key, metadata.value);
+                    volume->config.set_deserialize(metadata.key, metadata.value, config_substitutions);
             }
         }
 
@@ -2962,13 +2967,13 @@ 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;
 
         _3MF_Importer importer;
-        bool res = importer.load_model_from_file(path, *model, *config, check_version);
+        bool res = importer.load_model_from_file(path, *model, config, config_substitutions, check_version);
         importer.log_errors();
         return res;
     }

+ 2 - 1
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

Some files were not shown because too many files changed in this diff