Browse Source

WIP: Nullable configuration value concept, implemented for
ConfigOptionFloatsNullable, ConfigOptionIntsNullable,
ConfigOptionPercentsNullable, ConfigOptionBoolsNullable.

retract override values were added to the Filament profile:
vector of floats: "retract_length", "retract_lift", "retract_lift_above",
"retract_lift_below", "retract_speed", "deretract_speed",
"retract_restart_extra", "retract_before_travel",
vector of bools: "retract_layer_change", "wipe"
vector of percents: "retract_before_wipe"

bubnikv 5 years ago
parent
commit
3b1a44c084

+ 1 - 1
src/boost/nowide/utf8_codecvt.hpp

@@ -102,7 +102,7 @@ protected:
         #ifndef BOOST_NOWIDE_DO_LENGTH_MBSTATE_CONST
         return from - save_from;
         #else
-        return save_max - max;
+        return int(save_max - max);
         #endif
     }
 

+ 68 - 19
src/libslic3r/Config.cpp

@@ -211,25 +211,35 @@ std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
 
 ConfigOption* ConfigOptionDef::create_empty_option() const
 {
-    switch (this->type) {
-    case coFloat:           return new ConfigOptionFloat();
-    case coFloats:          return new ConfigOptionFloats();
-    case coInt:             return new ConfigOptionInt();
-    case coInts:            return new ConfigOptionInts();
-    case coString:          return new ConfigOptionString();
-    case coStrings:         return new ConfigOptionStrings();
-    case coPercent:         return new ConfigOptionPercent();
-    case coPercents:        return new ConfigOptionPercents();
-    case coFloatOrPercent:  return new ConfigOptionFloatOrPercent();
-    case coPoint:           return new ConfigOptionPoint();
-    case coPoints:          return new ConfigOptionPoints();
-    case coPoint3:          return new ConfigOptionPoint3();
-//    case coPoint3s:         return new ConfigOptionPoint3s();
-    case coBool:            return new ConfigOptionBool();
-    case coBools:           return new ConfigOptionBools();
-    case coEnum:            return new ConfigOptionEnumGeneric(this->enum_keys_map);
-    default:                throw std::runtime_error(std::string("Unknown option type for option ") + this->label);
-    }
+	if (this->nullable) {
+	    switch (this->type) {
+	    case coFloats:          return new ConfigOptionFloatsNullable();
+	    case coInts:            return new ConfigOptionIntsNullable();
+	    case coPercents:        return new ConfigOptionPercentsNullable();
+	    case coBools:           return new ConfigOptionBoolsNullable();
+	    default:                throw std::runtime_error(std::string("Unknown option type for nullable option ") + this->label);
+	    }
+	} else {
+	    switch (this->type) {
+	    case coFloat:           return new ConfigOptionFloat();
+	    case coFloats:          return new ConfigOptionFloats();
+	    case coInt:             return new ConfigOptionInt();
+	    case coInts:            return new ConfigOptionInts();
+	    case coString:          return new ConfigOptionString();
+	    case coStrings:         return new ConfigOptionStrings();
+	    case coPercent:         return new ConfigOptionPercent();
+	    case coPercents:        return new ConfigOptionPercents();
+	    case coFloatOrPercent:  return new ConfigOptionFloatOrPercent();
+	    case coPoint:           return new ConfigOptionPoint();
+	    case coPoints:          return new ConfigOptionPoints();
+	    case coPoint3:          return new ConfigOptionPoint3();
+	//    case coPoint3s:         return new ConfigOptionPoint3s();
+	    case coBool:            return new ConfigOptionBool();
+	    case coBools:           return new ConfigOptionBools();
+	    case coEnum:            return new ConfigOptionEnumGeneric(this->enum_keys_map);
+	    default:                throw std::runtime_error(std::string("Unknown option type for option ") + this->label);
+	    }
+	}
 }
 
 ConfigOption* ConfigOptionDef::create_default_option() const
@@ -254,6 +264,13 @@ ConfigOptionDef* ConfigDef::add(const t_config_option_key &opt_key, ConfigOption
     return opt;
 }
 
+ConfigOptionDef* ConfigDef::add_nullable(const t_config_option_key &opt_key, ConfigOptionType type)
+{
+	ConfigOptionDef *def = this->add(opt_key, type);
+	def->nullable = true;
+	return def;
+}
+
 std::string ConfigOptionDef::nocli = "~~~noCLI";
 
 std::ostream& ConfigDef::print_cli_help(std::ostream& out, bool show_defaults, std::function<bool(const ConfigOptionDef &)> filter) const
@@ -642,6 +659,17 @@ void ConfigBase::save(const std::string &file) const
     c.close();
 }
 
