diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 1b01234f8..638a02c9b 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -31,10 +31,9 @@ $Options->{threads}{readonly} = !$Slic3r::have_threads; # Fill in the underlying C++ Slic3r::DynamicPrintConfig with the content of the defaults # provided by the C++ class Slic3r::FullPrintConfig. +# Used by the UI. sub new_from_defaults { - my $class = shift; - my (@opt_keys) = @_; - + my ($class, @opt_keys) = @_; my $self = $class->new; # Instantiating the C++ class Slic3r::FullPrintConfig. my $defaults = Slic3r::Config::Full->new; @@ -47,7 +46,7 @@ sub new_from_defaults { return $self; } -# From command line parameters +# From command line parameters, used by slic3r.pl sub new_from_cli { my $class = shift; my %args = @_; @@ -112,12 +111,6 @@ sub load { } } -# Save the content of the underlying C++ Slic3r::DynamicPrintConfig as a flat ini file without any category. -sub save { - my ($self, $file) = @_; - return $self->_save($file); -} - # Deserialize a perl hash into the underlying C++ Slic3r::DynamicConfig class, # convert legacy configuration names. # Used to load a config bundle. @@ -309,6 +302,7 @@ sub validate { # CLASS METHODS: # Write a "Windows" style ini file with categories enclosed in squre brackets. +# Used by config-bundle-to-config.pl and to save slic3r.ini. sub write_ini { my $class = shift; my ($file, $ini) = @_; @@ -331,6 +325,7 @@ sub write_ini { # Returns a hash of hashes over strings. # {category}{name}=value # Non-categorized entries are stored under a category '_'. +# Used by config-bundle-to-config.pl and to read slic3r.ini. sub read_ini { my $class = shift; my ($file) = @_; diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index db46c7d59..b8954cc07 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -177,20 +177,24 @@ bool unescape_strings_cstyle(const std::string &str, std::vector &o } } -void ConfigBase::apply(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) +void ConfigBase::apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent) { // loop through options and apply them - for (const t_config_option_key &key : keys) { - ConfigOption *my_opt = this->option(key, true); + for (const t_config_option_key &opt_key : keys) { + // Create a new option with default value for the key. + // If the key is not in the parameter definition, or this ConfigBase is a static type and it does not support the parameter, + // an exception is thrown if not ignore_nonexistent. + ConfigOption *my_opt = this->option(opt_key, true); if (my_opt == nullptr) { - if (! ignore_nonexistent) - throw "Attempt to apply non-existent option"; - continue; + // opt_key does not exist in this ConfigBase and it cannot be created, because it is not defined by this->def(). + // This is only possible if other is of DynamicConfig type. + if (ignore_nonexistent) + continue; + throw UnknownOptionException(); } - // not the most efficient way, but easier than casting pointers to subclasses - const ConfigOption *other_opt = other.option(key); - if (other_opt != nullptr && ! my_opt->deserialize(other_opt->serialize())) - CONFESS((std::string("Unexpected failure when deserializing serialized value for ") + key).c_str()); + const ConfigOption *other_opt = other.option(opt_key); + if (other_opt != nullptr) + my_opt->set(other_opt); } } @@ -198,9 +202,12 @@ void ConfigBase::apply(const ConfigBase &other, const t_config_option_keys &keys t_config_option_keys ConfigBase::diff(const ConfigBase &other) const { t_config_option_keys diff; - for (const t_config_option_key &opt_key : this->keys()) - if (other.has(opt_key) && other.serialize(opt_key) != this->serialize(opt_key)) - diff.push_back(opt_key); + for (const t_config_option_key &opt_key : this->keys()) { + const ConfigOption *this_opt = this->option(opt_key); + const ConfigOption *other_opt = other.option(opt_key); + if (this_opt != nullptr && other_opt != nullptr && *this_opt != *other_opt) + diff.emplace_back(opt_key); + } return diff; } @@ -211,12 +218,27 @@ std::string ConfigBase::serialize(const t_config_option_key &opt_key) const return opt->serialize(); } -bool ConfigBase::set_deserialize(t_config_option_key opt_key, const std::string &str, bool append) +bool ConfigBase::set_deserialize(const t_config_option_key &opt_key_src, const std::string &value_src, bool append) { - const ConfigOptionDef* optdef = this->def->get(opt_key); + t_config_option_key opt_key = opt_key_src; + std::string value = value_src; + // Both opt_key and value may be modified by _handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by _handle_legacy(). + this->handle_legacy(opt_key, value); + if (opt_key.empty()) + // Ignore the option. + return true; + return this->set_deserialize_raw(opt_key, value, append); +} + +bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, const std::string &value, bool append) +{ + t_config_option_key opt_key = opt_key_src; + // Try to deserialize the option by its name. + const ConfigOptionDef* optdef = this->def()->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. - for (const auto &opt : this->def->options) { + for (const auto &opt : this->def()->options) { for (const t_config_option_key &opt_key2 : opt.second.aliases) { if (opt_key2 == opt_key) { opt_key = opt_key2; @@ -232,44 +254,49 @@ bool ConfigBase::set_deserialize(t_config_option_key opt_key, const std::string } if (! optdef->shortcut.empty()) { + // Aliasing for example "solid_layers" to "top_solid_layers" and "bottom_solid_layers". for (const t_config_option_key &shortcut : optdef->shortcut) - if (! this->set_deserialize(shortcut, str)) + // Recursive call. + if (! this->set_deserialize_raw(shortcut, value, append)) return false; return true; } ConfigOption *opt = this->option(opt_key, true); assert(opt != nullptr); - return opt->deserialize(str, append); + return opt->deserialize(value, append); } // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const { - const ConfigOption* opt = this->option(opt_key); - if (const ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { - // get option definition - const ConfigOptionDef* def = this->def->get(opt_key); + // Get stored option value. + const ConfigOption *raw_opt = this->option(opt_key); + assert(raw_opt != nullptr); + if (raw_opt->type() == coFloat) + return static_cast(raw_opt)->value; + if (raw_opt->type() == coFloatOrPercent) { + // Get option definition. + const ConfigOptionDef *def = this->def()->get(opt_key); assert(def != nullptr); - // compute absolute value over the absolute value of the base option - return optv->get_abs_value(this->get_abs_value(def->ratio_over)); - } else if (const ConfigOptionFloat* optv = dynamic_cast(opt)) { - return optv->value; - } else { - throw "Not a valid option type for get_abs_value()"; + // Compute absolute value over the absolute value of the base option. + return static_cast(raw_opt)->get_abs_value(this->get_abs_value(def->ratio_over)); } + throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value. double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const { - // get stored option value - const ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); - assert(opt != nullptr); - // compute absolute value - return opt->get_abs_value(ratio_over); + // Get stored option value. + const ConfigOption *raw_opt = this->option(opt_key); + assert(raw_opt != nullptr); + if (raw_opt->type() != coFloatOrPercent) + throw std::runtime_error("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent"); + // Compute absolute value. + return static_cast(raw_opt)->get_abs_value(ratio_over); } void ConfigBase::setenv_() @@ -301,8 +328,7 @@ void ConfigBase::load(const std::string &file) for (const pt::ptree::value_type &v : tree) { try { t_config_option_key opt_key = v.first; - std::string value = v.second.get_value(); - this->set_deserialize(opt_key, value); + this->set_deserialize(opt_key, v.second.get_value()); } catch (UnknownOptionException & /* e */) { // ignore } @@ -400,64 +426,43 @@ void ConfigBase::save(const std::string &file) const c.close(); } -ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { +ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) +{ t_options_map::iterator it = options.find(opt_key); - if (it == options.end()) { - if (create) { - const ConfigOptionDef* optdef = this->def->get(opt_key); - assert(optdef != NULL); - ConfigOption* opt; - if (optdef->type == coFloat) { - opt = new ConfigOptionFloat (); - } else if (optdef->type == coFloats) { - opt = new ConfigOptionFloats (); - } else if (optdef->type == coInt) { - opt = new ConfigOptionInt (); - } else if (optdef->type == coInts) { - opt = new ConfigOptionInts (); - } else if (optdef->type == coString) { - opt = new ConfigOptionString (); - } else if (optdef->type == coStrings) { - opt = new ConfigOptionStrings (); - } else if (optdef->type == coPercent) { - opt = new ConfigOptionPercent (); - } else if (optdef->type == coPercents) { - opt = new ConfigOptionPercents (); - } else if (optdef->type == coFloatOrPercent) { - opt = new ConfigOptionFloatOrPercent (); - } else if (optdef->type == coPoint) { - opt = new ConfigOptionPoint (); - } else if (optdef->type == coPoints) { - opt = new ConfigOptionPoints (); - } else if (optdef->type == coBool) { - opt = new ConfigOptionBool (); - } else if (optdef->type == coBools) { - opt = new ConfigOptionBools (); - } else if (optdef->type == coEnum) { - ConfigOptionEnumGeneric* optv = new ConfigOptionEnumGeneric (); - optv->keys_map = &optdef->enum_keys_map; - opt = static_cast(optv); - } else { - throw "Unknown option type"; - } - this->options[opt_key] = opt; - return opt; - } else { - return NULL; - } + if (it != options.end()) + // Option was found. + return it->second; + if (! create) + // Option was not found and a new option shall not be created. + return nullptr; + // Try to create a new ConfigOption. + const ConfigOptionDef *optdef = this->def()->get(opt_key); + if (optdef == nullptr) +// throw std::runtime_error(std::string("Invalid option name: ") + opt_key); + // Let the parent decide what to do if the opt_key is not defined by this->def(). + return nullptr; + ConfigOption *opt = nullptr; + switch (optdef->type) { + case coFloat: opt = new ConfigOptionFloat(); break; + case coFloats: opt = new ConfigOptionFloats(); break; + case coInt: opt = new ConfigOptionInt(); break; + case coInts: opt = new ConfigOptionInts(); break; + case coString: opt = new ConfigOptionString(); break; + case coStrings: opt = new ConfigOptionStrings(); break; + case coPercent: opt = new ConfigOptionPercent(); break; + case coPercents: opt = new ConfigOptionPercents(); break; + case coFloatOrPercent: opt = new ConfigOptionFloatOrPercent(); break; + case coPoint: opt = new ConfigOptionPoint(); break; + case coPoints: opt = new ConfigOptionPoints(); break; + case coBool: opt = new ConfigOptionBool(); break; + case coBools: opt = new ConfigOptionBools(); break; + case coEnum: opt = new ConfigOptionEnumGeneric(optdef->enum_keys_map); break; + default: throw std::runtime_error(std::string("Unknown option type for option ") + opt_key); } - return it->second; + this->options[opt_key] = opt; + return opt; } -template -T* DynamicConfig::opt(const t_config_option_key &opt_key, bool create) { - return dynamic_cast(this->option(opt_key, create)); -} -template ConfigOptionInt* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionBool* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionBools* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); -template ConfigOptionPercent* DynamicConfig::opt(const t_config_option_key &opt_key, bool create); - t_config_option_keys DynamicConfig::keys() const { t_config_option_keys keys; @@ -470,11 +475,13 @@ t_config_option_keys DynamicConfig::keys() const void StaticConfig::set_defaults() { // use defaults from definition - if (this->def != nullptr) { + auto *defs = this->def(); + if (defs != nullptr) { for (const std::string &key : this->keys()) { - const ConfigOptionDef* def = this->def->get(key); - if (def->default_value != nullptr) - this->option(key)->set(*def->default_value); + const ConfigOptionDef *def = defs->get(key); + ConfigOption *opt = this->option(key); + if (def != nullptr && opt != nullptr && def->default_value != nullptr) + opt->set(def->default_value); } } } @@ -483,7 +490,7 @@ t_config_option_keys StaticConfig::keys() const { t_config_option_keys keys; assert(this->def != nullptr); - for (const auto &opt_def : this->def->options) + for (const auto &opt_def : this->def()->options) if (this->option(opt_def.first) != nullptr) keys.push_back(opt_def.first); return keys; diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index 1e548c8fc..6e5c92123 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -16,527 +16,14 @@ namespace Slic3r { // Name of the configuration option. -typedef std::string t_config_option_key; -typedef std::vector t_config_option_keys; +typedef std::string t_config_option_key; +typedef std::vector t_config_option_keys; -extern std::string escape_string_cstyle(const std::string &str); -extern std::string escape_strings_cstyle(const std::vector &strs); -extern bool unescape_string_cstyle(const std::string &str, std::string &out); -extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); +extern std::string escape_string_cstyle(const std::string &str); +extern std::string escape_strings_cstyle(const std::vector &strs); +extern bool unescape_string_cstyle(const std::string &str, std::string &out); +extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); -// A generic value of a configuration option. -class ConfigOption { -public: - virtual ~ConfigOption() {}; - virtual std::string serialize() const = 0; - virtual bool deserialize(const std::string &str, bool append = false) = 0; - virtual void set(const ConfigOption &option) = 0; - virtual int getInt() const { return 0; }; - virtual double getFloat() const { return 0; }; - virtual bool getBool() const { return false; }; - virtual void setInt(int /* val */) { }; - bool operator==(const ConfigOption &rhs) { return this->serialize().compare(rhs.serialize()) == 0; } - bool operator!=(const ConfigOption &rhs) { return this->serialize().compare(rhs.serialize()) != 0; } -}; - -// Value of a single valued option (bool, int, float, string, point, enum) -template -class ConfigOptionSingle : public ConfigOption { -public: - T value; - ConfigOptionSingle(T _value) : value(_value) {}; - operator T() const { return this->value; }; - - void set(const ConfigOption &option) { - const ConfigOptionSingle* other = dynamic_cast< const ConfigOptionSingle* >(&option); - if (other != nullptr) this->value = other->value; - }; -}; - -// Value of a vector valued option (bools, ints, floats, strings, points) -class ConfigOptionVectorBase : public ConfigOption { -public: - virtual ~ConfigOptionVectorBase() {}; - // Currently used only to initialize the PlaceholderParser. - virtual std::vector vserialize() const = 0; -}; - -// Value of a vector valued option (bools, ints, floats, strings, points), template -template -class ConfigOptionVector : public ConfigOptionVectorBase -{ -public: - virtual ~ConfigOptionVector() {}; - std::vector values; - - void set(const ConfigOption &option) { - const ConfigOptionVector* other = dynamic_cast< const ConfigOptionVector* >(&option); - if (other != nullptr) this->values = other->values; - }; - - T& get_at(size_t i) { - assert(! this->values.empty()); - return (i < this->values.size()) ? this->values[i] : this->values.front(); - }; - - const T& get_at(size_t i) const { return const_cast*>(this)->get_at(i); } -}; - -class ConfigOptionFloat : public ConfigOptionSingle -{ -public: - ConfigOptionFloat() : ConfigOptionSingle(0) {}; - ConfigOptionFloat(double _value) : ConfigOptionSingle(_value) {}; - - double getFloat() const { return this->value; }; - - std::string serialize() const { - std::ostringstream ss; - ss << this->value; - return ss.str(); - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - std::istringstream iss(str); - iss >> this->value; - return !iss.fail(); - }; -}; - -class ConfigOptionFloats : public ConfigOptionVector -{ -public: - std::string serialize() const { - std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << *it; - } - return ss.str(); - }; - - std::vector vserialize() const { - std::vector vv; - vv.reserve(this->values.size()); - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - std::ostringstream ss; - ss << *it; - vv.push_back(ss.str()); - } - return vv; - }; - - bool deserialize(const std::string &str, bool append = false) { - 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; - iss >> value; - this->values.push_back(value); - } - return true; - }; -}; - -class ConfigOptionInt : public ConfigOptionSingle -{ -public: - ConfigOptionInt() : ConfigOptionSingle(0) {}; - ConfigOptionInt(double _value) : ConfigOptionSingle(int(floor(_value + 0.5))) {}; - - int getInt() const { return this->value; }; - void setInt(int val) { this->value = val; }; - - std::string serialize() const { - std::ostringstream ss; - ss << this->value; - return ss.str(); - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - std::istringstream iss(str); - iss >> this->value; - return !iss.fail(); - }; -}; - -class ConfigOptionInts : public ConfigOptionVector -{ -public: - std::string serialize() const { - std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << *it; - } - return ss.str(); - }; - - std::vector vserialize() const { - std::vector vv; - vv.reserve(this->values.size()); - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - std::ostringstream ss; - ss << *it; - vv.push_back(ss.str()); - } - return vv; - }; - - bool deserialize(const std::string &str, bool append = false) { - if (! append) - this->values.clear(); - 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); - } - return true; - }; -}; - -class ConfigOptionString : public ConfigOptionSingle -{ -public: - ConfigOptionString() : ConfigOptionSingle("") {}; - ConfigOptionString(std::string _value) : ConfigOptionSingle(_value) {}; - - std::string serialize() const { - return escape_string_cstyle(this->value); - } - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - return unescape_string_cstyle(str, this->value); - }; -}; - -// semicolon-separated strings -class ConfigOptionStrings : public ConfigOptionVector -{ -public: - std::string serialize() const { - return escape_strings_cstyle(this->values); - }; - - std::vector vserialize() const { - return this->values; - }; - - bool deserialize(const std::string &str, bool append = false) { - if (! append) - this->values.clear(); - return unescape_strings_cstyle(str, this->values); - }; -}; - -class ConfigOptionPercent : public ConfigOptionFloat -{ -public: - ConfigOptionPercent() : ConfigOptionFloat(0) {}; - ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}; - - double get_abs_value(double ratio_over) const { - return ratio_over * this->value / 100; - }; - - std::string serialize() const { - std::ostringstream ss; - ss << this->value; - std::string s(ss.str()); - s += "%"; - return s; - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - // don't try to parse the trailing % since it's optional - std::istringstream iss(str); - iss >> this->value; - return !iss.fail(); - }; -}; - -class ConfigOptionPercents : public ConfigOptionFloats -{ -public: - std::string serialize() const { - std::ostringstream ss; - for (const auto &v : this->values) { - if (&v != &this->values.front()) ss << ","; - ss << v << "%"; - } - std::string str = ss.str(); - return str; - }; - - std::vector vserialize() const { - std::vector vv; - vv.reserve(this->values.size()); - for (const auto v : this->values) { - std::ostringstream ss; - ss << v; - std::string sout = ss.str() + "%"; - vv.push_back(sout); - } - return vv; - }; - - bool deserialize(const std::string &str, bool append = false) { - 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; - }; -}; - -class ConfigOptionFloatOrPercent : public ConfigOptionPercent -{ -public: - bool percent; - ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}; - ConfigOptionFloatOrPercent(double _value, bool _percent) - : ConfigOptionPercent(_value), percent(_percent) {}; - - void set(const ConfigOption &option) { - const ConfigOptionFloatOrPercent* other = dynamic_cast< const ConfigOptionFloatOrPercent* >(&option); - if (other != NULL) { - this->value = other->value; - this->percent = other->percent; - } - }; - - double get_abs_value(double ratio_over) const { - if (this->percent) { - return ratio_over * this->value / 100; - } else { - return this->value; - } - }; - - std::string serialize() const { - std::ostringstream ss; - ss << this->value; - std::string s(ss.str()); - if (this->percent) s += "%"; - return s; - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - this->percent = str.find_first_of("%") != std::string::npos; - std::istringstream iss(str); - iss >> this->value; - return !iss.fail(); - }; -}; - -class ConfigOptionPoint : public ConfigOptionSingle -{ -public: - ConfigOptionPoint() : ConfigOptionSingle(Pointf(0,0)) {}; - ConfigOptionPoint(Pointf _value) : ConfigOptionSingle(_value) {}; - - std::string serialize() const { - std::ostringstream ss; - ss << this->value.x; - ss << ","; - ss << this->value.y; - return ss.str(); - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - std::istringstream iss(str); - iss >> this->value.x; - iss.ignore(std::numeric_limits::max(), ','); - iss.ignore(std::numeric_limits::max(), 'x'); - iss >> this->value.y; - return true; - }; -}; - -class ConfigOptionPoints : public ConfigOptionVector -{ -public: - std::string serialize() const { - std::ostringstream ss; - for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << it->x; - ss << "x"; - ss << it->y; - } - return ss.str(); - }; - - std::vector vserialize() const { - std::vector vv; - for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - std::ostringstream ss; - ss << *it; - vv.push_back(ss.str()); - } - return vv; - }; - - bool deserialize(const std::string &str, bool append = false) { - if (! append) - this->values.clear(); - std::istringstream is(str); - std::string point_str; - while (std::getline(is, point_str, ',')) { - Pointf point; - std::istringstream iss(point_str); - std::string coord_str; - if (std::getline(iss, coord_str, 'x')) { - std::istringstream(coord_str) >> point.x; - if (std::getline(iss, coord_str, 'x')) { - std::istringstream(coord_str) >> point.y; - } - } - this->values.push_back(point); - } - return true; - }; -}; - -class ConfigOptionBool : public ConfigOptionSingle -{ -public: - ConfigOptionBool() : ConfigOptionSingle(false) {}; - ConfigOptionBool(bool _value) : ConfigOptionSingle(_value) {}; - - bool getBool() const { return this->value; }; - - std::string serialize() const { - return std::string(this->value ? "1" : "0"); - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - this->value = (str.compare("1") == 0); - return true; - }; -}; - -class ConfigOptionBools : public ConfigOptionVector -{ -public: - void set(const ConfigOption &option) { - const ConfigOptionVector* other = dynamic_cast*>(&option); - if (other != nullptr) - this->values = other->values; - }; - - bool& get_at(size_t i) { - assert(! this->values.empty()); - return *reinterpret_cast(&((i < this->values.size()) ? this->values[i] : this->values.front())); - }; - - bool get_at(size_t i) const { return bool((i < this->values.size()) ? this->values[i] : this->values.front()); } - - std::string serialize() const { - std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ","; - ss << (*it ? "1" : "0"); - } - return ss.str(); - }; - - std::vector vserialize() const { - std::vector vv; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - std::ostringstream ss; - ss << (*it ? "1" : "0"); - vv.push_back(ss.str()); - } - return vv; - }; - - bool deserialize(const std::string &str, bool append = false) { - if (! append) - this->values.clear(); - std::istringstream is(str); - std::string item_str; - while (std::getline(is, item_str, ',')) { - this->values.push_back(item_str.compare("1") == 0); - } - return true; - }; -}; - -// Map from an enum name to an enum integer value. -typedef std::map t_config_enum_values; - -template -class ConfigOptionEnum : public ConfigOptionSingle -{ -public: - // by default, use the first value (0) of the T enum type - ConfigOptionEnum() : ConfigOptionSingle(static_cast(0)) {}; - ConfigOptionEnum(T _value) : ConfigOptionSingle(_value) {}; - - std::string serialize() const { - t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); - for (t_config_enum_values::iterator it = enum_keys_map.begin(); it != enum_keys_map.end(); ++it) { - if (it->second == static_cast(this->value)) return it->first; - } - return ""; - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - t_config_enum_values enum_keys_map = ConfigOptionEnum::get_enum_values(); - if (enum_keys_map.count(str) == 0) return false; - this->value = static_cast(enum_keys_map[str]); - return true; - }; - - // Map from an enum name to an enum integer value. - //FIXME The map is called often, it shall be initialized statically. - static t_config_enum_values get_enum_values(); -}; - -// Generic enum configuration value. -// We use this one in DynamicConfig objects when creating a config value object for ConfigOptionType == coEnum. -// In the StaticConfig, it is better to use the specialized ConfigOptionEnum containers. -class ConfigOptionEnumGeneric : public ConfigOptionInt -{ -public: - const t_config_enum_values* keys_map; - - std::string serialize() const { - for (t_config_enum_values::const_iterator it = this->keys_map->begin(); it != this->keys_map->end(); ++it) { - if (it->second == this->value) return it->first; - } - return ""; - }; - - bool deserialize(const std::string &str, bool append = false) { - UNUSED(append); - if (this->keys_map->count(str) == 0) return false; - this->value = (*const_cast(this->keys_map))[str]; - return true; - }; -}; // Type of a configuration value. enum ConfigOptionType { @@ -571,76 +58,762 @@ enum ConfigOptionType { coEnum, }; +// A generic value of a configuration option. +class ConfigOption { +public: + virtual ~ConfigOption() {}; + + virtual ConfigOptionType type() const = 0; + virtual std::string serialize() const = 0; + virtual bool deserialize(const std::string &str, bool append = false) = 0; + virtual ConfigOption* clone() const = 0; + // Set a value from a ConfigOption. The two options should be compatible. + virtual void set(const ConfigOption *option) = 0; + virtual int getInt() const { throw std::runtime_error("Calling ConfigOption::getInt on a non-int ConfigOption"); return 0; } + virtual double getFloat() const { throw std::runtime_error("Calling ConfigOption::getFloat on a non-float ConfigOption"); return 0; } + virtual bool getBool() const { throw std::runtime_error("Calling ConfigOption::getBool on a non-boolean ConfigOption"); return 0; } + virtual void setInt(int /* val */) { throw std::runtime_error("Calling ConfigOption::setInt on a non-int ConfigOption"); } + virtual bool operator==(const ConfigOption &rhs) const = 0; + bool operator!=(const ConfigOption &rhs) const { return ! (*this == rhs); } +}; + +// Value of a single valued option (bool, int, float, string, point, enum) +template +class ConfigOptionSingle : public ConfigOption { +public: + T value; + explicit ConfigOptionSingle(T value) : value(value) {} + operator T() const { return this->value; } + + void set(const ConfigOption *rhs) override + { + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); + assert(dynamic_cast*>(rhs)); + this->value = static_cast*>(rhs)->value; + }; + + bool operator==(const ConfigOption &rhs) const override + { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionSingle: Comparing incompatible types"); + assert(dynamic_cast*>(&rhs)); + return this->value == static_cast*>(&rhs)->value; + } + + bool operator==(const T &rhs) const { return this->value == rhs; } + bool operator!=(const T &rhs) const { return this->value != rhs; } +}; + +// Value of a vector valued option (bools, ints, floats, strings, points) +class ConfigOptionVectorBase : public ConfigOption { +public: + // Currently used only to initialize the PlaceholderParser. + virtual std::vector vserialize() const = 0; +}; + +// Value of a vector valued option (bools, ints, floats, strings, points), template +template +class ConfigOptionVector : public ConfigOptionVectorBase +{ +public: + std::vector values; + + void set(const ConfigOption *rhs) override + { + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); + assert(dynamic_cast*>(rhs)); + this->values = static_cast*>(rhs)->values; + }; + + T& get_at(size_t i) + { + assert(! this->values.empty()); + return (i < this->values.size()) ? this->values[i] : this->values.front(); + }; + + const T& get_at(size_t i) const { return const_cast*>(this)->get_at(i); } + + bool operator==(const ConfigOption &rhs) const override + { + if (rhs.type() != this->type()) + throw std::runtime_error("ConfigOptionVector: Comparing incompatible types"); + assert(dynamic_cast*>(&rhs)); + return this->values == static_cast*>(&rhs)->values; + } + + bool operator==(const std::vector &rhs) const { return this->values == rhs; } + bool operator!=(const std::vector &rhs) const { return this->values != rhs; } +}; + +class ConfigOptionFloat : public ConfigOptionSingle +{ +public: + ConfigOptionFloat() : ConfigOptionSingle(0) {}; + explicit ConfigOptionFloat(double _value) : ConfigOptionSingle(_value) {}; + + ConfigOptionType type() const override { return coFloat; } + double getFloat() const override { return this->value; } + ConfigOption* clone() const override { return new ConfigOptionFloat(*this); } + bool operator==(const ConfigOptionFloat &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value; + return ss.str(); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + std::istringstream iss(str); + iss >> this->value; + return !iss.fail(); + } + + ConfigOptionFloat& operator=(const ConfigOption *opt) + { + this->set(opt); + return *this; + } +}; + +class ConfigOptionFloats : public ConfigOptionVector +{ +public: + ConfigOptionType type() const override { return coFloats; } + ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } + bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } + + std::string serialize() const override + { + std::ostringstream ss; + for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + if (it - this->values.begin() != 0) ss << ","; + ss << *it; + } + return ss.str(); + }; + + std::vector vserialize() const override + { + std::vector vv; + vv.reserve(this->values.size()); + for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + std::ostringstream ss; + ss << *it; + 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; + iss >> value; + this->values.push_back(value); + } + return true; + } + + ConfigOptionFloats& operator=(const ConfigOption *opt) + { + this->set(opt); + return *this; + } +}; + +class ConfigOptionInt : public ConfigOptionSingle +{ +public: + ConfigOptionInt() : ConfigOptionSingle(0) {}; + explicit ConfigOptionInt(int value) : ConfigOptionSingle(value) {}; + explicit ConfigOptionInt(double _value) : ConfigOptionSingle(int(floor(_value + 0.5))) {}; + + ConfigOptionType type() const override { return coInt; } + int getInt() const override { return this->value; }; + void setInt(int val) { this->value = val; }; + ConfigOption* clone() const override { return new ConfigOptionInt(*this); } + bool operator==(const ConfigOptionInt &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value; + return ss.str(); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + std::istringstream iss(str); + iss >> this->value; + return !iss.fail(); + } + + ConfigOptionInt& operator=(const ConfigOption *opt) + { + this->set(opt); + return *this; + } +}; + +class ConfigOptionInts : public ConfigOptionVector +{ +public: + ConfigOptionType type() const override { return coInts; } + 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; } + + std::string serialize() const override { + std::ostringstream ss; + for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + if (it - this->values.begin() != 0) ss << ","; + ss << *it; + } + return ss.str(); + } + + std::vector vserialize() const override + { + std::vector vv; + vv.reserve(this->values.size()); + for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + std::ostringstream ss; + ss << *it; + 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); + int value; + iss >> value; + this->values.push_back(value); + } + return true; + } +}; + +class ConfigOptionString : public ConfigOptionSingle +{ +public: + ConfigOptionString() : ConfigOptionSingle("") {}; + explicit ConfigOptionString(std::string _value) : ConfigOptionSingle(_value) {}; + + ConfigOptionType type() const override { return coString; } + ConfigOption* clone() const override { return new ConfigOptionString(*this); } + ConfigOptionString& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionString &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + return escape_string_cstyle(this->value); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + return unescape_string_cstyle(str, this->value); + } +}; + +// semicolon-separated strings +class ConfigOptionStrings : public ConfigOptionVector +{ +public: + ConfigOptionType type() const override { return coStrings; } + 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; } + + std::string serialize() const override + { + return escape_strings_cstyle(this->values); + } + + std::vector vserialize() const override + { + return this->values; + } + + bool deserialize(const std::string &str, bool append = false) override + { + if (! append) + this->values.clear(); + return unescape_strings_cstyle(str, this->values); + } +}; + +class ConfigOptionPercent : public ConfigOptionFloat +{ +public: + ConfigOptionPercent() : ConfigOptionFloat(0) {}; + explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}; + + ConfigOptionType type() const override { return coPercent; } + ConfigOption* clone() const override { return new ConfigOptionPercent(*this); } + ConfigOptionPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercent &rhs) const { return this->value == rhs.value; } + double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100; } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value; + std::string s(ss.str()); + s += "%"; + return s; + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + // don't try to parse the trailing % since it's optional + std::istringstream iss(str); + iss >> this->value; + return !iss.fail(); + } +}; + +class ConfigOptionPercents : public ConfigOptionFloats +{ +public: + ConfigOptionType type() const override { return coPercents; } + 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; } + + std::string serialize() const override + { + std::ostringstream ss; + for (const auto &v : this->values) { + if (&v != &this->values.front()) ss << ","; + ss << v << "%"; + } + std::string str = ss.str(); + return str; + } + + std::vector vserialize() const override + { + std::vector vv; + vv.reserve(this->values.size()); + for (const auto v : this->values) { + std::ostringstream ss; + ss << v; + std::string sout = ss.str() + "%"; + vv.push_back(sout); + } + 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; + } +}; + +class ConfigOptionFloatOrPercent : public ConfigOptionPercent +{ +public: + bool percent; + ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {}; + explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {}; + + ConfigOptionType type() const override { return coFloatOrPercent; } + ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); } + ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionFloatOrPercent &rhs) const + { return this->value == rhs.value && this->percent == rhs.percent; } + double get_abs_value(double ratio_over) const + { return this->percent ? (ratio_over * this->value / 100) : this->value; } + + void set(const ConfigOption *rhs) override { + if (rhs->type() != this->type()) + throw std::runtime_error("ConfigOptionFloatOrPercent: Assigning an incompatible type"); + assert(dynamic_cast(rhs)); + *this = *static_cast(rhs); + } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value; + std::string s(ss.str()); + if (this->percent) s += "%"; + return s; + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + this->percent = str.find_first_of("%") != std::string::npos; + std::istringstream iss(str); + iss >> this->value; + return !iss.fail(); + } +}; + +class ConfigOptionPoint : public ConfigOptionSingle +{ +public: + ConfigOptionPoint() : ConfigOptionSingle(Pointf(0,0)) {}; + explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle(value) {}; + + ConfigOptionType type() const override { return coPoint; } + ConfigOption* clone() const override { return new ConfigOptionPoint(*this); } + ConfigOptionPoint& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPoint &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + std::ostringstream ss; + ss << this->value.x; + ss << ","; + ss << this->value.y; + return ss.str(); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + std::istringstream iss(str); + iss >> this->value.x; + iss.ignore(std::numeric_limits::max(), ','); + iss.ignore(std::numeric_limits::max(), 'x'); + iss >> this->value.y; + return true; + } +}; + +class ConfigOptionPoints : public ConfigOptionVector +{ +public: + ConfigOptionType type() const override { return coPoints; } + 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; } + + std::string serialize() const override + { + std::ostringstream ss; + for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + if (it - this->values.begin() != 0) ss << ","; + ss << it->x; + ss << "x"; + ss << it->y; + } + return ss.str(); + } + + std::vector vserialize() const override + { + std::vector vv; + for (Pointfs::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + std::ostringstream ss; + ss << *it; + 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 point_str; + while (std::getline(is, point_str, ',')) { + Pointf point; + std::istringstream iss(point_str); + std::string coord_str; + if (std::getline(iss, coord_str, 'x')) { + std::istringstream(coord_str) >> point.x; + if (std::getline(iss, coord_str, 'x')) { + std::istringstream(coord_str) >> point.y; + } + } + this->values.push_back(point); + } + return true; + } +}; + +class ConfigOptionBool : public ConfigOptionSingle +{ +public: + ConfigOptionBool() : ConfigOptionSingle(false) {}; + explicit ConfigOptionBool(bool _value) : ConfigOptionSingle(_value) {}; + + ConfigOptionType type() const override { return coBool; } + bool getBool() const override { return this->value; }; + ConfigOption* clone() const override { return new ConfigOptionBool(*this); } + ConfigOptionBool& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionBool &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + return std::string(this->value ? "1" : "0"); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + this->value = (str.compare("1") == 0); + return true; + } +}; + +class ConfigOptionBools : public ConfigOptionVector +{ +public: + ConfigOptionType type() const override { return coBools; } + 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; } + + bool& get_at(size_t i) { + assert(! this->values.empty()); + return *reinterpret_cast(&((i < this->values.size()) ? this->values[i] : this->values.front())); + } + + //FIXME this smells, the parent class has the method declared returning (unsigned char&). + bool get_at(size_t i) const { return bool((i < this->values.size()) ? this->values[i] : this->values.front()); } + + std::string serialize() const override + { + std::ostringstream ss; + for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + if (it - this->values.begin() != 0) ss << ","; + ss << (*it ? "1" : "0"); + } + return ss.str(); + } + + std::vector vserialize() const override + { + std::vector vv; + for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { + std::ostringstream ss; + ss << (*it ? "1" : "0"); + 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, ',')) { + this->values.push_back(item_str.compare("1") == 0); + } + return true; + } +}; + +// Map from an enum integer value to an enum name. +typedef std::vector t_config_enum_names; +// Map from an enum name to an enum integer value. +typedef std::map t_config_enum_values; + +template +class ConfigOptionEnum : public ConfigOptionSingle +{ +public: + // by default, use the first value (0) of the T enum type + ConfigOptionEnum() : ConfigOptionSingle(static_cast(0)) {}; + explicit ConfigOptionEnum(T _value) : ConfigOptionSingle(_value) {}; + + ConfigOptionType type() const override { return coEnum; } + ConfigOption* clone() const override { return new ConfigOptionEnum(*this); } + ConfigOptionEnum& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionEnum &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + const t_config_enum_names& names = ConfigOptionEnum::get_enum_names(); + assert(static_cast(this->value) < int(names.size())); + return names[static_cast(this->value)]; + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); + auto it = enum_keys_map.find(str); + if (it == enum_keys_map.end()) + return false; + this->value = static_cast(it->second); + return true; + } + + static bool has(T value) + { + for (const std::map &kvp : get_enum_values()) + if (kvp.second == value) + return true; + return false; + } + + // Map from an enum name to an enum integer value. + static t_config_enum_names& get_enum_names() + { + static t_config_enum_names names; + if (names.empty()) { + // Initialize the map. + const t_config_enum_values &enum_keys_map = ConfigOptionEnum::get_enum_values(); + int cnt = 0; + for (auto &kvp : enum_keys_map) + cnt = std::max(cnt, kvp.second); + cnt += 1; + names.assign(cnt, ""); + for (auto &kvp : enum_keys_map) + names[kvp.second] = kvp.first; + } + return names; + } + // Map from an enum name to an enum integer value. + static t_config_enum_values& get_enum_values(); +}; + +// Generic enum configuration value. +// We use this one in DynamicConfig objects when creating a config value object for ConfigOptionType == coEnum. +// In the StaticConfig, it is better to use the specialized ConfigOptionEnum containers. +class ConfigOptionEnumGeneric : public ConfigOptionInt +{ +public: + ConfigOptionEnumGeneric(const t_config_enum_values* keys_map = nullptr) : keys_map(keys_map) {} + + const t_config_enum_values* keys_map; + + ConfigOptionType type() const override { return coEnum; } + ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } + ConfigOptionEnumGeneric& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionEnumGeneric &rhs) const { return this->value == rhs.value; } + + std::string serialize() const override + { + for (const auto &kvp : *this->keys_map) + if (kvp.second == this->value) + return kvp.first; + return std::string(); + } + + bool deserialize(const std::string &str, bool append = false) override + { + UNUSED(append); + auto it = this->keys_map->find(str); + if (it == this->keys_map->end()) + return false; + this->value = it->second; + return true; + } +}; + // Definition of a configuration value for the purpose of GUI presentation, editing, value mapping and config file handling. class ConfigOptionDef { public: // What type? bool, int, string etc. - ConfigOptionType type; + ConfigOptionType type = coNone; // Default value of this option. The default value object is owned by ConfigDef, it is released in its destructor. - ConfigOption* default_value; + ConfigOption *default_value = nullptr; // Usually empty. // Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection, // "select_open" - to open a selection dialog (currently only a serial port selection). - std::string gui_type; + std::string gui_type; // Usually empty. Otherwise "serialized" or "show_value" // The flags may be combined. // "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon. // "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label. - std::string gui_flags; + std::string gui_flags; // Label of the GUI input field. // In case the GUI input fields are grouped in some views, the label defines a short label of a grouped value, // while full_label contains a label of a stand-alone field. // The full label is shown, when adding an override parameter for an object or a modified object. - std::string label; - std::string full_label; + std::string label; + std::string full_label; // Category of a configuration field, from the GUI perspective. // One of: "Layers and Perimeters", "Infill", "Support material", "Speed", "Extruders", "Advanced", "Extrusion Width" - std::string category; + std::string category; // A tooltip text shown in the GUI. - std::string tooltip; + std::string tooltip; // Text right from the input field, usually a unit of measurement. - std::string sidetext; + std::string sidetext; // Format of this parameter on a command line. - std::string cli; + std::string cli; // Set for type == coFloatOrPercent. // It provides a link to a configuration value, of which this option provides a ratio. // For example, // For example external_perimeter_speed may be defined as a fraction of perimeter_speed. - t_config_option_key ratio_over; + t_config_option_key ratio_over; // True for multiline strings. - bool multiline; + bool multiline = false; // For text input: If true, the GUI text box spans the complete page width. - bool full_width; + bool full_width = false; // Not editable. Currently only used for the display of the number of threads. - bool readonly; + bool readonly = false; // Height of a multiline GUI text box. - int height; + int height = -1; // Optional width of an input field. - int width; + int width = -1; // limit of a numeric input. // If not set, the is set to // By setting min=0, only nonnegative input is allowed. - int min; - int max; + int min = INT_MIN; + int max = INT_MAX; // Legacy names for this configuration option. // Used when parsing legacy configuration file. - std::vector aliases; + std::vector aliases; // Sometimes a single value may well define multiple values in a "beginner" mode. // Currently used for aliasing "solid_layers" to "top_solid_layers", "bottom_solid_layers". - std::vector shortcut; + std::vector shortcut; // Definition of values / labels for a combo box. // Mostly used for enums (when type == coEnum), but may be used for ints resp. floats, if gui_type is set to "i_enum_open" resp. "f_enum_open". - std::vector enum_values; - std::vector enum_labels; + std::vector enum_values; + std::vector enum_labels; // For enums (when type == coEnum). Maps enum_values to enums. // Initialized by ConfigOptionEnum::get_enum_values() - t_config_enum_values enum_keys_map; + t_config_enum_values *enum_keys_map = nullptr; - ConfigOptionDef() : type(coNone), default_value(NULL), - multiline(false), full_width(false), readonly(false), - height(-1), width(-1), min(INT_MIN), max(INT_MAX) {}; + bool has_enum_value(const std::string &value) const { + for (const std::string &v : enum_values) + if (v == value) + return true; + return false; + } }; // Map from a config option name to its definition. @@ -656,13 +829,13 @@ class ConfigDef public: t_optiondef_map options; ~ConfigDef() { for (auto &opt : this->options) delete opt.second.default_value; } - ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { + ConfigOptionDef* add(const t_config_option_key &opt_key, ConfigOptionType type) { ConfigOptionDef* opt = &this->options[opt_key]; opt->type = type; return opt; } - bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } - const ConfigOptionDef* get(const t_config_option_key &opt_key) const { + bool has(const t_config_option_key &opt_key) const { return this->options.count(opt_key) > 0; } + const ConfigOptionDef* get(const t_config_option_key &opt_key) const { t_optiondef_map::iterator it = const_cast(this)->options.find(opt_key); return (it == this->options.end()) ? nullptr : &it->second; } @@ -675,24 +848,46 @@ public: // Definition of configuration values for the purpose of GUI presentation, editing, value mapping and config file handling. // The configuration definition is static: It does not carry the actual configuration values, // but it carries the defaults of the configuration values. - // ConfigBase does not own ConfigDef, it only references it. - const ConfigDef* def; - ConfigBase(const ConfigDef *def = nullptr) : def(def) {}; - virtual ~ConfigBase() {}; + ConfigBase() {} + virtual ~ConfigBase() {} + + // Virtual overridables: +public: + // Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + virtual const ConfigDef* def() const = 0; + // Find ando/or create a ConfigOption instance for a given name. + virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; + // Collect names of all configuration values maintained by this configuration store. + virtual t_config_option_keys keys() const = 0; +protected: + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + virtual void handle_legacy(t_config_option_key &opt_key, std::string &value) const {} + +public: + // Non-virtual methods: bool has(const t_config_option_key &opt_key) const { return this->option(opt_key) != nullptr; } const ConfigOption* option(const t_config_option_key &opt_key) const { return const_cast(this)->option(opt_key, false); } ConfigOption* option(const t_config_option_key &opt_key, bool create = false) { return this->optptr(opt_key, create); } - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; - virtual t_config_option_keys keys() const = 0; - void apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->apply(other, other.keys(), ignore_nonexistent); } - void apply(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); + // Apply all keys of other ConfigBase defined by this->def() to this ConfigBase. + // An UnknownOptionException is thrown in case some option keys of other are not defined by this->def(), + // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. + void apply(const ConfigBase &other, bool ignore_nonexistent = false) { this->apply_only(other, other.keys(), ignore_nonexistent); } + // Apply explicitely enumerated keys of other ConfigBase defined by this->def() to this ConfigBase. + // An UnknownOptionException is thrown in case some option keys are not defined by this->def(), + // or this ConfigBase is of a StaticConfig type and it does not support some of the keys, and ignore_nonexistent is not set. + void apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false); bool equals(const ConfigBase &other) const { return this->diff(other).empty(); } t_config_option_keys diff(const ConfigBase &other) const; std::string serialize(const t_config_option_key &opt_key) const; - bool set_deserialize(t_config_option_key opt_key, const std::string &str, bool append = false); + // Set a configuration value from a string, it will call an overridable handle_legacy() + // to resolve renamed and removed configuration keys. + bool set_deserialize(const t_config_option_key &opt_key, const std::string &str, bool append = false); double get_abs_value(const t_config_option_key &opt_key) const; double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; @@ -700,6 +895,10 @@ public: void load(const std::string &file); void load_from_gcode(const std::string &file); void save(const std::string &file) const; + +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); }; // Configuration store with dynamic number of configuration values. @@ -707,49 +906,100 @@ public: class DynamicConfig : public virtual ConfigBase { public: - DynamicConfig() {}; - DynamicConfig(const DynamicConfig& other) : ConfigBase(other.def) { this->apply(other, false); } - DynamicConfig& operator= (DynamicConfig other) { this->swap(other); return *this; } - virtual ~DynamicConfig() { for (auto &opt : this->options) delete opt.second; } - void swap(DynamicConfig &other) { std::swap(this->def, other.def); std::swap(this->options, other.options); } - template T* opt(const t_config_option_key &opt_key, bool create = false); - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false); - t_config_option_keys keys() const; - void erase(const t_config_option_key &opt_key) { this->options.erase(opt_key); } + DynamicConfig(const DynamicConfig& other) { *this = other; } + DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } + virtual ~DynamicConfig() { clear(); } - std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->optptr(opt_key, create))->value; } + DynamicConfig& operator=(const DynamicConfig &other) + { + this->clear(); + for (const auto &kvp : other.options) + this->options[kvp.first] = kvp.second->clone(); + return *this; + } + + DynamicConfig& operator=(DynamicConfig &&other) + { + this->clear(); + this->options = std::move(other.options); + other.options.clear(); + return *this; + } + + void swap(DynamicConfig &other) + { + std::swap(this->options, other.options); + } + + void clear() + { + for (auto &opt : this->options) + delete opt.second; + this->options.clear(); + } + + bool erase(const t_config_option_key &opt_key) + { + auto it = this->options.find(opt_key); + if (it == this->options.end()) + return false; + delete it->second; + this->options.erase(it); + return true; + } + + template T* opt(const t_config_option_key &opt_key, bool create = false) + { return dynamic_cast(this->option(opt_key, create)); } + // Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. + ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override; + // Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. + t_config_option_keys keys() const override; + + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast(this->option(opt_key, create))->value; } const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast(this)->opt_string(opt_key); } - std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast(this->optptr(opt_key))->get_at(idx); } + std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast(this->option(opt_key))->get_at(idx); } const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_string(opt_key, idx); } - double& opt_float(const t_config_option_key &opt_key) { return dynamic_cast(this->optptr(opt_key))->value; } - const double& opt_float(const t_config_option_key &opt_key) const { return const_cast(this)->opt_float(opt_key); } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast(this->optptr(opt_key))->get_at(idx); } - const double& opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast(this)->opt_float(opt_key, idx); } + double& opt_float(const t_config_option_key &opt_key) { return dynamic_cast(this->option(opt_key))->value; } + const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast(this->option(opt_key))->get_at(idx); } + const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + + int& opt_int(const t_config_option_key &opt_key) { return dynamic_cast(this->option(opt_key))->value; } + const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; } + int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast(this->option(opt_key))->get_at(idx); } + const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); } + +protected: + DynamicConfig() {} private: typedef std::map t_options_map; t_options_map options; }; -// Configuration store with a static definition of configuration values. -// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons, -// because the configuration values could be accessed directly. +/// Configuration store with a static definition of configuration values. +/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons, +/// because the configuration values could be accessed directly. class StaticConfig : public virtual ConfigBase { public: - StaticConfig() : ConfigBase() {}; - // Gets list of config option names for each config option of this->def, which has a static counter-part defined by the derived object - // and which could be resolved by this->optptr(key) call. + StaticConfig() {} + /// Gets list of config option names for each config option of this->def, which has a static counter-part defined by the derived object + /// and which could be resolved by this->optptr(key) call. t_config_option_keys keys() const; - // Set all statically defined config options to their defaults defined by this->def. + +protected: + /// Set all statically defined config options to their defaults defined by this->def(). void set_defaults(); - // The derived class has to implement optptr to resolve a static configuration value. - // virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) = 0; }; /// Specialization of std::exception to indicate that an unknown config option has been encountered. -class UnknownOptionException : public std::exception {}; +class UnknownOptionException : public std::exception +{ +public: + const char* what() const override { return "Unknown config option"; } +}; } diff --git a/xs/src/libslic3r/GCodeReader.cpp b/xs/src/libslic3r/GCodeReader.cpp index 7df370d1c..453cc9f94 100644 --- a/xs/src/libslic3r/GCodeReader.cpp +++ b/xs/src/libslic3r/GCodeReader.cpp @@ -6,7 +6,13 @@ namespace Slic3r { -void GCodeReader::apply_config(const PrintConfigBase &config) +void GCodeReader::apply_config(const GCodeConfig &config) +{ + m_config = config; + m_extrusion_axis = m_config.get_extrusion_axis()[0]; +} + +void GCodeReader::apply_config(const DynamicPrintConfig &config) { m_config.apply(config, true); m_extrusion_axis = m_config.get_extrusion_axis()[0]; diff --git a/xs/src/libslic3r/GCodeReader.hpp b/xs/src/libslic3r/GCodeReader.hpp index 3ff5f56bb..1792d6cde 100644 --- a/xs/src/libslic3r/GCodeReader.hpp +++ b/xs/src/libslic3r/GCodeReader.hpp @@ -50,7 +50,8 @@ public: callback_t callback; GCodeReader() : X(0), Y(0), Z(0), E(0), F(0), verbose(false), m_extrusion_axis('E') {}; - void apply_config(const PrintConfigBase &config); + void apply_config(const GCodeConfig &config); + void apply_config(const DynamicPrintConfig &config); void parse(const std::string &gcode, callback_t callback); void parse_line(std::string line, callback_t callback); void parse_file(const std::string &file, callback_t callback); diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index eb14c19ec..68e17407e 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -260,7 +260,7 @@ void LayerRegion::process_external_surfaces(const Layer* lower_layer) #ifdef SLIC3R_DEBUG printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); #endif - if (bd.detect_angle(Geometry::deg2rad(this->region()->config.bridge_angle))) { + if (bd.detect_angle(Geometry::deg2rad(this->region()->config.bridge_angle.value))) { bridges[idx_last].bridge_angle = bd.angle; if (this->layer()->object()->config.support_material) { polygons_append(this->bridged, bd.coverage()); diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index 554c119fa..e25ec9526 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -64,7 +64,7 @@ void PlaceholderParser::update_timestamp() void PlaceholderParser::apply_config(const DynamicPrintConfig &config) { for (const t_config_option_key &opt_key : config.keys()) { - const ConfigOptionDef* def = config.def->get(opt_key); + const ConfigOptionDef* def = config.def()->get(opt_key); if (def->multiline || opt_key == "post_process") continue; diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index 667a3b4ea..54faadf84 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -389,7 +389,7 @@ bool Print::apply_config(DynamicPrintConfig config) // handle changes to print config t_config_option_keys print_diff = this->config.diff(config); - this->config.apply(config, print_diff, true); + this->config.apply_only(config, print_diff, true); bool invalidated = this->invalidate_state_by_config_options(print_diff); // handle changes to object config defaults @@ -410,7 +410,7 @@ bool Print::apply_config(DynamicPrintConfig config) } // check whether the new config is different from the current one t_config_option_keys diff = object->config.diff(new_config); - object->config.apply(new_config, diff, true); + object->config.apply_only(new_config, diff, true); invalidated |= object->invalidate_state_by_config_options(diff); } @@ -460,7 +460,7 @@ bool Print::apply_config(DynamicPrintConfig config) if (this_region_config_set) { t_config_option_keys diff = region.config.diff(this_region_config); if (! diff.empty()) { - region.config.apply(this_region_config, diff); + region.config.apply_only(this_region_config, diff); for (PrintObject *object : this->objects) if (region_id < object->region_volumes.size() && ! object->region_volumes[region_id].empty()) invalidated |= object->invalidate_state_by_config_options(diff); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index b8be13234..189fc57e1 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace Slic3r { @@ -221,7 +222,7 @@ PrintConfigDef::PrintConfigDef() def->category = "Infill"; def->tooltip = "Fill pattern for top/bottom infill. This only affects the external visible layer, and not its adjacent solid shells."; def->cli = "external-fill-pattern|solid-fill-pattern=s"; - def->enum_keys_map = ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("concentric"); def->enum_values.push_back("hilbertcurve"); @@ -529,7 +530,7 @@ PrintConfigDef::PrintConfigDef() def->category = "Infill"; def->tooltip = "Fill pattern for general low-density infill."; def->cli = "fill-pattern=s"; - def->enum_keys_map = ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("grid"); def->enum_values.push_back("triangles"); @@ -633,7 +634,7 @@ PrintConfigDef::PrintConfigDef() def->label = "G-code flavor"; def->tooltip = "Some G/M-code commands, including temperature control and others, are not universal. Set this option to your printer's firmware to get a compatible output. The \"No extrusion\" flavor prevents Slic3r from exporting any extrusion value at all."; def->cli = "gcode-flavor=s"; - def->enum_keys_map = ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("reprap"); def->enum_values.push_back("repetier"); def->enum_values.push_back("teacup"); @@ -1136,7 +1137,7 @@ PrintConfigDef::PrintConfigDef() def->category = "Layers and Perimeters"; def->tooltip = "Position of perimeters starting points."; def->cli = "seam-position=s"; - def->enum_keys_map = ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("random"); def->enum_values.push_back("nearest"); def->enum_values.push_back("aligned"); @@ -1458,7 +1459,7 @@ PrintConfigDef::PrintConfigDef() def->category = "Support material"; def->tooltip = "Pattern used to generate support material."; def->cli = "support-material-pattern=s"; - def->enum_keys_map = ConfigOptionEnum::get_enum_values(); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear-grid"); def->enum_values.push_back("honeycomb"); @@ -1538,7 +1539,7 @@ PrintConfigDef::PrintConfigDef() def->readonly = true; def->min = 1; { - unsigned int threads = boost::thread::hardware_concurrency(); + int threads = (unsigned int)boost::thread::hardware_concurrency(); def->default_value = new ConfigOptionInt(threads > 0 ? threads : 2); } @@ -1671,56 +1672,7 @@ PrintConfigDef::PrintConfigDef() def->default_value = new ConfigOptionFloat(0); } -PrintConfigDef print_config_def; - -void -DynamicPrintConfig::normalize() { - if (this->has("extruder")) { - int extruder = this->option("extruder")->getInt(); - this->erase("extruder"); - if (extruder != 0) { - if (!this->has("infill_extruder")) - this->option("infill_extruder", true)->setInt(extruder); - if (!this->has("perimeter_extruder")) - this->option("perimeter_extruder", true)->setInt(extruder); - // Don't propagate the current extruder to support. - // For non-soluble supports, the default "0" extruder means to use the active extruder, - // for soluble supports one certainly does not want to set the extruder to non-soluble. - // if (!this->has("support_material_extruder")) - // this->option("support_material_extruder", true)->setInt(extruder); - // if (!this->has("support_material_interface_extruder")) - // this->option("support_material_interface_extruder", true)->setInt(extruder); - } - } - - if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) - this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); - - if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { - { - // this should be actually done only on the spiral layers instead of all - ConfigOptionBools* opt = this->opt("retract_layer_change", true); - opt->values.assign(opt->values.size(), false); // set all values to false - } - { - this->opt("perimeters", true)->value = 1; - this->opt("top_solid_layers", true)->value = 0; - this->opt("fill_density", true)->value = 0; - } - } -} - - -bool PrintConfigBase::set_deserialize(t_config_option_key opt_key, std::string str, bool append) -{ - this->_handle_legacy(opt_key, str); - return opt_key.empty() ? - // ignore option - true : - ConfigBase::set_deserialize(opt_key, str, append); -} - -void PrintConfigBase::_handle_legacy(t_config_option_key &opt_key, std::string &value) const +void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value) { // handle legacy options if (opt_key == "extrusion_width_ratio" || opt_key == "bottom_layer_speed_ratio" @@ -1783,22 +1735,249 @@ void PrintConfigBase::_handle_legacy(t_config_option_key &opt_key, std::string & return; } - if (!this->def->has(opt_key)) { + if (! print_config_def.has(opt_key)) { //printf("Unknown option %s\n", opt_key.c_str()); opt_key = ""; return; } } -double PrintConfigBase::min_object_distance() const +PrintConfigDef print_config_def; + +void DynamicPrintConfig::normalize() { - double extruder_clearance_radius = this->option("extruder_clearance_radius")->getFloat(); - double duplicate_distance = this->option("duplicate_distance")->getFloat(); + if (this->has("extruder")) { + int extruder = this->option("extruder")->getInt(); + this->erase("extruder"); + if (extruder != 0) { + if (!this->has("infill_extruder")) + this->option("infill_extruder", true)->setInt(extruder); + if (!this->has("perimeter_extruder")) + this->option("perimeter_extruder", true)->setInt(extruder); + // Don't propagate the current extruder to support. + // For non-soluble supports, the default "0" extruder means to use the active extruder, + // for soluble supports one certainly does not want to set the extruder to non-soluble. + // if (!this->has("support_material_extruder")) + // this->option("support_material_extruder", true)->setInt(extruder); + // if (!this->has("support_material_interface_extruder")) + // this->option("support_material_interface_extruder", true)->setInt(extruder); + } + } + + if (!this->has("solid_infill_extruder") && this->has("infill_extruder")) + this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt()); + + if (this->has("spiral_vase") && this->opt("spiral_vase", true)->value) { + { + // this should be actually done only on the spiral layers instead of all + ConfigOptionBools* opt = this->opt("retract_layer_change", true); + opt->values.assign(opt->values.size(), false); // set all values to false + } + { + this->opt("perimeters", true)->value = 1; + this->opt("top_solid_layers", true)->value = 0; + this->opt("fill_density", true)->value = 0; + } + } +} + +std::string DynamicPrintConfig::validate() +{ + // Full print config is initialized from the defaults. + FullPrintConfig fpc; + fpc.apply(*this); + // Verify this print options through the FullPrintConfig. + return fpc.validate(); +} + +double PrintConfig::min_object_distance() const +{ + return PrintConfig::min_object_distance(static_cast(this)); +} + +double PrintConfig::min_object_distance(const ConfigBase *config) +{ + double extruder_clearance_radius = config->option("extruder_clearance_radius")->getFloat(); + double duplicate_distance = config->option("duplicate_distance")->getFloat(); // min object distance is max(duplicate_distance, clearance_radius) - return (this->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) + return (config->option("complete_objects")->getBool() && extruder_clearance_radius > duplicate_distance) ? extruder_clearance_radius : duplicate_distance; } +std::string FullPrintConfig::validate() +{ + // --layer-height + if (this->get_abs_value("layer_height") <= 0) + return "Invalid value for --layer-height"; + if (fabs(fmod(this->get_abs_value("layer_height"), SCALING_FACTOR)) > 1e-4) + return "--layer-height must be a multiple of print resolution"; + + // --first-layer-height + if (this->get_abs_value("first_layer_height") <= 0) + return "Invalid value for --first-layer-height"; + + // --filament-diameter + for (double fd : this->filament_diameter.values) + if (fd < 1) + return "Invalid value for --filament-diameter"; + + // --nozzle-diameter + for (double nd : this->nozzle_diameter.values) + if (nd < 1) + return "Invalid value for --nozzle-diameter"; + + // --perimeters + if (this->perimeters.value < 0) + return "Invalid value for --perimeters"; + + // --solid-layers + if (this->top_solid_layers < 0) + return "Invalid value for --top-solid-layers"; + if (this->bottom_solid_layers < 0) + return "Invalid value for --bottom-solid-layers"; + + if (this->use_firmware_retraction.value && + this->gcode_flavor.value != gcfSmoothie && + this->gcode_flavor.value != gcfRepRap && + this->gcode_flavor.value != gcfMachinekit && + this->gcode_flavor.value != gcfRepetier) + return "--use-firmware-retraction is only supported by Marlin, Smoothie, Repetier and Machinekit firmware"; + + if (this->use_firmware_retraction.value) + for (bool wipe : this->wipe.values) + if (wipe) + return "--use-firmware-retraction is not compatible with --wipe"; + + // --gcode-flavor + if (! print_config_def.get("gcode_flavor")->has_enum_value(this->gcode_flavor.serialize())) + return "Invalid value for --gcode-flavor"; + + // --fill-pattern + if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize())) + return "Invalid value for --fill-pattern"; + + // --external-fill-pattern + if (! print_config_def.get("external_fill_pattern")->has_enum_value(this->external_fill_pattern.serialize())) + return "Invalid value for --external-fill-pattern"; + + // --fill-density + if (fabs(this->fill_density.value - 100.) < EPSILON && + ! print_config_def.get("external_fill_pattern")->has_enum_value(this->fill_pattern.serialize())) + return "The selected fill pattern is not supposed to work at 100% density"; + + // --infill-every-layers + if (this->infill_every_layers < 1) + return "Invalid value for --infill-every-layers"; + + // --skirt-height + if (this->skirt_height < -1) // -1 means as tall as the object + return "Invalid value for --skirt-height"; + + // --bridge-flow-ratio + if (this->bridge_flow_ratio <= 0) + return "Invalid value for --bridge-flow-ratio"; + + // extruder clearance + if (this->extruder_clearance_radius <= 0) + return "Invalid value for --extruder-clearance-radius"; + if (this->extruder_clearance_height <= 0) + return "Invalid value for --extruder-clearance-height"; + + // --extrusion-multiplier + for (float em : this->extrusion_multiplier.values) + if (em <= 0) + return "Invalid value for --extrusion-multiplier"; + + // --default-acceleration + if ((this->perimeter_acceleration != 0. || this->infill_acceleration != 0. || this->bridge_acceleration != 0. || this->first_layer_acceleration != 0.) && + this->default_acceleration == 0.) + return "Invalid zero value for --default-acceleration when using other acceleration settings"; + + // --spiral-vase + if (this->spiral_vase) { + // Note that we might want to have more than one perimeter on the bottom + // solid layers. + if (this->perimeters > 1) + return "Can't make more than one perimeter when spiral vase mode is enabled"; + else if (this->perimeters < 1) + return "Can't make less than one perimeter when spiral vase mode is enabled"; + if (this->fill_density > 0) + return "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0"; + if (this->top_solid_layers > 0) + return "Spiral vase mode is not compatible with top solid layers"; + if (this->support_material || this->support_material_enforce_layers > 0) + return "Spiral vase mode is not compatible with support material"; + } + + // extrusion widths + { + double max_nozzle_diameter = 0.; + for (double dmr : this->nozzle_diameter.values) + max_nozzle_diameter = std::max(max_nozzle_diameter, dmr); + const char *widths[] = { "external_perimeter", "perimeter", "infill", "solid_infill", "top_infill", "support_material", "first_layer" }; + for (size_t i = 0; i < sizeof(widths) / sizeof(widths[i]); ++ i) { + std::string key(widths[i]); + key += "_extrusion_width"; + if (this->get_abs_value(key, max_nozzle_diameter) > 10. * max_nozzle_diameter) + return std::string("Invalid extrusion width (too large): ") + key; + } + } + + // Out of range validation of numeric values. + for (const std::string &opt_key : this->keys()) { + const ConfigOption *opt = this->optptr(opt_key); + assert(opt != nullptr); + const ConfigOptionDef *optdef = print_config_def.get(opt_key); + assert(optdef != nullptr); + bool out_of_range = false; + switch (opt->type()) { + case coFloat: + case coPercent: + case coFloatOrPercent: + { + auto *fopt = static_cast(opt); + out_of_range = fopt->value < optdef->min || fopt->value > optdef->max; + break; + } + case coFloats: + case coPercents: + for (double v : static_cast(opt)->values) + if (v < optdef->min || v > optdef->max) { + out_of_range = true; + break; + } + break; + case coInt: + { + auto *iopt = static_cast(opt); + out_of_range = iopt->value < optdef->min || iopt->value > optdef->max; + break; + } + case coInts: + for (int v : static_cast(opt)->values) + if (v < optdef->min || v > optdef->max) { + out_of_range = true; + break; + } + break; + default:; + } + if (out_of_range) + return std::string("Value out of range: " + opt_key); + } + + // The configuration is valid. + return ""; +} + +// Declare the static caches for each StaticPrintConfig derived class. +StaticPrintConfig::StaticCache PrintObjectConfig::s_cache; +StaticPrintConfig::StaticCache PrintRegionConfig::s_cache; +StaticPrintConfig::StaticCache GCodeConfig::s_cache; +StaticPrintConfig::StaticCache PrintConfig::s_cache; +StaticPrintConfig::StaticCache HostConfig::s_cache; +StaticPrintConfig::StaticCache FullPrintConfig::s_cache; + } diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 26e486058..1f3354923 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -5,7 +5,6 @@ // during the slicing and the g-code generation. // // The classes derived from StaticPrintConfig form a following hierarchy. -// Virtual inheritance is used for some of the parent objects. // // FullPrintConfig // PrintObjectConfig @@ -21,8 +20,6 @@ #include "libslic3r.h" #include "Config.hpp" -#define OPT_PTR(KEY) if (opt_key == #KEY) return &this->KEY - namespace Slic3r { enum GCodeFlavor { @@ -46,66 +43,76 @@ enum FilamentType { ftPLA, ftABS, ftPET, ftHIPS, ftFLEX, ftSCAFF, ftEDGE, ftNGEN, ftPVA }; -template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { - t_config_enum_values keys_map; - keys_map["reprap"] = gcfRepRap; - keys_map["repetier"] = gcfRepetier; - keys_map["teacup"] = gcfTeacup; - keys_map["makerware"] = gcfMakerWare; - keys_map["sailfish"] = gcfSailfish; - keys_map["smoothie"] = gcfSmoothie; - keys_map["mach3"] = gcfMach3; - keys_map["machinekit"] = gcfMachinekit; - keys_map["no-extrusion"] = gcfNoExtrusion; +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["reprap"] = gcfRepRap; + keys_map["repetier"] = gcfRepetier; + keys_map["teacup"] = gcfTeacup; + keys_map["makerware"] = gcfMakerWare; + keys_map["sailfish"] = gcfSailfish; + keys_map["smoothie"] = gcfSmoothie; + keys_map["mach3"] = gcfMach3; + keys_map["machinekit"] = gcfMachinekit; + keys_map["no-extrusion"] = gcfNoExtrusion; + } return keys_map; } -template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { - t_config_enum_values keys_map; - keys_map["rectilinear"] = ipRectilinear; - keys_map["grid"] = ipGrid; - keys_map["triangles"] = ipTriangles; - keys_map["stars"] = ipStars; - keys_map["cubic"] = ipCubic; - keys_map["line"] = ipLine; - keys_map["concentric"] = ipConcentric; - keys_map["honeycomb"] = ipHoneycomb; - keys_map["3dhoneycomb"] = ip3DHoneycomb; - keys_map["hilbertcurve"] = ipHilbertCurve; - keys_map["archimedeanchords"] = ipArchimedeanChords; - keys_map["octagramspiral"] = ipOctagramSpiral; +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["rectilinear"] = ipRectilinear; + keys_map["grid"] = ipGrid; + keys_map["triangles"] = ipTriangles; + keys_map["stars"] = ipStars; + keys_map["cubic"] = ipCubic; + keys_map["line"] = ipLine; + keys_map["concentric"] = ipConcentric; + keys_map["honeycomb"] = ipHoneycomb; + keys_map["3dhoneycomb"] = ip3DHoneycomb; + keys_map["hilbertcurve"] = ipHilbertCurve; + keys_map["archimedeanchords"] = ipArchimedeanChords; + keys_map["octagramspiral"] = ipOctagramSpiral; + } return keys_map; } -template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { - t_config_enum_values keys_map; - keys_map["rectilinear"] = smpRectilinear; - keys_map["rectilinear-grid"] = smpRectilinearGrid; - keys_map["honeycomb"] = smpHoneycomb; - keys_map["pillars"] = smpPillars; +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["rectilinear"] = smpRectilinear; + keys_map["rectilinear-grid"] = smpRectilinearGrid; + keys_map["honeycomb"] = smpHoneycomb; + keys_map["pillars"] = smpPillars; + } return keys_map; } -template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { - t_config_enum_values keys_map; - keys_map["random"] = spRandom; - keys_map["nearest"] = spNearest; - keys_map["aligned"] = spAligned; - keys_map["rear"] = spRear; +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["random"] = spRandom; + keys_map["nearest"] = spNearest; + keys_map["aligned"] = spAligned; + keys_map["rear"] = spRear; + } return keys_map; } -template<> inline t_config_enum_values ConfigOptionEnum::get_enum_values() { - t_config_enum_values keys_map; - keys_map["PLA"] = ftPLA; - keys_map["ABS"] = ftABS; - keys_map["PET"] = ftPET; - keys_map["HIPS"] = ftHIPS; - keys_map["FLEX"] = ftFLEX; - keys_map["SCAFF"] = ftSCAFF; - keys_map["EDGE"] = ftEDGE; - keys_map["NGEN"] = ftNGEN; - keys_map["PVA"] = ftPVA; +template<> inline t_config_enum_values& ConfigOptionEnum::get_enum_values() { + static t_config_enum_values keys_map; + if (keys_map.empty()) { + keys_map["PLA"] = ftPLA; + keys_map["ABS"] = ftABS; + keys_map["PET"] = ftPET; + keys_map["HIPS"] = ftHIPS; + keys_map["FLEX"] = ftFLEX; + keys_map["SCAFF"] = ftSCAFF; + keys_map["EDGE"] = ftEDGE; + keys_map["NGEN"] = ftNGEN; + keys_map["PVA"] = ftPVA; + } return keys_map; } @@ -113,60 +120,177 @@ template<> inline t_config_enum_values ConfigOptionEnum::get_enum_ // Does not store the actual values, but defines default values. class PrintConfigDef : public ConfigDef { - public: +public: PrintConfigDef(); + + static void handle_legacy(t_config_option_key &opt_key, std::string &value); }; // The one and only global definition of SLic3r configuration options. // This definition is constant. extern PrintConfigDef print_config_def; -// Slic3r configuration storage with print_config_def assigned. -class PrintConfigBase : public virtual ConfigBase -{ -public: - PrintConfigBase() { - this->def = &print_config_def; - }; - - bool set_deserialize(t_config_option_key opt_key, std::string str, bool append = false); - - double min_object_distance() const; - -protected: - void _handle_legacy(t_config_option_key &opt_key, std::string &value) const; -}; - // Slic3r dynamic configuration, used to override the configuration // per object, per modification volume or per printing material. // The dynamic configuration is also used to store user modifications of the print global parameters, // so the modified configuration values may be diffed against the active configuration // to invalidate the proper slicing resp. g-code generation processing steps. // This object is mapped to Perl as Slic3r::Config. -class DynamicPrintConfig : public PrintConfigBase, public DynamicConfig +class DynamicPrintConfig : public DynamicConfig { - public: - DynamicPrintConfig() : PrintConfigBase(), DynamicConfig() {}; - void normalize(); +public: + DynamicPrintConfig() {} + DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} + + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef* def() const override { return &print_config_def; } + + void normalize(); + + // Validate the PrintConfig. Returns an empty string on success, otherwise an error message is returned. + std::string validate(); }; template void normalize_and_apply_config(CONFIG &dst, const DynamicPrintConfig &src) { - DynamicPrintConfig src_normalized = src; + DynamicPrintConfig src_normalized(src); src_normalized.normalize(); dst.apply(src_normalized, true); } -class StaticPrintConfig : public PrintConfigBase, public StaticConfig +class StaticPrintConfig : public StaticConfig { +public: + StaticPrintConfig() {} + + // Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. + const ConfigDef* def() const override { return &print_config_def; } + +protected: + // Verify whether the opt_key has not been obsoleted or renamed. + // Both opt_key and value may be modified by handle_legacy(). + // If the opt_key is no more valid in this version of Slic3r, opt_key is cleared by handle_legacy(). + // handle_legacy() is called internally by set_deserialize(). + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override + { PrintConfigDef::handle_legacy(opt_key, value); } + + // Internal class for keeping a dynamic map to static options. + class StaticCacheBase + { public: - StaticPrintConfig() : PrintConfigBase(), StaticConfig() {}; + // To be called during the StaticCache setup. + // Add one ConfigOption into m_map_name_to_offset. + template + void opt_add(const std::string &name, const char *base_ptr, const T &opt) + { + assert(m_map_name_to_offset.find(name) == m_map_name_to_offset.end()); + m_map_name_to_offset[name] = (const char*)&opt - base_ptr; + } + + protected: + std::map m_map_name_to_offset; + }; + + // Parametrized by the type of the topmost class owning the options. + template + class StaticCache : public StaticCacheBase + { + public: + // Calling the constructor of m_defaults with 0 forces m_defaults to not run the initialization. + StaticCache() : m_defaults(nullptr) {} + ~StaticCache() { delete m_defaults; m_defaults = nullptr; } + + bool initialized() const { return ! m_keys.empty(); } + + ConfigOption* optptr(const std::string &name, T *owner) const + { + const auto it = m_map_name_to_offset.find(name); + return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((char*)owner + it->second); + } + + const ConfigOption* optptr(const std::string &name, const T *owner) const + { + const auto it = m_map_name_to_offset.find(name); + return (it == m_map_name_to_offset.end()) ? nullptr : reinterpret_cast((const char*)owner + it->second); + } + + const std::vector& keys() const { return m_keys; } + const T& defaults() const { return *m_defaults; } + + // To be called during the StaticCache setup. + // Collect option keys from m_map_name_to_offset, + // assign default values to m_defaults. + void finalize(T *defaults, const ConfigDef *defs) + { + assert(defs != nullptr); + m_defaults = defaults; + m_keys.clear(); + m_keys.reserve(m_map_name_to_offset.size()); + for (const auto &kvp : defs->options) { + // Find the option given the option name kvp.first by an offset from (char*)m_defaults. + ConfigOption *opt = this->optptr(kvp.first, m_defaults); + if (opt == nullptr) + // This option is not defined by the ConfigBase of type T. + continue; + m_keys.emplace_back(kvp.first); + const ConfigOptionDef *def = defs->get(kvp.first); + assert(def != nullptr); + if (def->default_value != nullptr) + opt->set(def->default_value); + } + } + + private: + T *m_defaults; + std::vector m_keys; + }; }; +#define STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ +public: \ + /* Overrides ConfigBase::optptr(). Find ando/or create a ConfigOption instance for a given name. */ \ + ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override \ + { return s_cache.optptr(opt_key, this); } \ + /* Overrides ConfigBase::keys(). Collect names of all configuration values maintained by this configuration store. */ \ + t_config_option_keys keys() const override { return s_cache.keys(); } \ + static const CLASS_NAME& defaults() { initialize_cache(); return s_cache.defaults(); } \ +protected: \ + static void initialize_cache() \ + { \ + if (! s_cache.initialized()) { \ + CLASS_NAME *inst = new CLASS_NAME(1); \ + inst->initialize(s_cache, (const char*)inst); \ + s_cache.finalize(inst, inst->def()); \ + } \ + } \ + /* Cache object holding a key/option map, a list of option keys and a copy of this static config initialized with the defaults. */ \ + static StaticCache s_cache; + +#define STATIC_PRINT_CONFIG_CACHE(CLASS_NAME) \ + STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ +public: \ + /* Public default constructor will initialize the key/option cache and the default object copy if needed. */ \ + CLASS_NAME() { initialize_cache(); *this = s_cache.defaults(); } \ +protected: \ + /* Protected constructor to be called when compounded. */ \ + CLASS_NAME(int) {} + +#define STATIC_PRINT_CONFIG_CACHE_DERIVED(CLASS_NAME) \ + STATIC_PRINT_CONFIG_CACHE_BASE(CLASS_NAME) \ +public: \ + /* Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here. */ \ + const ConfigDef* def() const override { return &print_config_def; } \ + /* Handle legacy and obsoleted config keys */ \ + void handle_legacy(t_config_option_key &opt_key, std::string &value) const override \ + { PrintConfigDef::handle_legacy(opt_key, value); } + +#define OPT_PTR(KEY) cache.opt_add(#KEY, base_ptr, this->KEY) + // This object is mapped to Perl as Slic3r::Config::PrintObject. -class PrintObjectConfig : public virtual StaticPrintConfig +class PrintObjectConfig : public StaticPrintConfig { + STATIC_PRINT_CONFIG_CACHE(PrintObjectConfig) public: ConfigOptionBool clip_multipart_objects; ConfigOptionBool dont_support_bridges; @@ -201,13 +325,9 @@ public: ConfigOptionFloatOrPercent support_material_xy_spacing; ConfigOptionFloat xy_size_compensation; - PrintObjectConfig(bool initialize = true) : StaticPrintConfig() { - if (initialize) - this->set_defaults(); - } - - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { - UNUSED(create); +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { OPT_PTR(clip_multipart_objects); OPT_PTR(dont_support_bridges); OPT_PTR(elefant_foot_compensation); @@ -240,14 +360,13 @@ public: OPT_PTR(support_material_threshold); OPT_PTR(support_material_with_sheath); OPT_PTR(xy_size_compensation); - - return NULL; - }; + } }; // This object is mapped to Perl as Slic3r::Config::PrintRegion. -class PrintRegionConfig : public virtual StaticPrintConfig +class PrintRegionConfig : public StaticPrintConfig { + STATIC_PRINT_CONFIG_CACHE(PrintRegionConfig) public: ConfigOptionFloat bridge_angle; ConfigOptionInt bottom_solid_layers; @@ -283,14 +402,10 @@ public: ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; ConfigOptionFloatOrPercent top_solid_infill_speed; - - PrintRegionConfig(bool initialize = true) : StaticPrintConfig() { - if (initialize) - this->set_defaults(); - } - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { - UNUSED(create); +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { OPT_PTR(bridge_angle); OPT_PTR(bottom_solid_layers); OPT_PTR(bridge_flow_ratio); @@ -325,14 +440,13 @@ public: OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_solid_infill_speed); OPT_PTR(top_solid_layers); - - return NULL; - }; + } }; // This object is mapped to Perl as Slic3r::Config::GCode. -class GCodeConfig : public virtual StaticPrintConfig +class GCodeConfig : public StaticPrintConfig { + STATIC_PRINT_CONFIG_CACHE(GCodeConfig) public: ConfigOptionString before_layer_gcode; ConfigOptionFloats deretract_speed; @@ -372,13 +486,16 @@ public: ConfigOptionBool use_volumetric_e; ConfigOptionBool variable_layer_height; - GCodeConfig(bool initialize = true) : StaticPrintConfig() { - if (initialize) - this->set_defaults(); + std::string get_extrusion_axis() const + { + return + ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) ? "A" : + (this->gcode_flavor.value == gcfNoExtrusion) ? "" : this->extrusion_axis.value; } - - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { - UNUSED(create); + +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { OPT_PTR(before_layer_gcode); OPT_PTR(deretract_speed); OPT_PTR(end_gcode); @@ -416,25 +533,18 @@ public: OPT_PTR(use_relative_e_distances); OPT_PTR(use_volumetric_e); OPT_PTR(variable_layer_height); - return NULL; - }; - - std::string get_extrusion_axis() const - { - if ((this->gcode_flavor.value == gcfMach3) || (this->gcode_flavor.value == gcfMachinekit)) { - return "A"; - } else if (this->gcode_flavor.value == gcfNoExtrusion) { - return ""; - } else { - return this->extrusion_axis.value; - } - }; + } }; // This object is mapped to Perl as Slic3r::Config::Print. class PrintConfig : public GCodeConfig { + STATIC_PRINT_CONFIG_CACHE_DERIVED(PrintConfig) + PrintConfig() : GCodeConfig(0) { initialize_cache(); *this = s_cache.defaults(); } public: + double min_object_distance() const; + static double min_object_distance(const ConfigBase *config); + ConfigOptionBool avoid_crossing_perimeters; ConfigOptionPoints bed_shape; ConfigOptionInts bed_temperature; @@ -494,12 +604,11 @@ public: ConfigOptionFloat wipe_tower_per_color_wipe; ConfigOptionFloat z_offset; - PrintConfig(bool initialize = true) : GCodeConfig(false) { - if (initialize) - this->set_defaults(); - } - - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { +protected: + PrintConfig(int) : GCodeConfig(1) {} + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + this->GCodeConfig::initialize(cache, base_ptr); OPT_PTR(avoid_crossing_perimeters); OPT_PTR(bed_shape); OPT_PTR(bed_temperature); @@ -558,64 +667,58 @@ public: OPT_PTR(wipe_tower_width); OPT_PTR(wipe_tower_per_color_wipe); OPT_PTR(z_offset); - - // look in parent class - ConfigOption* opt; - if ((opt = GCodeConfig::optptr(opt_key, create)) != NULL) return opt; - - return NULL; - }; + } }; -class HostConfig : public virtual StaticPrintConfig +class HostConfig : public StaticPrintConfig { + STATIC_PRINT_CONFIG_CACHE(HostConfig) public: ConfigOptionString octoprint_host; ConfigOptionString octoprint_apikey; ConfigOptionString serial_port; ConfigOptionInt serial_speed; - HostConfig(bool initialize = true) : StaticPrintConfig() { - if (initialize) - this->set_defaults(); - } - - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { - UNUSED(create); +protected: + void initialize(StaticCacheBase &cache, const char *base_ptr) + { OPT_PTR(octoprint_host); OPT_PTR(octoprint_apikey); OPT_PTR(serial_port); OPT_PTR(serial_speed); - - return NULL; - }; + } }; // This object is mapped to Perl as Slic3r::Config::Full. -class FullPrintConfig - : public PrintObjectConfig, public PrintRegionConfig, public PrintConfig, public HostConfig +class FullPrintConfig : + public PrintObjectConfig, + public PrintRegionConfig, + public PrintConfig, + public HostConfig { - public: - FullPrintConfig(bool initialize = true) : - PrintObjectConfig(false), - PrintRegionConfig(false), - PrintConfig(false), - HostConfig(false) - { - if (initialize) - this->set_defaults(); - } + STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig) + FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache.defaults(); } - virtual ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) { - ConfigOption* opt; - if ((opt = PrintObjectConfig::optptr(opt_key, create)) != NULL) return opt; - if ((opt = PrintRegionConfig::optptr(opt_key, create)) != NULL) return opt; - if ((opt = PrintConfig::optptr(opt_key, create)) != NULL) return opt; - if ((opt = HostConfig::optptr(opt_key, create)) != NULL) return opt; - return NULL; - }; +public: + // Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned. + std::string validate(); +protected: + // Protected constructor to be called to initialize ConfigCache::m_default. + FullPrintConfig(int) : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) {} + void initialize(StaticCacheBase &cache, const char *base_ptr) + { + this->PrintObjectConfig::initialize(cache, base_ptr); + this->PrintRegionConfig::initialize(cache, base_ptr); + this->PrintConfig ::initialize(cache, base_ptr); + this->HostConfig ::initialize(cache, base_ptr); + } }; +#undef STATIC_PRINT_CONFIG_CACHE +#undef STATIC_PRINT_CONFIG_CACHE_BASE +#undef STATIC_PRINT_CONFIG_CACHE_DERIVED +#undef OPT_PTR + } #endif diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 7b3d3ec0b..7a05cf998 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -948,7 +948,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_ slices_margin_cached, // How much to offset the extracted contour outside of the grid. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle)); + Geometry::deg2rad(m_object_config->support_material_angle.value)); // 1) infill polygons, expand them by half the extrusion width + a tiny bit of extra. new_layer.polygons = support_grid_pattern.extract_support(m_support_material_flow.scaled_spacing()/2 + 5); // 2) Contact polygons will be projected down. To keep the interface and base layers to grow, return a contour a tiny bit smaller than the grid cells. @@ -1155,7 +1155,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta trimming, // How much to offset the extracted contour outside of the grid. m_object_config->support_material_spacing.value + m_support_material_flow.spacing(), - Geometry::deg2rad(m_object_config->support_material_angle)); + Geometry::deg2rad(m_object_config->support_material_angle.value)); tbb::task_group task_group_inner; // 1) Cache the slice of a support volume. The support volume is expanded by 1/2 of support material flow spacing // to allow a placement of suppot zig-zag snake along the grid lines. @@ -2471,8 +2471,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width()); loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0; - float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle)); - float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle + 90.)); + float base_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value)); + float interface_angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); coordf_t interface_spacing = m_object_config->support_material_interface_spacing.value + m_support_material_interface_flow.spacing(); coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing); coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing(); @@ -2763,7 +2763,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( if (base_layer.layer->bottom_z < EPSILON) { // Base flange (the 1st layer). filler = filler_interface.get(); - filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle + 90.)); + filler->angle = Geometry::deg2rad(float(m_object_config->support_material_angle.value + 90.)); density = 0.5f; flow = m_first_layer_flow; // use the proper spacing for first layer as we don't need to align diff --git a/xs/src/perlglue.cpp b/xs/src/perlglue.cpp index 3b7a62299..e7f03d1f5 100644 --- a/xs/src/perlglue.cpp +++ b/xs/src/perlglue.cpp @@ -77,7 +77,7 @@ ConfigBase__get(ConfigBase* THIS, const t_config_option_key &opt_key) { ConfigOption* opt = THIS->option(opt_key); if (opt == NULL) return &PL_sv_undef; - const ConfigOptionDef* def = THIS->def->get(opt_key); + const ConfigOptionDef* def = THIS->def()->get(opt_key); return ConfigOption_to_SV(*opt, *def); } @@ -155,7 +155,7 @@ ConfigBase__get_at(ConfigBase* THIS, const t_config_option_key &opt_key, size_t ConfigOption* opt = THIS->option(opt_key); if (opt == NULL) return &PL_sv_undef; - const ConfigOptionDef* def = THIS->def->get(opt_key); + const ConfigOptionDef* def = THIS->def()->get(opt_key); if (def->type == coFloats || def->type == coPercents) { ConfigOptionFloats* optv = dynamic_cast(opt); return newSVnv(optv->get_at(i)); @@ -183,7 +183,7 @@ ConfigBase__set(ConfigBase* THIS, const t_config_option_key &opt_key, SV* value) ConfigOption* opt = THIS->option(opt_key, true); if (opt == NULL) CONFESS("Trying to set non-existing option"); - const ConfigOptionDef* def = THIS->def->get(opt_key); + const ConfigOptionDef* def = THIS->def()->get(opt_key); if (def->type == coFloat) { if (!looks_like_number(value)) return false; ConfigOptionFloat* optv = dynamic_cast(opt); @@ -297,7 +297,7 @@ ConfigBase__set_ifndef(ConfigBase* THIS, const t_config_option_key &opt_key, SV* bool StaticConfig__set(StaticConfig* THIS, const t_config_option_key &opt_key, SV* value) { - const ConfigOptionDef* optdef = THIS->def->get(opt_key); + const ConfigOptionDef* optdef = THIS->def()->get(opt_key); if (!optdef->shortcut.empty()) { for (std::vector::const_iterator it = optdef->shortcut.begin(); it != optdef->shortcut.end(); ++it) { if (!StaticConfig__set(THIS, *it, value)) return false; diff --git a/xs/src/slic3r/GUI/Preset.cpp b/xs/src/slic3r/GUI/Preset.cpp index ea48cd1f5..0accb51a1 100644 --- a/xs/src/slic3r/GUI/Preset.cpp +++ b/xs/src/slic3r/GUI/Preset.cpp @@ -23,7 +23,7 @@ DynamicPrintConfig& Preset::load(const std::vector &keys) { // Set the configuration from the defaults. Slic3r::FullPrintConfig defaults; - this->config.apply(defaults, keys.empty() ? defaults.keys() : keys); + this->config.apply_only(defaults, keys.empty() ? defaults.keys() : keys); if (! this->is_default) { // Load the preset file, apply preset values on top of defaults. diff --git a/xs/xsp/Config.xsp b/xs/xsp/Config.xsp index 368746de4..0e76b4cf1 100644 --- a/xs/xsp/Config.xsp +++ b/xs/xsp/Config.xsp @@ -37,7 +37,7 @@ void erase(t_config_option_key opt_key); void normalize(); %name{setenv} void setenv_(); - double min_object_distance(); + double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; %name{_load} void load(std::string file); %name{_load_from_gcode} void load_from_gcode(std::string input_file) %code%{ @@ -47,20 +47,20 @@ croak("Error extracting configuration from a g-code %s:\n%s\n", input_file.c_str(), e.what()); } %}; - %name{_save} void save(std::string file); + void save(std::string file); }; %name{Slic3r::Config::Static} class StaticPrintConfig { static StaticPrintConfig* new_GCodeConfig() - %code{% RETVAL = new GCodeConfig (); %}; + %code{% RETVAL = new GCodeConfig(); %}; static StaticPrintConfig* new_PrintConfig() - %code{% RETVAL = new PrintConfig (); %}; + %code{% RETVAL = new PrintConfig(); %}; static StaticPrintConfig* new_PrintObjectConfig() - %code{% RETVAL = new PrintObjectConfig (); %}; + %code{% RETVAL = new PrintObjectConfig(); %}; static StaticPrintConfig* new_PrintRegionConfig() - %code{% RETVAL = new PrintRegionConfig (); %}; + %code{% RETVAL = new PrintRegionConfig(); %}; static StaticPrintConfig* new_FullPrintConfig() - %code{% RETVAL = new FullPrintConfig (); %}; + %code{% RETVAL = static_cast(new FullPrintConfig()); %}; ~StaticPrintConfig(); bool has(t_config_option_key opt_key); SV* as_hash() @@ -93,10 +93,10 @@ } %}; %name{setenv} void setenv_(); - double min_object_distance(); + double min_object_distance() %code{% RETVAL = PrintConfig::min_object_distance(THIS); %}; %name{_load} void load(std::string file); %name{_load_from_gcode} void load_from_gcode(std::string file); - %name{_save} void save(std::string file); + void save(std::string file); }; %package{Slic3r::Config};