diff options
author | YuSanka <yusanka@gmail.com> | 2019-07-31 14:05:47 +0300 |
---|---|---|
committer | YuSanka <yusanka@gmail.com> | 2019-07-31 14:05:47 +0300 |
commit | 3bade450b8a4d1189138bb3cbed325a479fe6b94 (patch) | |
tree | ce2cbe63accbf59226ec785790e83130b640504a /src | |
parent | aeb29b11849e2e3e9a0282c289a9a7d165bfdbc8 (diff) | |
parent | 7c2e199472524c40bbd3f50650c2a0c7345bd4dd (diff) |
Merge remote-tracking branch 'origin/ys_overrides'
Diffstat (limited to 'src')
-rw-r--r-- | src/boost/nowide/utf8_codecvt.hpp | 2 | ||||
-rw-r--r-- | src/libslic3r/Config.cpp | 87 | ||||
-rw-r--r-- | src/libslic3r/Config.hpp | 459 | ||||
-rw-r--r-- | src/libslic3r/GCode.cpp | 23 | ||||
-rw-r--r-- | src/libslic3r/PlaceholderParser.cpp | 28 | ||||
-rw-r--r-- | src/libslic3r/PlaceholderParser.hpp | 9 | ||||
-rw-r--r-- | src/libslic3r/Print.cpp | 166 | ||||
-rw-r--r-- | src/libslic3r/Print.hpp | 9 | ||||
-rw-r--r-- | src/libslic3r/PrintBase.hpp | 10 | ||||
-rw-r--r-- | src/libslic3r/PrintConfig.cpp | 47 | ||||
-rw-r--r-- | src/libslic3r/PrintConfig.hpp | 8 | ||||
-rw-r--r-- | src/libslic3r/SLAPrint.cpp | 17 | ||||
-rw-r--r-- | src/libslic3r/SLAPrint.hpp | 2 | ||||
-rw-r--r-- | src/slic3r/GUI/Field.cpp | 123 | ||||
-rw-r--r-- | src/slic3r/GUI/Field.hpp | 22 | ||||
-rw-r--r-- | src/slic3r/GUI/GUI.cpp | 7 | ||||
-rw-r--r-- | src/slic3r/GUI/OptionsGroup.cpp | 54 | ||||
-rw-r--r-- | src/slic3r/GUI/OptionsGroup.hpp | 3 | ||||
-rw-r--r-- | src/slic3r/GUI/Preset.cpp | 4 | ||||
-rw-r--r-- | src/slic3r/GUI/PresetBundle.cpp | 2 | ||||
-rw-r--r-- | src/slic3r/GUI/Tab.cpp | 111 | ||||
-rw-r--r-- | src/slic3r/GUI/Tab.hpp | 5 |
22 files changed, 873 insertions, 325 deletions
diff --git a/src/boost/nowide/utf8_codecvt.hpp b/src/boost/nowide/utf8_codecvt.hpp index cc5046fc8..877c9f0e0 100644 --- a/src/boost/nowide/utf8_codecvt.hpp +++ b/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 } diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 9d0649a1f..577698071 100644 --- a/src/libslic3r/Config.cpp +++ b/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) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 7d96f6cf3..2850f1cb9 100644 --- a/src/libslic3r/Config.hpp +++ b/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,23 @@ 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; } + // Is this option overridden by another option? + // An option overrides another option if it is not nil and not equal. + virtual bool overriden_by(const ConfigOption *rhs) const { + assert(! this->nullable() && ! rhs->nullable()); + return *this != *rhs; + } + // Apply an override option, possibly a nullable one. + virtual bool apply_override(const ConfigOption *rhs) { + if (*this == *rhs) + return false; + *this = *rhs; + return true; + } }; typedef ConfigOption* ConfigOptionPtr; @@ -183,6 +201,8 @@ public: virtual size_t size() const = 0; // Is this vector empty? virtual bool empty() const = 0; + // Is the value nil? That should only be possible if this->nullable(). + virtual bool is_nil(size_t idx) const = 0; protected: // Used to verify type compatibility when assigning to / from a scalar ConfigOption. @@ -302,6 +322,62 @@ public: bool operator==(const std::vector<T> &rhs) const { return this->values == rhs; } bool operator!=(const std::vector<T> &rhs) const { return this->values != rhs; } + // Is this option overridden by another option? + // 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 std::runtime_error("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw std::runtime_error("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. + return this->values != rhs_vec->values; + size_t i = 0; + size_t cnt = std::min(this->size(), rhs_vec->size()); + for (; i < cnt; ++ i) + if (! rhs_vec->is_nil(i) && this->values[i] != rhs_vec->values[i]) + return true; + for (; i < rhs_vec->size(); ++ i) + if (! rhs_vec->is_nil(i)) + return true; + return false; + } + // Apply an override option, possibly a nullable one. + bool apply_override(const ConfigOption *rhs) override { + if (this->nullable()) + throw std::runtime_error("Cannot override a nullable ConfigOption."); + if (rhs->type() != this->type()) + throw std::runtime_error("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. + if (this->values != rhs_vec->values) { + this->values = rhs_vec->values; + return true; + } + return false; + } + size_t i = 0; + size_t cnt = std::min(this->size(), rhs_vec->size()); + bool modified = false; + for (; i < cnt; ++ i) + if (! rhs_vec->is_nil(i) && this->values[i] != rhs_vec->values[i]) { + this->values[i] = rhs_vec->values[i]; + modified = true; + } + for (; i < rhs_vec->size(); ++ i) + if (! rhs_vec->is_nil(i)) { + if (this->values.empty()) + this->values.resize(i + 1); + else + this->values.resize(i + 1, this->values.front()); + this->values[i] = rhs_vec->values[i]; + modified = true; + } + return false; + } + private: friend class cereal::access; template<class Archive> void serialize(Archive & ar) { ar(this->values); } @@ -345,26 +421,41 @@ 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 vectors_equal(this->values, rhs.values); } + bool operator==(const ConfigOption &rhs) const override { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionFloatsTempl: Comparing incompatible types"); + assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs)); + return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&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. + 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 override { return std::isnan(this->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 +464,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 +479,61 @@ 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"); + } + static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) { + if (NULLABLE) { + if (v1.size() != v2.size()) + return false; + for (auto it1 = v1.begin(), it2 = v2.begin(); it1 != v1.end(); ++ it1, ++ it2) + if (! ((std::isnan(*it1) && std::isnan(*it2)) || *it1 == *it2)) + return false; + return true; + } else + // Not supporting nullable values, the default vector compare is cheaper. + return v1 == v2; + } + 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 +574,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. + 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 override { return this->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 +625,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: @@ -544,6 +702,7 @@ public: ConfigOption* clone() const override { return new ConfigOptionStrings(*this); } ConfigOptionStrings& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionStrings &rhs) const { return this->values == rhs.values; } + bool is_nil(size_t idx) const override { return false; } std::string serialize() const override { @@ -603,64 +762,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: @@ -761,6 +917,7 @@ public: ConfigOption* clone() const override { return new ConfigOptionPoints(*this); } ConfigOptionPoints& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPoints &rhs) const { return this->values == rhs.values; } + bool is_nil(size_t idx) const override { return false; } std::string serialize() const override { @@ -887,18 +1044,29 @@ 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(std::initializer_list<unsigned char> il) { values.reserve(il.size()); for (unsigned char b : il) values.emplace_back(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. + 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 override { return this->values[idx] == nil_value(); } bool& get_at(size_t i) { assert(! this->values.empty()); @@ -911,19 +1079,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 +1105,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 +1286,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 +1299,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 +1475,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 +1560,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,9 +1660,12 @@ 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; }; + const ConfigDef* def() const override { return nullptr; } template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast<T*>(this->option(opt_key, create)); } template<class T> const T* opt(const t_config_option_key &opt_key) const diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 81880831f..bc3730026 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1829,25 +1829,12 @@ void GCode::apply_print_config(const PrintConfig &print_config) m_config.apply(print_config); } -void GCode::append_full_config(const Print& print, std::string& str) +void GCode::append_full_config(const Print &print, std::string &str) { - const StaticPrintConfig *configs[] = { static_cast<const GCodeConfig*>(&print.config()), &print.default_object_config(), &print.default_region_config() }; - for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++i) { - const StaticPrintConfig *cfg = configs[i]; - for (const std::string &key : cfg->keys()) - if (key != "compatible_printers") - str += "; " + key + " = " + cfg->opt_serialize(key) + "\n"; - } - const DynamicConfig &full_config = print.placeholder_parser().config(); - for (const char *key : { - "print_settings_id", "filament_settings_id", "sla_print_settings_id", "sla_material_settings_id", "printer_settings_id", - "printer_model", "printer_variant", - "default_print_profile", "default_filament_profile", "default_sla_print_profile", "default_sla_material_profile", - "compatible_prints_condition_cummulative", "compatible_printers_condition_cummulative", "inherits_cummulative" }) { - const ConfigOption *opt = full_config.option(key); - if (opt != nullptr) - str += std::string("; ") + key + " = " + opt->serialize() + "\n"; - } + const DynamicPrintConfig &cfg = print.full_print_config(); + for (const std::string &key : cfg.keys()) + if (key != "compatible_prints" && key != "compatible_printers" && ! cfg.option(key)->is_nil()) + str += "; " + key + " = " + cfg.opt_serialize(key) + "\n"; } void GCode::set_extruders(const std::vector<unsigned int> &extruder_ids) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 2bfe9b745..c3ac22e96 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -62,7 +62,7 @@ namespace Slic3r { -PlaceholderParser::PlaceholderParser() +PlaceholderParser::PlaceholderParser(const DynamicConfig *external_config) : m_external_config(external_config) { this->set("version", std::string(SLIC3R_VERSION)); this->apply_env_variables(); @@ -94,14 +94,6 @@ void PlaceholderParser::update_timestamp(DynamicConfig &config) config.set_key_value("second", new ConfigOptionInt(timeinfo->tm_sec)); } -// Ignore this key by the placeholder parser. -static inline bool placeholder_parser_ignore(const ConfigDef *def, const std::string &opt_key) -{ - const ConfigOptionDef *opt_def = def->get(opt_key); - assert(opt_def != nullptr); - return (opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process"; -} - static inline bool opts_equal(const DynamicConfig &config_old, const DynamicConfig &config_new, const std::string &opt_key) { const ConfigOption *opt_old = config_old.option(opt_key); @@ -119,7 +111,7 @@ std::vector<std::string> PlaceholderParser::config_diff(const DynamicPrintConfig const ConfigDef *def = rhs.def(); std::vector<std::string> diff_keys; for (const t_config_option_key &opt_key : rhs.keys()) - if (! placeholder_parser_ignore(def, opt_key) && ! opts_equal(m_config, rhs, opt_key)) + if (! opts_equal(m_config, rhs, opt_key)) diff_keys.emplace_back(opt_key); return diff_keys; } @@ -135,8 +127,6 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) const ConfigDef *def = rhs.def(); bool modified = false; for (const t_config_option_key &opt_key : rhs.keys()) { - if (placeholder_parser_ignore(def, opt_key)) - continue; if (! opts_equal(m_config, rhs, opt_key)) { // Store a copy of the config option. // Convert FloatOrPercent values to floats first. @@ -155,7 +145,6 @@ bool PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vector<std::string> &keys) { for (const t_config_option_key &opt_key : keys) { - assert(! placeholder_parser_ignore(rhs.def(), opt_key)); // Store a copy of the config option. // Convert FloatOrPercent values to floats first. //FIXME there are some ratio_over chains, which end with empty ratio_with. @@ -167,6 +156,11 @@ void PlaceholderParser::apply_only(const DynamicPrintConfig &rhs, const std::vec } } +void PlaceholderParser::apply_config(DynamicPrintConfig &&rhs) +{ + m_config += std::move(rhs); +} + void PlaceholderParser::apply_env_variables() { for (char** env = environ; *env; ++ env) { @@ -608,6 +602,7 @@ namespace client } struct MyContext { + const DynamicConfig *external_config = nullptr; const DynamicConfig *config = nullptr; const DynamicConfig *config_override = nullptr; size_t current_extruder_id = 0; @@ -628,6 +623,8 @@ namespace client opt = config_override->option(opt_key); if (opt == nullptr) opt = config->option(opt_key); + if (opt == nullptr && external_config != nullptr) + opt = external_config->option(opt_key); return opt; } @@ -1255,6 +1252,7 @@ static std::string process_macro(const std::string &templ, client::MyContext &co std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const { client::MyContext context; + context.external_config = this->external_config(); context.config = &this->config(); context.config_override = config_override; context.current_extruder_id = current_extruder_id; @@ -1266,8 +1264,8 @@ std::string PlaceholderParser::process(const std::string &templ, unsigned int cu bool PlaceholderParser::evaluate_boolean_expression(const std::string &templ, const DynamicConfig &config, const DynamicConfig *config_override) { client::MyContext context; - context.config = &config; - context.config_override = config_override; + context.config = &config; + context.config_override = config_override; // Let the macro processor parse just a boolean expression, not the full macro language. context.just_boolean_expression = true; return process_macro(templ, context) == "true"; diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index 22c790e6b..14fdc5c28 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -12,13 +12,14 @@ namespace Slic3r { class PlaceholderParser { public: - PlaceholderParser(); + PlaceholderParser(const DynamicConfig *external_config = nullptr); // Return a list of keys, which should be changed in m_config from rhs. // This contains keys, which are found in rhs, but not in m_config. std::vector<std::string> config_diff(const DynamicPrintConfig &rhs); // Return true if modified. bool apply_config(const DynamicPrintConfig &config); + void apply_config(DynamicPrintConfig &&config); // To be called on the values returned by PlaceholderParser::config_diff(). // The keys should already be valid. void apply_only(const DynamicPrintConfig &config, const std::vector<std::string> &keys); @@ -35,6 +36,8 @@ public: DynamicConfig& config_writable() { return m_config; } const DynamicConfig& config() const { return m_config; } const ConfigOption* option(const std::string &key) const { return m_config.option(key); } + // External config is not owned by PlaceholderParser. It has a lowest priority when looking up an option. + const DynamicConfig* external_config() const { return m_external_config; } // Fill in the template using a macro processing language. // Throws std::runtime_error on syntax or runtime error. @@ -50,7 +53,9 @@ public: void update_timestamp() { update_timestamp(m_config); } private: - DynamicConfig m_config; + // config has a higher priority than external_config when looking up a symbol. + DynamicConfig m_config; + const DynamicConfig *m_external_config; }; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 5702f49e3..c423afeb9 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -485,25 +485,82 @@ bool layer_height_ranges_equal(const t_layer_config_ranges &lr1, const t_layer_c return true; } -Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in) +// Collect diffs of configuration values at various containers, +// resolve the filament rectract overrides of extruder retract values. +void Print::config_diffs( + const DynamicPrintConfig &new_full_config, + t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, + t_config_option_keys &full_config_diff, + DynamicPrintConfig &placeholder_parser_overrides, + DynamicPrintConfig &filament_overrides) const +{ + // Collect changes to print config, account for overrides of extruder retract values by filament presets. + { + const std::vector<std::string> &extruder_retract_keys = print_config_def.extruder_retract_keys(); + const std::string filament_prefix = "filament_"; + for (const t_config_option_key &opt_key : m_config.keys()) { + const ConfigOption *opt_old = m_config.option(opt_key); + assert(opt_old != nullptr); + const ConfigOption *opt_new = new_full_config.option(opt_key); + // assert(opt_new != nullptr); + if (opt_new == nullptr) + //FIXME This may happen when executing some test cases. + continue; + const ConfigOption *opt_new_filament = std::binary_search(extruder_retract_keys.begin(), extruder_retract_keys.end(), opt_key) ? new_full_config.option(filament_prefix + opt_key) : nullptr; + if (opt_new_filament != nullptr && ! opt_new_filament->is_nil()) { + // An extruder retract override is available at some of the filament presets. + if (*opt_old != *opt_new || opt_new->overriden_by(opt_new_filament)) { + auto opt_copy = opt_new->clone(); + opt_copy->apply_override(opt_new_filament); + if (*opt_old == *opt_copy) + delete opt_copy; + else { + filament_overrides.set_key_value(opt_key, opt_copy); + print_diff.emplace_back(opt_key); + } + } + } else if (*opt_new != *opt_old) + print_diff.emplace_back(opt_key); + } + } + // Collect changes to object and region configs. + object_diff = m_default_object_config.diff(new_full_config); + region_diff = m_default_region_config.diff(new_full_config); + // Prepare for storing of the full print config into new_full_config to be exported into the G-code and to be used by the PlaceholderParser. + // As the PlaceholderParser does not interpret the FloatOrPercent values itself, these values are stored into the PlaceholderParser converted to floats. + for (const t_config_option_key &opt_key : new_full_config.keys()) { + const ConfigOption *opt_old = m_full_print_config.option(opt_key); + const ConfigOption *opt_new = new_full_config.option(opt_key); + if (opt_old == nullptr || *opt_new != *opt_old) + full_config_diff.emplace_back(opt_key); + if (opt_new->type() == coFloatOrPercent) { + // The m_placeholder_parser is never modified by the background processing, GCode.cpp/hpp makes a copy. + const ConfigOption *opt_old_pp = this->placeholder_parser().config().option(opt_key); + double new_value = new_full_config.get_abs_value(opt_key); + if (opt_old_pp == nullptr || static_cast<const ConfigOptionFloat*>(opt_old_pp)->value != new_value) + placeholder_parser_overrides.set_key_value(opt_key, new ConfigOptionFloat(new_value)); + } + } +} + +Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_config) { #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - // Make a copy of the config, normalize it. - DynamicPrintConfig config(config_in); - config.option("print_settings_id", true); - config.option("filament_settings_id", true); - config.option("printer_settings_id", true); - config.normalize(); - // Collect changes to print config. - t_config_option_keys print_diff = m_config.diff(config); - t_config_option_keys object_diff = m_default_object_config.diff(config); - t_config_option_keys region_diff = m_default_region_config.diff(config); - t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); - - // Do not use the ApplyStatus as we will use the max function when updating apply_status. + // Normalize the config. + new_full_config.option("print_settings_id", true); + new_full_config.option("filament_settings_id", true); + new_full_config.option("printer_settings_id", true); + new_full_config.normalize(); + + // Find modified keys of the various configs. Resolve overrides extruder retract values by filament profiles. + t_config_option_keys print_diff, object_diff, region_diff, full_config_diff; + DynamicPrintConfig placeholder_parser_overrides, filament_overrides; + this->config_diffs(new_full_config, print_diff, object_diff, region_diff, full_config_diff, placeholder_parser_overrides, filament_overrides); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; auto update_apply_status = [&apply_status](bool invalidated) { apply_status = std::max<unsigned int>(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; @@ -516,24 +573,25 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co // The following call may stop the background processing. if (! print_diff.empty()) update_apply_status(this->invalidate_state_by_config_options(print_diff)); + // Apply variables to placeholder parser. The placeholder parser is used by G-code export, // which should be stopped if print_diff is not empty. - if (! placeholder_parser_diff.empty()) { + if (! full_config_diff.empty() || ! placeholder_parser_overrides.empty()) { update_apply_status(this->invalidate_step(psGCodeExport)); - PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_only(config, placeholder_parser_diff); + m_placeholder_parser.apply_config(std::move(placeholder_parser_overrides)); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config.option("print_settings_id")->clone()); - pp.set("filament_preset", config.option("filament_settings_id")->clone()); - pp.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", new_full_config.option("print_settings_id")->clone()); + m_placeholder_parser.set("filament_preset", new_full_config.option("filament_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", new_full_config.option("printer_settings_id")->clone()); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_config.apply_only(new_full_config, print_diff, true); + m_config.apply(filament_overrides); + // Handle changes to object config defaults + m_default_object_config.apply_only(new_full_config, object_diff, true); + // Handle changes to regions config defaults + m_default_region_config.apply_only(new_full_config, region_diff, true); + m_full_print_config = std::move(new_full_config); } - - // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. - m_config.apply_only(config, print_diff, true); - // Handle changes to object config defaults - m_default_object_config.apply_only(config, object_diff, true); - // Handle changes to regions config defaults - m_default_region_config.apply_only(config, region_diff, true); class LayerRanges { @@ -545,9 +603,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co m_ranges.reserve(in.size()); // Input ranges are sorted lexicographically. First range trims the other ranges. coordf_t last_z = 0; - for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in) { -// for (auto &range : in) { - if (range.first.second > last_z) { + for (const std::pair<const t_layer_height_range, DynamicPrintConfig> &range : in) + if (range.first.second > last_z) { coordf_t min_z = std::max(range.first.first, 0.); if (min_z > last_z + EPSILON) { m_ranges.emplace_back(t_layer_height_range(last_z, min_z), nullptr); @@ -559,7 +616,6 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co last_z = range.first.second; } } - } if (m_ranges.empty()) m_ranges.emplace_back(t_layer_height_range(0, DBL_MAX), nullptr); else if (m_ranges.back().second == nullptr) @@ -1707,9 +1763,9 @@ void Print::_make_wipe_tower() (float)m_config.filament_cooling_initial_speed.get_at(i), (float)m_config.filament_cooling_final_speed.get_at(i), m_config.filament_ramming_parameters.get_at(i), - m_config.filament_max_volumetric_speed.get_at(i), - m_config.nozzle_diameter.get_at(i), - m_config.filament_diameter.get_at(i)); + (float)m_config.filament_max_volumetric_speed.get_at(i), + (float)m_config.nozzle_diameter.get_at(i), + (float)m_config.filament_diameter.get_at(i)); m_wipe_tower_data.priming = Slic3r::make_unique<std::vector<WipeTower::ToolChangeResult>>( wipe_tower.prime((float)this->skirt_first_layer_height(), m_wipe_tower_data.tool_ordering.all_extruders(), false)); @@ -1791,47 +1847,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; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 368bf4ee8..b6d7b678d 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -296,7 +296,7 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } - ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; void process() override; // Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file. @@ -368,6 +368,13 @@ protected: bool invalidate_step(PrintStep step); private: + void config_diffs( + const DynamicPrintConfig &new_full_config, + t_config_option_keys &print_diff, t_config_option_keys &object_diff, t_config_option_keys ®ion_diff, + t_config_option_keys &full_config_diff, + DynamicPrintConfig &placeholder_parser_overrides, + DynamicPrintConfig &filament_overrides) const; + bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys); void _make_skirt(); diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index d84d492a0..aebc87904 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -217,7 +217,7 @@ protected: class PrintBase { public: - PrintBase() { this->restart(); } + PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } inline virtual ~PrintBase() {} virtual PrinterTechnology technology() const noexcept = 0; @@ -240,7 +240,7 @@ public: // Some data was changed, which in turn invalidated already calculated steps. APPLY_STATUS_INVALIDATED, }; - virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0; + virtual ApplyStatus apply(const Model &model, DynamicPrintConfig config) = 0; const Model& model() const { return m_model; } struct TaskParams { @@ -316,7 +316,7 @@ public: virtual bool finished() const = 0; const PlaceholderParser& placeholder_parser() const { return m_placeholder_parser; } - PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + const DynamicPrintConfig& full_print_config() const { return m_full_print_config; } virtual std::string output_filename(const std::string &filename_base = std::string()) const = 0; // If the filename_base is set, it is used as the input for the template processing. In that case the path is expected to be the directory (may be empty). @@ -341,6 +341,8 @@ protected: void update_object_placeholders(DynamicConfig &config, const std::string &default_ext) const; Model m_model; + DynamicPrintConfig m_full_print_config; + PlaceholderParser m_placeholder_parser; private: tbb::atomic<CancelStatus> m_cancel_status; @@ -354,8 +356,6 @@ private: // The mutex will be used to guard the worker thread against entering a stage // while the data influencing the stage is modified. mutable tbb::mutex m_state_mutex; - - PlaceholderParser m_placeholder_parser; }; template<typename PrintStepEnum, const size_t COUNT> diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8e091fb80..304f6f749 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -29,6 +29,7 @@ PrintConfigDef::PrintConfigDef() this->init_common_params(); assign_printer_technology_to_unknown(this->options, ptAny); this->init_fff_params(); + this->init_extruder_retract_keys(); assign_printer_technology_to_unknown(this->options, ptFFF); this->init_sla_params(); assign_printer_technology_to_unknown(this->options, ptSLA); @@ -2238,6 +2239,48 @@ 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_extruder_retract_keys() +{ + m_extruder_retract_keys = { + "deretract_speed", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_speed", + "wipe" + }; + assert(std::is_sorted(m_extruder_retract_keys.begin(), m_extruder_retract_keys.end())); } void PrintConfigDef::init_sla_params() @@ -2997,7 +3040,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; @@ -3010,7 +3053,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; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5731bef00..f2d0775fa 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -185,10 +185,18 @@ public: static void handle_legacy(t_config_option_key &opt_key, std::string &value); + // Options defining the extruder retract properties. These keys are sorted lexicographically. + // The extruder retract keys could be overidden by the same values defined at the Filament level + // (then the key is further prefixed with the "filament_" prefix). + const std::vector<std::string>& extruder_retract_keys() const { return m_extruder_retract_keys; } + private: void init_common_params(); void init_fff_params(); + void init_extruder_retract_keys(); void init_sla_params(); + + std::vector<std::string> m_extruder_retract_keys; }; // The one and only global definition of SLic3r configuration options. diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 5669307ac..45f8a0c83 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -145,14 +145,13 @@ static std::vector<SLAPrintObject::Instance> sla_instances(const ModelObject &mo return instances; } -SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConfig &config_in) +SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, DynamicPrintConfig config) { #ifdef _DEBUG check_model_ids_validity(model); #endif /* _DEBUG */ - // Make a copy of the config, normalize it. - DynamicPrintConfig config(config_in); + // Normalize the config. config.option("sla_print_settings_id", true); config.option("sla_material_settings_id", true); config.option("printer_settings_id", true); @@ -162,7 +161,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf t_config_option_keys printer_diff = m_printer_config.diff(config); t_config_option_keys material_diff = m_material_config.diff(config); t_config_option_keys object_diff = m_default_object_config.diff(config); - t_config_option_keys placeholder_parser_diff = this->placeholder_parser().config_diff(config); + t_config_option_keys placeholder_parser_diff = m_placeholder_parser.config_diff(config); // Do not use the ApplyStatus as we will use the max function when updating apply_status. unsigned int apply_status = APPLY_STATUS_UNCHANGED; @@ -187,12 +186,11 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf // only to generate the output file name. if (! placeholder_parser_diff.empty()) { // update_apply_status(this->invalidate_step(slapsRasterize)); - PlaceholderParser &pp = this->placeholder_parser(); - pp.apply_config(config); + m_placeholder_parser.apply_config(config); // Set the profile aliases for the PrintBase::output_filename() - pp.set("print_preset", config.option("sla_print_settings_id")->clone()); - pp.set("material_preset", config.option("sla_material_settings_id")->clone()); - pp.set("printer_preset", config.option("printer_settings_id")->clone()); + m_placeholder_parser.set("print_preset", config.option("sla_print_settings_id")->clone()); + m_placeholder_parser.set("material_preset", config.option("sla_material_settings_id")->clone()); + m_placeholder_parser.set("printer_preset", config.option("printer_settings_id")->clone()); } // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. @@ -459,6 +457,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf check_model_ids_equal(m_model, model); #endif /* _DEBUG */ + m_full_print_config = std::move(config); return static_cast<ApplyStatus>(apply_status); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index c4e58ab39..e8cdac1b8 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -349,7 +349,7 @@ public: void clear() override; bool empty() const override { return m_objects.empty(); } - ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; + ApplyStatus apply(const Model &model, DynamicPrintConfig config) override; void set_task(const TaskParams ¶ms) override; void process() override; void finalize() override; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index db935cc05..39fa9c54b 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -136,6 +136,8 @@ bool Field::is_matched(const std::string& string, const std::string& pattern) return std::regex_match(string, regex_pattern); } +static wxString na_value() { return _(L("N/A")); } + void Field::get_value_by_opt_type(wxString& str) { switch (m_opt.type) { @@ -165,7 +167,9 @@ void Field::get_value_by_opt_type(wxString& str) val = 0.0; else { - if (!str.ToCDouble(&val)) + if (m_opt.nullable && str == na_value()) + val = ConfigOptionFloatsNullable::nil_value(); + else if (!str.ToCDouble(&val)) { show_error(m_parent, _(L("Invalid numeric input."))); set_value(double_to_string(val), true); @@ -256,6 +260,7 @@ void TextCtrl::BUILD() { m_opt.default_value->getFloat() : m_opt.get_default_value<ConfigOptionPercents>()->get_at(m_opt_idx); text_value = double_to_string(val); + m_last_meaningful_value = text_value; break; } case coString: @@ -325,24 +330,7 @@ void TextCtrl::BUILD() { } propagate_value(); }), temp->GetId()); - /* - temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt) - { -#ifdef __WXGTK__ - if (bChangedValueEvent) -#endif //__WXGTK__ - if(is_defined_input_value()) - on_change_field(); - }), temp->GetId()); -#ifdef __WXGTK__ - // to correct value updating on GTK we should: - // call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT - // and prevent value updating on wxEVT_KEY_DOWN - temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this); - temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this); -#endif //__WXGTK__ -*/ // select all text using Ctrl+A temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event) { @@ -355,14 +343,70 @@ void TextCtrl::BUILD() { window = dynamic_cast<wxWindow*>(temp); } +bool TextCtrl::value_was_changed() +{ + if (m_value.empty()) + return true; + + boost::any val = m_value; + wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue(); + // update m_value! + get_value_by_opt_type(ret_str); + + switch (m_opt.type) { + case coInt: + return boost::any_cast<int>(m_value) != boost::any_cast<int>(val); + case coPercent: + case coPercents: + case coFloats: + case coFloat: { + if (m_opt.nullable && std::isnan(boost::any_cast<double>(m_value)) && + std::isnan(boost::any_cast<double>(val))) + return false; + return boost::any_cast<double>(m_value) != boost::any_cast<double>(val); + } + case coString: + case coStrings: + case coFloatOrPercent: + return boost::any_cast<std::string>(m_value) != boost::any_cast<std::string>(val); + default: + return true; + } +} + void TextCtrl::propagate_value() { - if (is_defined_input_value<wxTextCtrl>(window, m_opt.type)) + if (is_defined_input_value<wxTextCtrl>(window, m_opt.type) && value_was_changed()) on_change_field(); else on_kill_focus(); } +void TextCtrl::set_value(const boost::any& value, bool change_event/* = false*/) { + m_disable_change_event = !change_event; + if (m_opt.nullable) { + const bool m_is_na_val = boost::any_cast<wxString>(value) == na_value(); + if (!m_is_na_val) + m_last_meaningful_value = value; + dynamic_cast<wxTextCtrl*>(window)->SetValue(m_is_na_val ? na_value() : boost::any_cast<wxString>(value)); + } + else + dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value)); + m_disable_change_event = false; +} + +void TextCtrl::set_last_meaningful_value() +{ + dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(m_last_meaningful_value)); + propagate_value(); +} + +void TextCtrl::set_na_value() +{ + dynamic_cast<wxTextCtrl*>(window)->SetValue(na_value()); + propagate_value(); +} + boost::any& TextCtrl::get_value() { wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue(); @@ -409,6 +453,8 @@ void CheckBox::BUILD() { m_opt.get_default_value<ConfigOptionBools>()->get_at(m_opt_idx) : false; + m_last_meaningful_value = static_cast<unsigned char>(check_value); + // Set Label as a string of at least one space simbol to correct system scaling of a CheckBox auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(" "), wxDefaultPosition, size); temp->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -416,7 +462,10 @@ void CheckBox::BUILD() { temp->SetValue(check_value); if (m_opt.readonly) temp->Disable(); - temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId()); + temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { + m_is_na_val = false; + on_change_field(); + }), temp->GetId()); temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false")); @@ -424,6 +473,38 @@ void CheckBox::BUILD() { window = dynamic_cast<wxWindow*>(temp); } +void CheckBox::set_value(const boost::any& value, bool change_event) +{ + m_disable_change_event = !change_event; + if (m_opt.nullable) { + m_is_na_val = boost::any_cast<unsigned char>(value) == ConfigOptionBoolsNullable::nil_value(); + if (!m_is_na_val) + m_last_meaningful_value = value; + dynamic_cast<wxCheckBox*>(window)->SetValue(m_is_na_val ? false : boost::any_cast<unsigned char>(value) != 0); + } + else + dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value)); + m_disable_change_event = false; +} + +void CheckBox::set_last_meaningful_value() +{ + if (m_opt.nullable) { + m_is_na_val = false; + dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<unsigned char>(m_last_meaningful_value) != 0); + on_change_field(); + } +} + +void CheckBox::set_na_value() +{ + if (m_opt.nullable) { + m_is_na_val = true; + dynamic_cast<wxCheckBox*>(window)->SetValue(false); + on_change_field(); + } +} + boost::any& CheckBox::get_value() { // boost::any m_value; @@ -431,7 +512,7 @@ boost::any& CheckBox::get_value() if (m_opt.type == coBool) m_value = static_cast<bool>(value); else - m_value = static_cast<unsigned char>(value); + m_value = m_is_na_val ? ConfigOptionBoolsNullable::nil_value() : static_cast<unsigned char>(value); return m_value; } diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index 990c40e6f..6c16f90f2 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -123,6 +123,8 @@ public: /// subclasses should overload with a specific version /// Postcondition: Method does not fire the on_change event. virtual void set_value(const boost::any& value, bool change_event) = 0; + virtual void set_last_meaningful_value() {} + virtual void set_na_value() {} /// Gets a boost::any representing this control. /// subclasses should overload with a specific version @@ -247,6 +249,8 @@ protected: // current value boost::any m_value; + // last maeningful value + boost::any m_last_meaningful_value; int m_em_unit; @@ -277,6 +281,7 @@ public: ~TextCtrl() {} void BUILD(); + bool value_was_changed(); // Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER void propagate_value(); wxWindow* window {nullptr}; @@ -286,11 +291,9 @@ public: dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value)); m_disable_change_event = false; } - virtual void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value)); - m_disable_change_event = false; - } + virtual void set_value(const boost::any& value, bool change_event = false) override; + virtual void set_last_meaningful_value() override; + virtual void set_na_value() override; boost::any& get_value() override; @@ -303,6 +306,7 @@ public: class CheckBox : public Field { using Field::Field; + bool m_is_na_val {false}; public: CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {} CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} @@ -316,11 +320,9 @@ public: dynamic_cast<wxCheckBox*>(window)->SetValue(value); m_disable_change_event = false; } - void set_value(const boost::any& value, bool change_event = false) { - m_disable_change_event = !change_event; - dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value)); - m_disable_change_event = false; - } + void set_value(const boost::any& value, bool change_event = false) override; + void set_last_meaningful_value() override; + void set_na_value() override; boost::any& get_value() override; void msw_rescale() override; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 8f41ed5a3..826f2d6fc 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -148,6 +148,13 @@ void config_wizard(int reason) void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/) { try{ + + if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) { + ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast<unsigned char>(value) }; + config.option<ConfigOptionBoolsNullable>(opt_key)->set_at(vec_new, opt_index, 0); + return; + } + switch (config.def()->get(opt_key)->type) { case coFloatOrPercent:{ std::string str = boost::any_cast<std::string>(value); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 9feca2f3d..656e07a0e 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -202,7 +202,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n // so we need a horizontal sizer to arrange these things auto sizer = new wxBoxSizer(wxHORIZONTAL); grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1); - sizer->Add(m_near_label_widget_ptrs.back(), 0, wxRIGHT, 7); + sizer->Add(m_near_label_widget_ptrs.back(), 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, 7); sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5); } } @@ -372,30 +372,10 @@ void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const b auto option = m_options.at(opt_id).opt; - // get value -//! auto field_value = get_value(opt_id); - if (option.gui_flags.compare("serialized")==0) { - if (opt_index != -1) { - // die "Can't set serialized option indexed value" ; - } - change_opt_value(*m_config, opt_key, value); - } - else { - if (opt_index == -1) { - // change_opt_value(*m_config, opt_key, field_value); - //!? why field_value?? in this case changed value will be lose! No? - change_opt_value(*m_config, opt_key, value); - } - else { - change_opt_value(*m_config, opt_key, value, opt_index); -// auto value = m_config->get($opt_key); -// $value->[$opt_index] = $field_value; -// $self->config->set($opt_key, $value); - } - } + change_opt_value(*m_config, opt_key, value, opt_index == -1 ? 0 : opt_index); } - OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this + OptionsGroup::on_change_OG(opt_id, value); } void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key) @@ -578,6 +558,34 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config boost::any ret; wxString text_value = wxString(""); const ConfigOptionDef* opt = config.def()->get(opt_key); + + if (opt->nullable) + { + switch (opt->type) + { + case coPercents: + case coFloats: { + if (config.option(opt_key)->is_nil()) + ret = _(L("N/A")); + else { + double val = opt->type == coFloats ? + config.option<ConfigOptionFloatsNullable>(opt_key)->get_at(idx) : + config.option<ConfigOptionPercentsNullable>(opt_key)->get_at(idx); + ret = double_to_string(val); } + } + break; + case coBools: + ret = config.option<ConfigOptionBoolsNullable>(opt_key)->values[idx]; + break; + case coInts: + ret = config.option<ConfigOptionIntsNullable>(opt_key)->get_at(idx); + break; + default: + break; + } + return ret; + } + switch (opt->type) { case coFloatOrPercent:{ const auto &value = *config.option<ConfigOptionFloatOrPercent>(opt_key); diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index d720787b6..cc3d89b1f 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -252,6 +252,9 @@ public: Option option = get_option(title, idx); return OptionsGroup::create_single_option_line(option); } + Line create_single_option_line(const Option& option) const { + return OptionsGroup::create_single_option_line(option); + } void append_single_option_line(const Option& option) { OptionsGroup::append_single_option_line(option); } diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index e34c9be28..fa8b5baee 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/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; diff --git a/src/slic3r/GUI/PresetBundle.cpp b/src/slic3r/GUI/PresetBundle.cpp index 00c1f8168..7c9f7af4e 100644 --- a/src/slic3r/GUI/PresetBundle.cpp +++ b/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(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a00f7902f..9d7fc20a3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1497,6 +1497,105 @@ void TabPrint::OnActivate() Tab::OnActivate(); } +void TabFilament::add_filament_overrides_page() +{ + PageShp page = add_options_page(_(L("Filament Overrides")), "wrench"); + ConfigOptionsGroupShp optgroup = page->new_optgroup(_(L("Retraction"))); + + auto append_single_option_line = [optgroup, this](const std::string& opt_key, int opt_index) + { + Line line {"",""}; + if (opt_key == "filament_retract_lift_above" || opt_key == "filament_retract_lift_below") { + Option opt = optgroup->get_option(opt_key); + opt.opt.label = opt.opt.full_label; + line = optgroup->create_single_option_line(opt); + } + else + line = optgroup->create_single_option_line(optgroup->get_option(opt_key)); + + line.near_label_widget = [this, optgroup, opt_key, opt_index](wxWindow* parent) { + wxCheckBox* check_box = new wxCheckBox(parent, wxID_ANY, ""); + + check_box->Bind(wxEVT_CHECKBOX, [this, optgroup, opt_key, opt_index](wxCommandEvent& evt) { + const bool is_checked = evt.IsChecked(); + Field* field = optgroup->get_fieldc(opt_key, opt_index); + if (field != nullptr) { + field->toggle(is_checked); + if (is_checked) + field->set_last_meaningful_value(); + else + field->set_na_value(); + } + }, check_box->GetId()); + + m_overrides_options[opt_key] = check_box; + return check_box; + }; + + optgroup->append_line(line); + }; + + const int extruder_idx = 0; // #ys_FIXME + + for (const std::string opt_key : { "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" + }) + append_single_option_line(opt_key, extruder_idx); +} + +void TabFilament::update_filament_overrides_page() +{ + const auto page_it = std::find_if(m_pages.begin(), m_pages.end(), [](const PageShp page) {return page->title() == _(L("Filament Overrides")); }); + if (page_it == m_pages.end()) + return; + PageShp page = *page_it; + + const auto og_it = std::find_if(page->m_optgroups.begin(), page->m_optgroups.end(), [](const ConfigOptionsGroupShp og) {return og->title == _(L("Retraction")); }); + if (og_it == page->m_optgroups.end()) + return; + ConfigOptionsGroupShp optgroup = *og_it; + + std::vector<std::string> opt_keys = { "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" + }; + + const int extruder_idx = 0; // #ys_FIXME + + const bool have_retract_length = m_config->option("filament_retract_length")->is_nil() || + m_config->opt_float("filament_retract_length", extruder_idx) > 0; + + for (const std::string& opt_key : opt_keys) + { + bool is_checked = opt_key=="filament_retract_length" ? true : have_retract_length; + m_overrides_options[opt_key]->Enable(is_checked); + + is_checked &= !m_config->option(opt_key)->is_nil(); + m_overrides_options[opt_key]->SetValue(is_checked); + + Field* field = optgroup->get_fieldc(opt_key, extruder_idx); + if (field != nullptr) + field->toggle(is_checked); + } +} + void TabFilament::build() { m_presets = &m_preset_bundle->filaments; @@ -1594,10 +1693,14 @@ void TabFilament::build() }; optgroup->append_line(line); + + add_filament_overrides_page(); + + const int gcode_field_height = 15; // 150 const int notes_field_height = 25; // 250 - page = add_options_page(_(L("Custom G-code")), "cog"); + page = add_options_page(_(L("Custom G-code")), "cog"); optgroup = page->new_optgroup(_(L("Start G-code")), 0); Option option = optgroup->get_option("start_filament_gcode"); option.opt.full_width = true; @@ -1661,7 +1764,7 @@ void TabFilament::update() return; // ys_FIXME m_update_cnt++; -// Freeze(); + wxString text = from_u8(PresetHints::cooling_description(m_presets->get_edited_preset())); m_cooling_description_line->SetText(text); text = from_u8(PresetHints::maximum_volumetric_flow_description(*m_preset_bundle)); @@ -1676,7 +1779,9 @@ void TabFilament::update() for (auto el : { "min_fan_speed", "disable_fan_first_layers" }) get_field(el)->toggle(fan_always_on); -// Thaw(); + + update_filament_overrides_page(); + m_update_cnt--; if (m_update_cnt == 0) diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 73b6bb08d..6ff76f5c4 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -337,6 +337,11 @@ class TabFilament : public Tab { ogStaticText* m_volumetric_speed_description_line; ogStaticText* m_cooling_description_line; + + void add_filament_overrides_page(); + void update_filament_overrides_page(); + + std::map<std::string, wxCheckBox*> m_overrides_options; public: TabFilament(wxNotebook* parent) : // Tab(parent, _(L("Filament Settings")), L("filament")) {} |