+// Set all the nullable values to nils.
+void ConfigBase::null_nullables()
+{
+    for (const std::string &opt_key : this->keys()) {
+        ConfigOption *opt = this->optptr(opt_key, false);
+        assert(opt != nullptr);
+        if (opt->nullable())
+        	opt->deserialize("nil");
+    }
+}
+
 bool DynamicConfig::operator==(const DynamicConfig &rhs) const
 {
     auto it1     = this->options.begin();
@@ -655,6 +683,19 @@ bool DynamicConfig::operator==(const DynamicConfig &rhs) const
     return it1 == it1_end && it2 == it2_end;
 }
 
+// Remove options with all nil values, those are optional and it does not help to hold them.
+size_t DynamicConfig::remove_nil_options()
+{
+	size_t cnt_removed = 0;
+	for (auto it = options.begin(); it != options.end();)
+		if (it->second->is_nil()) {
+			it = options.erase(it);
+			++ cnt_removed;
+		} else
+			++ it;
+	return cnt_removed;
+}
+
 ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create)
 {
     auto it = options.find(opt_key);
@@ -838,18 +879,22 @@ CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<Slic3r::Vec2d>)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionVector<unsigned char>)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloat)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloats)
+CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatsNullable)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInt)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionInts)
+CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionIntsNullable)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionString)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionStrings)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercent)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercents)
+CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPercentsNullable)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionFloatOrPercent)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoints)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionPoint3)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBool)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBools)
+CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionBoolsNullable)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigOptionEnumGeneric)
 CEREAL_REGISTER_TYPE(Slic3r::ConfigBase)
 CEREAL_REGISTER_TYPE(Slic3r::DynamicConfig)
@@ -868,17 +913,21 @@ CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::Con
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVectorBase, Slic3r::ConfigOptionVector<unsigned char>)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<double>, Slic3r::ConfigOptionFloat)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloats)
+CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<double>, Slic3r::ConfigOptionFloatsNullable)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<int>, Slic3r::ConfigOptionInt)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionInts)
+CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<int>, Slic3r::ConfigOptionIntsNullable)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<std::string>, Slic3r::ConfigOptionString)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<std::string>, Slic3r::ConfigOptionStrings)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloat, Slic3r::ConfigOptionPercent)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercents)
+CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionFloats, Slic3r::ConfigOptionPercentsNullable)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionPercent, Slic3r::ConfigOptionFloatOrPercent)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec2d>, Slic3r::ConfigOptionPoint)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<Slic3r::Vec2d>, Slic3r::ConfigOptionPoints)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<Slic3r::Vec3d>, Slic3r::ConfigOptionPoint3)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionSingle<bool>, Slic3r::ConfigOptionBool)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<unsigned char>, Slic3r::ConfigOptionBools)
+CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionVector<unsigned char>, Slic3r::ConfigOptionBoolsNullable)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigOptionInt, Slic3r::ConfigOptionEnumGeneric)
 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ConfigBase, Slic3r::DynamicConfig)

+ 246 - 119
src/libslic3r/Config.hpp

@@ -15,6 +15,7 @@
 #include "clonable_ptr.hpp"
 #include "Point.hpp"
 
+#include <boost/algorithm/string/trim.hpp>
 #include <boost/format.hpp>
 #include <boost/property_tree/ptree.hpp>
 
@@ -124,6 +125,10 @@ public:
     bool                        operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); }
     bool                        is_scalar()     const { return (int(this->type()) & int(coVectorType)) == 0; }
     bool                        is_vector()     const { return ! this->is_scalar(); }
+    // If this option is nullable, then it may have its value or values set to nil.
+    virtual bool 				nullable()		const { return false; }
+    // A scalar is nil, or all values of a vector are nil.
+    virtual bool 				is_nil() 		const { return false; }
 };
 
 typedef ConfigOption*       ConfigOptionPtr;
@@ -345,26 +350,35 @@ private:
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<double>>(this)); }
 };
 
-class ConfigOptionFloats : public ConfigOptionVector<double>
+template<bool NULLABLE>
+class ConfigOptionFloatsTempl : public ConfigOptionVector<double>
 {
 public:
-    ConfigOptionFloats() : ConfigOptionVector<double>() {}
-    explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector<double>(n, value) {}
-    explicit ConfigOptionFloats(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {}
-    explicit ConfigOptionFloats(const std::vector<double> &vec) : ConfigOptionVector<double>(vec) {}
-    explicit ConfigOptionFloats(std::vector<double> &&vec) : ConfigOptionVector<double>(std::move(vec)) {}
+    ConfigOptionFloatsTempl() : ConfigOptionVector<double>() {}
+    explicit ConfigOptionFloatsTempl(size_t n, double value) : ConfigOptionVector<double>(n, value) {}
+    explicit ConfigOptionFloatsTempl(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {}
+    explicit ConfigOptionFloatsTempl(const std::vector<double> &vec) : ConfigOptionVector<double>(vec) {}
+    explicit ConfigOptionFloatsTempl(std::vector<double> &&vec) : ConfigOptionVector<double>(std::move(vec)) {}
 
     static ConfigOptionType static_type() { return coFloats; }
     ConfigOptionType        type()  const override { return static_type(); }
-    ConfigOption*           clone() const override { return new ConfigOptionFloats(*this); }
-    bool                    operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; }
+    ConfigOption*           clone() const override { return new ConfigOptionFloatsTempl(*this); }
+    bool                    operator==(const ConfigOptionFloatsTempl &rhs) const { return this->values == rhs.values; }
+    // Could a special "nil" value be stored inside the vector, indicating undefined value?
+    bool 					nullable() const override { return NULLABLE; }
+    // Special "nil" value to be stored into the vector if this->supports_nil().
+    static double 			nil_value() { return std::numeric_limits<double>::quiet_NaN(); }
+    // A scalar is nil, or all values of a vector are nil.
+    virtual bool 			is_nil() const override { for (auto v : this->values) if (! std::isnan(v)) return false; return true; }
+    bool 					is_nil(size_t idx) const { return std::isnan(v->values[idx]); }
 
     std::string serialize() const override
     {
         std::ostringstream ss;
-        for (std::vector<double>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
-            if (it - this->values.begin() != 0) ss << ",";
-            ss << *it;
+        for (const double &v : this->values) {
+            if (&v != &this->values.front())
+            	ss << ",";
+        	serialize_single_value(ss, v);
         }
         return ss.str();
     }
@@ -373,14 +387,14 @@ public:
     {
         std::vector<std::string> vv;
         vv.reserve(this->values.size());
-        for (std::vector<double>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+        for (const double v : this->values) {
             std::ostringstream ss;
-            ss << *it;
+        	serialize_single_value(ss, v);
             vv.push_back(ss.str());
         }
         return vv;
     }
-    
+
     bool deserialize(const std::string &str, bool append = false) override
     {
         if (! append)
@@ -388,25 +402,49 @@ public:
         std::istringstream is(str);
         std::string item_str;
         while (std::getline(is, item_str, ',')) {
-            std::istringstream iss(item_str);
-            double value;
-            iss >> value;
-            this->values.push_back(value);
+        	boost::trim(item_str);
+        	if (item_str == "nil") {
+        		if (NULLABLE)
+        			this->values.push_back(nil_value());
+        		else
+        			std::runtime_error("Deserializing nil into a non-nullable object");
+        	} else {
+	            std::istringstream iss(item_str);
+	            double value;
+	            iss >> value;
+	            this->values.push_back(value);
+	        }
         }
         return true;
     }
 
-    ConfigOptionFloats& operator=(const ConfigOption *opt)
+    ConfigOptionFloatsTempl& operator=(const ConfigOption *opt)
     {   
         this->set(opt);
         return *this;
     }
 
+protected:
+	void serialize_single_value(std::ostringstream &ss, const double v) const {
+        	if (std::isfinite(v))
+	            ss << v;
+	        else if (std::isnan(v)) {
+        		if (NULLABLE)
+        			ss << "nil";
+        		else
+        			std::runtime_error("Serializing NaN");
+        	} else
+        		std::runtime_error("Serializing invalid number");		
+	}
+
 private:
 	friend class cereal::access;
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<double>>(this)); }
 };
 
+using ConfigOptionFloats 		 = ConfigOptionFloatsTempl<false>;
+using ConfigOptionFloatsNullable = ConfigOptionFloatsTempl<true>;
+
 class ConfigOptionInt : public ConfigOptionSingle<int>
 {
 public:
@@ -447,35 +485,45 @@ private:
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<int>>(this)); }
 };
 
-class ConfigOptionInts : public ConfigOptionVector<int>
+template<bool NULLABLE>
+class ConfigOptionIntsTempl : public ConfigOptionVector<int>
 {
 public:
-    ConfigOptionInts() : ConfigOptionVector<int>() {}
-    explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
-    explicit ConfigOptionInts(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
+    ConfigOptionIntsTempl() : ConfigOptionVector<int>() {}
+    explicit ConfigOptionIntsTempl(size_t n, int value) : ConfigOptionVector<int>(n, value) {}
+    explicit ConfigOptionIntsTempl(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {}
 
     static ConfigOptionType static_type() { return coInts; }
     ConfigOptionType        type()  const override { return static_type(); }
-    ConfigOption*           clone() const override { return new ConfigOptionInts(*this); }
-    ConfigOptionInts&       operator=(const ConfigOption *opt) { this->set(opt); return *this; }
-    bool                    operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; }
+    ConfigOption*           clone() const override { return new ConfigOptionIntsTempl(*this); }
+    ConfigOptionIntsTempl&  operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+    bool                    operator==(const ConfigOptionIntsTempl &rhs) const { return this->values == rhs.values; }
+    // Could a special "nil" value be stored inside the vector, indicating undefined value?
+    bool 					nullable() const override { return NULLABLE; }
+    // Special "nil" value to be stored into the vector if this->supports_nil().
+    static int	 			nil_value() { return std::numeric_limits<int>::max(); }
+    // A scalar is nil, or all values of a vector are nil.
+    virtual bool 			is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; }
+    bool 					is_nil(size_t idx) const { return v->values[idx] == nil_value(); }
 
-    std::string serialize() const override {
+    std::string serialize() const override
+    {
         std::ostringstream ss;
-        for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
-            if (it - this->values.begin() != 0) ss << ",";
-            ss << *it;
+        for (const int &v : this->values) {
+            if (&v != &this->values.front())
+            	ss << ",";
+        	serialize_single_value(ss, v);
         }
         return ss.str();
     }
     
-    std::vector<std::string> vserialize() const override 
+    std::vector<std::string> vserialize() const override
     {
         std::vector<std::string> vv;
         vv.reserve(this->values.size());
-        for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
+        for (const int v : this->values) {
             std::ostringstream ss;
-            ss << *it;
+        	serialize_single_value(ss, v);
             vv.push_back(ss.str());
         }
         return vv;
@@ -488,19 +536,40 @@ public:
         std::istringstream is(str);
         std::string item_str;
         while (std::getline(is, item_str, ',')) {
-            std::istringstream iss(item_str);
-            int value;
-            iss >> value;
-            this->values.push_back(value);
+        	boost::trim(item_str);
+        	if (item_str == "nil") {
+        		if (NULLABLE)
+        			this->values.push_back(nil_value());
+        		else
+        			std::runtime_error("Deserializing nil into a non-nullable object");
+        	} else {
+	            std::istringstream iss(item_str);
+	            int value;
+	            iss >> value;
+	            this->values.push_back(value);
+	        }
         }
         return true;
     }
 
 private:
+	void serialize_single_value(std::ostringstream &ss, const int v) const {
+			if (v == nil_value()) {
+        		if (NULLABLE)
+        			ss << "nil";
+        		else
+        			std::runtime_error("Serializing NaN");
+        	} else
+        		ss << v;
+	}
+
 	friend class cereal::access;
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<int>>(this)); }
 };
 
+using ConfigOptionInts   	   = ConfigOptionIntsTempl<false>;
+using ConfigOptionIntsNullable = ConfigOptionIntsTempl<true>;
+
 class ConfigOptionString : public ConfigOptionSingle<std::string>
 {
 public:
@@ -603,64 +672,61 @@ private:
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionFloat>(this)); }
 };
 
-class ConfigOptionPercents : public ConfigOptionFloats
+template<bool NULLABLE>
+class ConfigOptionPercentsTempl : public ConfigOptionFloatsTempl<NULLABLE>
 {
 public:
-    ConfigOptionPercents() : ConfigOptionFloats() {}
-    explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {}
-    explicit ConfigOptionPercents(std::initializer_list<double> il) : ConfigOptionFloats(std::move(il)) {}
+    ConfigOptionPercentsTempl() : ConfigOptionFloatsTempl<NULLABLE>() {}
+    explicit ConfigOptionPercentsTempl(size_t n, double value) : ConfigOptionFloatsTempl<NULLABLE>(n, value) {}
+    explicit ConfigOptionPercentsTempl(std::initializer_list<double> il) : ConfigOptionFloatsTempl<NULLABLE>(std::move(il)) {}
+	explicit ConfigOptionPercentsTempl(const std::vector<double>& vec) : ConfigOptionFloatsTempl<NULLABLE>(vec) {}
+	explicit ConfigOptionPercentsTempl(std::vector<double>&& vec) : ConfigOptionFloatsTempl<NULLABLE>(std::move(vec)) {}
 
     static ConfigOptionType static_type() { return coPercents; }
     ConfigOptionType        type()  const override { return static_type(); }
-    ConfigOption*           clone() const override { return new ConfigOptionPercents(*this); }
-    ConfigOptionPercents&   operator=(const ConfigOption *opt) { this->set(opt); return *this; }
-    bool                    operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; }
+    ConfigOption*           clone() const override { return new ConfigOptionPercentsTempl(*this); }
+    ConfigOptionPercentsTempl&   operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+    bool                    operator==(const ConfigOptionPercentsTempl &rhs) const { return this->values == rhs.values; }
 
     std::string serialize() const override
     {
         std::ostringstream ss;
-        for (const auto &v : this->values) {
-            if (&v != &this->values.front()) ss << ",";
-            ss << v << "%";
+        for (const double &v : this->values) {
+            if (&v != &this->values.front())
+            	ss << ",";
+			this->serialize_single_value(ss, v);
+			if (! std::isnan(v))
+				ss << "%";
         }
         std::string str = ss.str();
         return str;
     }
-    
+
     std::vector<std::string> vserialize() const override
     {
         std::vector<std::string> vv;
         vv.reserve(this->values.size());
-        for (const auto v : this->values) {
+        for (const double v : this->values) {
             std::ostringstream ss;
-            ss << v;
-            std::string sout = ss.str() + "%";
-            vv.push_back(sout);
+			this->serialize_single_value(ss, v);
+			if (! std::isnan(v))
+				ss << "%";
+            vv.push_back(ss.str());
         }
         return vv;
     }
 
-    bool deserialize(const std::string &str, bool append = false) override
-    {
-        if (! append)
-            this->values.clear();
-        std::istringstream is(str);
-        std::string item_str;
-        while (std::getline(is, item_str, ',')) {
-            std::istringstream iss(item_str);
-            double value;
-            // don't try to parse the trailing % since it's optional
-            iss >> value;
-            this->values.push_back(value);
-        }
-        return true;
-    }
+    // The float's deserialize function shall ignore the trailing optional %.
+    // bool deserialize(const std::string &str, bool append = false) override;
 
 private:
 	friend class cereal::access;
-	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionFloats>(this)); }
+	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionFloatsTempl<NULLABLE>>(this)); }
 };
 
+using ConfigOptionPercents 	   		= ConfigOptionPercentsTempl<false>;
+using ConfigOptionPercentsNullable 	= ConfigOptionPercentsTempl<true>;
+
 class ConfigOptionFloatOrPercent : public ConfigOptionPercent
 {
 public:
@@ -887,18 +953,28 @@ private:
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionSingle<bool>>(this)); }
 };
 
-class ConfigOptionBools : public ConfigOptionVector<unsigned char>
+template<bool NULLABLE>
+class ConfigOptionBoolsTempl : public ConfigOptionVector<unsigned char>
 {
 public:
-    ConfigOptionBools() : ConfigOptionVector<unsigned char>() {}
-    explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {}
-    explicit ConfigOptionBools(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); }
+    ConfigOptionBoolsTempl() : ConfigOptionVector<unsigned char>() {}
+    explicit ConfigOptionBoolsTempl(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {}
+    explicit ConfigOptionBoolsTempl(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); }
+	explicit ConfigOptionBoolsTempl(const std::vector<unsigned char>& vec) : ConfigOptionVector<unsigned char>(vec) {}
+	explicit ConfigOptionBoolsTempl(std::vector<unsigned char>&& vec) : ConfigOptionVector<unsigned char>(std::move(vec)) {}
 
     static ConfigOptionType static_type() { return coBools; }
     ConfigOptionType        type()  const override { return static_type(); }
-    ConfigOption*           clone() const override { return new ConfigOptionBools(*this); }
-    ConfigOptionBools&      operator=(const ConfigOption *opt) { this->set(opt); return *this; }
-    bool                    operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; }
+    ConfigOption*           clone() const override { return new ConfigOptionBoolsTempl(*this); }
+    ConfigOptionBoolsTempl& operator=(const ConfigOption *opt) { this->set(opt); return *this; }
+    bool                    operator==(const ConfigOptionBoolsTempl &rhs) const { return this->values == rhs.values; }
+    // Could a special "nil" value be stored inside the vector, indicating undefined value?
+    bool 					nullable() const override { return NULLABLE; }
+    // Special "nil" value to be stored into the vector if this->supports_nil().
+    static unsigned char	nil_value() { return std::numeric_limits<unsigned char>::max(); }
+    // A scalar is nil, or all values of a vector are nil.
+    virtual bool 			is_nil() const override { for (auto v : this->values) if (v != nil_value()) return false; return true; }
+    bool 					is_nil(size_t idx) const { return v->values[idx] == nil_value(); }
 
     bool& get_at(size_t i) {
         assert(! this->values.empty());
@@ -911,19 +987,20 @@ public:
     std::string serialize() const override
     {
         std::ostringstream ss;
-        for (std::vector<unsigned char>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
-            if (it - this->values.begin() != 0) ss << ",";
-            ss << (*it ? "1" : "0");
-        }
+        for (const unsigned char &v : this->values) {
+            if (&v != &this->values.front())
+            	ss << ",";
+			this->serialize_single_value(ss, v);
+		}
         return ss.str();
     }
     
     std::vector<std::string> vserialize() const override
     {
         std::vector<std::string> vv;
-        for (std::vector<unsigned char>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
-            std::ostringstream ss;
-            ss << (*it ? "1" : "0");
+        for (const unsigned char v : this->values) {
+			std::ostringstream ss;
+			this->serialize_single_value(ss, v);
             vv.push_back(ss.str());
         }
         return vv;
@@ -936,16 +1013,37 @@ public:
         std::istringstream is(str);
         std::string item_str;
         while (std::getline(is, item_str, ',')) {
-            this->values.push_back(item_str.compare("1") == 0);
+        	boost::trim(item_str);
+        	if (item_str == "nil") {
+        		if (NULLABLE)
+        			this->values.push_back(nil_value());
+        		else
+        			std::runtime_error("Deserializing nil into a non-nullable object");
+        	} else
+        		this->values.push_back(item_str.compare("1") == 0);	
         }
         return true;
     }
 
+protected:
+	void serialize_single_value(std::ostringstream &ss, const unsigned char v) const {
+        	if (v == nil_value()) {
+        		if (NULLABLE)
+        			ss << "nil";
+        		else
+        			std::runtime_error("Serializing NaN");
+        	} else
+        		ss << (v ? "1" : "0");
+	}
+
 private:
 	friend class cereal::access;
 	template<class Archive> void serialize(Archive &ar) { ar(cereal::base_class<ConfigOptionVector<unsigned char>>(this)); }
 };
 
+using ConfigOptionBools    	    = ConfigOptionBoolsTempl<false>;
+using ConfigOptionBoolsNullable = ConfigOptionBoolsTempl<true>;
+
 // Map from an enum integer value to an enum name.
 typedef std::vector<std::string>  t_config_enum_names;
 // Map from an enum name to an enum integer value.
@@ -1096,6 +1194,8 @@ public:
 	t_config_option_key 				opt_key;
     // What type? bool, int, string etc.
     ConfigOptionType                    type            = coNone;
+	// If a type is nullable, then it accepts a "nil" value (scalar) or "nil" values (vector).
+	bool								nullable		= false;
     // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor.
     Slic3r::clonable_ptr<const ConfigOption> default_value;
     void 								set_default_value(const ConfigOption* ptr) { this->default_value = Slic3r::clonable_ptr<const ConfigOption>(ptr); }
@@ -1107,45 +1207,65 @@ public:
     ConfigOption*						create_default_option() const;
 
     template<class Archive> ConfigOption* load_option_from_archive(Archive &archive) const {
-	    switch (this->type) {
-	    case coFloat:           { auto opt = new ConfigOptionFloat();  			archive(*opt); return opt; }
-	    case coFloats:          { auto opt = new ConfigOptionFloats(); 			archive(*opt); return opt; }
-	    case coInt:             { auto opt = new ConfigOptionInt();    			archive(*opt); return opt; }
-	    case coInts:            { auto opt = new ConfigOptionInts();   			archive(*opt); return opt; }
-	    case coString:          { auto opt = new ConfigOptionString(); 			archive(*opt); return opt; }
-	    case coStrings:         { auto opt = new ConfigOptionStrings(); 		archive(*opt); return opt; }
-	    case coPercent:         { auto opt = new ConfigOptionPercent(); 		archive(*opt); return opt; }
-	    case coPercents:        { auto opt = new ConfigOptionPercents(); 		archive(*opt); return opt; }
-	    case coFloatOrPercent:  { auto opt = new ConfigOptionFloatOrPercent(); 	archive(*opt); return opt; }
-	    case coPoint:           { auto opt = new ConfigOptionPoint(); 			archive(*opt); return opt; }
-	    case coPoints:          { auto opt = new ConfigOptionPoints(); 			archive(*opt); return opt; }
-	    case coPoint3:          { auto opt = new ConfigOptionPoint3(); 			archive(*opt); return opt; }
-	    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 std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
-	    }
+    	if (this->nullable) {
+		    switch (this->type) {
+		    case coFloats:          { auto opt = new ConfigOptionFloatsNullable();	archive(*opt); return opt; }
+		    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 std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
+		    }
+    	} else {
+		    switch (this->type) {
+		    case coFloat:           { auto opt = new ConfigOptionFloat();  			archive(*opt); return opt; }
+		    case coFloats:          { auto opt = new ConfigOptionFloats(); 			archive(*opt); return opt; }
+		    case coInt:             { auto opt = new ConfigOptionInt();    			archive(*opt); return opt; }
+		    case coInts:            { auto opt = new ConfigOptionInts();   			archive(*opt); return opt; }
+		    case coString:          { auto opt = new ConfigOptionString(); 			archive(*opt); return opt; }
+		    case coStrings:         { auto opt = new ConfigOptionStrings(); 		archive(*opt); return opt; }
+		    case coPercent:         { auto opt = new ConfigOptionPercent(); 		archive(*opt); return opt; }
+		    case coPercents:        { auto opt = new ConfigOptionPercents(); 		archive(*opt); return opt; }
+		    case coFloatOrPercent:  { auto opt = new ConfigOptionFloatOrPercent(); 	archive(*opt); return opt; }
+		    case coPoint:           { auto opt = new ConfigOptionPoint(); 			archive(*opt); return opt; }
+		    case coPoints:          { auto opt = new ConfigOptionPoints(); 			archive(*opt); return opt; }
+		    case coPoint3:          { auto opt = new ConfigOptionPoint3(); 			archive(*opt); return opt; }
+		    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 std::runtime_error(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
+		    }
+		}
 	}
 
     template<class Archive> ConfigOption* save_option_to_archive(Archive &archive, const ConfigOption *opt) const {
-	    switch (this->type) {
-	    case coFloat:           archive(*static_cast<const ConfigOptionFloat*>(opt));  			break;
-	    case coFloats:          archive(*static_cast<const ConfigOptionFloats*>(opt)); 			break;
-	    case coInt:             archive(*static_cast<const ConfigOptionInt*>(opt)); 	 		break;
-	    case coInts:            archive(*static_cast<const ConfigOptionInts*>(opt)); 	 		break;
-	    case coString:          archive(*static_cast<const ConfigOptionString*>(opt)); 			break;
-	    case coStrings:         archive(*static_cast<const ConfigOptionStrings*>(opt)); 		break;
-	    case coPercent:         archive(*static_cast<const ConfigOptionPercent*>(opt)); 		break;
-	    case coPercents:        archive(*static_cast<const ConfigOptionPercents*>(opt)); 		break;
-	    case coFloatOrPercent:  archive(*static_cast<const ConfigOptionFloatOrPercent*>(opt));	break;
-	    case coPoint:           archive(*static_cast<const ConfigOptionPoint*>(opt)); 			break;
-	    case coPoints:          archive(*static_cast<const ConfigOptionPoints*>(opt)); 			break;
-	    case coPoint3:          archive(*static_cast<const ConfigOptionPoint3*>(opt)); 			break;
-	    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 std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
-	    }
+    	if (this->nullable) {
+		    switch (this->type) {
+		    case coFloats:          archive(*static_cast<const ConfigOptionFloatsNullable*>(opt));  break;
+		    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 std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
+		    }
+		} else {
+		    switch (this->type) {
+		    case coFloat:           archive(*static_cast<const ConfigOptionFloat*>(opt));  			break;
+		    case coFloats:          archive(*static_cast<const ConfigOptionFloats*>(opt)); 			break;
+		    case coInt:             archive(*static_cast<const ConfigOptionInt*>(opt)); 	 		break;
+		    case coInts:            archive(*static_cast<const ConfigOptionInts*>(opt)); 	 		break;
+		    case coString:          archive(*static_cast<const ConfigOptionString*>(opt)); 			break;
+		    case coStrings:         archive(*static_cast<const ConfigOptionStrings*>(opt)); 		break;
+		    case coPercent:         archive(*static_cast<const ConfigOptionPercent*>(opt)); 		break;
+		    case coPercents:        archive(*static_cast<const ConfigOptionPercents*>(opt)); 		break;
+		    case coFloatOrPercent:  archive(*static_cast<const ConfigOptionFloatOrPercent*>(opt));	break;
+		    case coPoint:           archive(*static_cast<const ConfigOptionPoint*>(opt)); 			break;
+		    case coPoints:          archive(*static_cast<const ConfigOptionPoints*>(opt)); 			break;
+		    case coPoint3:          archive(*static_cast<const ConfigOptionPoint3*>(opt)); 			break;
+		    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 std::runtime_error(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
+		    }
+		}
 		// Make the compiler happy, shut up the warnings.
 		return nullptr;
 	}
@@ -1263,6 +1383,7 @@ public:
 
 protected:
     ConfigOptionDef*        add(const t_config_option_key &opt_key, ConfigOptionType type);
+    ConfigOptionDef*        add_nullable(const t_config_option_key &opt_key, ConfigOptionType type);
 };
 
 // An abstract configuration store.
@@ -1347,6 +1468,9 @@ public:
     void load(const boost::property_tree::ptree &tree);
     void save(const std::string &file) const;
 
+	// Set all the nullable values to nils.
+    void null_nullables();
+
 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);
@@ -1444,6 +1568,9 @@ public:
         return true;
     }
 
+    // Remove options with all nil values, those are optional and it does not help to hold them.
+    size_t remove_nil_options();
+
     // Allow DynamicConfig to be instantiated on ints own without a definition.
     // If the definition is not defined, the method requiring the definition will throw NoDefinitionException.
     const ConfigDef*        def() const override { return nullptr; };

+ 1 - 41
src/libslic3r/Print.cpp

@@ -1783,47 +1783,7 @@ std::string Print::output_filename(const std::string &filename_base) const
     DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders();
     return this->PrintBase::output_filename(m_config.output_filename_format.value, ".gcode", filename_base, &config);
 }
-/*
-// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes
-// and removing spaces.
-static std::string short_time(const std::string &time)
-{
-    // Parse the dhms time format.
-    int days    = 0;
-    int hours   = 0;
-    int minutes = 0;
-    int seconds = 0;
-    if (time.find('d') != std::string::npos)
-        ::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
-    else if (time.find('h') != std::string::npos)
-        ::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
-    else if (time.find('m') != std::string::npos)
-        ::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
-    else if (time.find('s') != std::string::npos)
-        ::sscanf(time.c_str(), "%ds", &seconds);
-    // Round to full minutes.
-    if (days + hours + minutes > 0 && seconds >= 30) {
-        if (++ minutes == 60) {
-            minutes = 0;
-            if (++ hours == 24) {
-                hours = 0;
-                ++ days;
-            }
-        }
-    }
-    // Format the dhm time.
-    char buffer[64];
-    if (days > 0)
-        ::sprintf(buffer, "%dd%dh%dm", days, hours, minutes);
-    else if (hours > 0)
-        ::sprintf(buffer, "%dh%dm", hours, minutes);
-    else if (minutes > 0)
-        ::sprintf(buffer, "%dm", minutes);
-    else
-        ::sprintf(buffer, "%ds", seconds);
-    return buffer;
-}
-*/
+
 DynamicConfig PrintStatistics::config() const
 {
     DynamicConfig config;

+ 26 - 2
src/libslic3r/PrintConfig.cpp

@@ -2228,6 +2228,30 @@ void PrintConfigDef::init_fff_params()
     def->sidetext = L("mm");
     def->mode = comAdvanced;
     def->set_default_value(new ConfigOptionFloat(0));
+
+    // Declare retract values for filament profile, overriding the printer's extruder profile.
+    for (const char *opt_key : {
+    	// floats
+		"retract_length", "retract_lift", "retract_lift_above", "retract_lift_below", "retract_speed", "deretract_speed", "retract_restart_extra", "retract_before_travel", 
+		// bools
+		"retract_layer_change", "wipe",
+		// percents
+		"retract_before_wipe"}) {
+    	auto it_opt = options.find(opt_key);
+    	assert(it_opt != options.end());
+    	def = this->add_nullable(std::string("filament_") + opt_key, it_opt->second.type);
+    	def->label 		= it_opt->second.label;
+    	def->full_label = it_opt->second.full_label;
+    	def->tooltip 	= it_opt->second.tooltip;
+    	def->sidetext   = it_opt->second.sidetext;
+    	def->mode       = it_opt->second.mode;
+    	switch (def->type) {
+    	case coFloats   : def->set_default_value(new ConfigOptionFloatsNullable  (static_cast<const ConfigOptionFloats*  >(it_opt->second.default_value.get())->values)); break;
+    	case coPercents : def->set_default_value(new ConfigOptionPercentsNullable(static_cast<const ConfigOptionPercents*>(it_opt->second.default_value.get())->values)); break;
+    	case coBools    : def->set_default_value(new ConfigOptionBoolsNullable   (static_cast<const ConfigOptionBools*   >(it_opt->second.default_value.get())->values)); break;
+    	default: assert(false);
+    	}
+    }
 }
 
 void PrintConfigDef::init_sla_params()
@@ -2987,7 +3011,7 @@ std::string FullPrintConfig::validate()
         }
         case coFloats:
         case coPercents:
-            for (double v : static_cast<const ConfigOptionFloats*>(opt)->values)
+            for (double v : static_cast<const ConfigOptionVector<double>*>(opt)->values)
                 if (v < optdef->min || v > optdef->max) {
                     out_of_range = true;
                     break;
@@ -3000,7 +3024,7 @@ std::string FullPrintConfig::validate()
             break;
         }
         case coInts:
-            for (int v : static_cast<const ConfigOptionInts*>(opt)->values)
+            for (int v : static_cast<const ConfigOptionVector<int>*>(opt)->values)
                 if (v < optdef->min || v > optdef->max) {
                     out_of_range = true;
                     break;

+ 4 - 0
src/slic3r/GUI/Preset.cpp

@@ -400,6 +400,10 @@ const std::vector<std::string>& Preset::filament_options()
         "temperature", "first_layer_temperature", "bed_temperature", "first_layer_bed_temperature", "fan_always_on", "cooling", "min_fan_speed",
         "max_fan_speed", "bridge_fan_speed", "disable_fan_first_layers", "fan_below_layer_time", "slowdown_below_layer_time", "min_print_speed",
         "start_filament_gcode", "end_filament_gcode",
+        // Retract overrides
+		"filament_retract_length", "filament_retract_lift", "filament_retract_lift_above", "filament_retract_lift_below", "filament_retract_speed", "filament_deretract_speed", "filament_retract_restart_extra", "filament_retract_before_travel", 
+		"filament_retract_layer_change", "filament_wipe", "filament_retract_before_wipe",
+		// Profile compatibility
         "compatible_prints", "compatible_prints_condition", "compatible_printers", "compatible_printers_condition", "inherits"
     };
     return s_opts;

+ 2 - 0
src/slic3r/GUI/PresetBundle.cpp

@@ -72,6 +72,8 @@ PresetBundle::PresetBundle() :
     this->filaments.default_preset().config.option<ConfigOptionStrings>("filament_settings_id", true)->values = { "" };
     this->filaments.default_preset().compatible_printers_condition();
     this->filaments.default_preset().inherits();
+	// Set all the nullable values to nils.
+	this->filaments.default_preset().config.null_nullables();
 
     this->sla_materials.default_preset().config.optptr("sla_material_settings_id", true);
     this->sla_materials.default_preset().compatible_printers_condition();