diff options
author | Enrico Turri <enricoturri@seznam.cz> | 2017-12-11 14:01:30 +0300 |
---|---|---|
committer | Enrico Turri <enricoturri@seznam.cz> | 2017-12-11 14:01:30 +0300 |
commit | 50a45949d1a2878e114cd5e8728275c2586a60e2 (patch) | |
tree | 3f7fa3dbd70d5b6b2bac0b6b3c5226a0fa775810 /xs/src/libslic3r | |
parent | bea9628be08d9c2be9ba49c7538112d39b1d6fa8 (diff) | |
parent | 19388285205ff46379ce0f9b0291aff1badd6568 (diff) |
merge with master
Diffstat (limited to 'xs/src/libslic3r')
28 files changed, 2183 insertions, 888 deletions
diff --git a/xs/src/libslic3r/BoundingBox.cpp b/xs/src/libslic3r/BoundingBox.cpp index 08be120b6..46a43ca69 100644 --- a/xs/src/libslic3r/BoundingBox.cpp +++ b/xs/src/libslic3r/BoundingBox.cpp @@ -7,7 +7,8 @@ namespace Slic3r { template <class PointClass> BoundingBoxBase<PointClass>::BoundingBoxBase(const std::vector<PointClass> &points) { - if (points.empty()) CONFESS("Empty point set supplied to BoundingBoxBase constructor"); + if (points.empty()) + CONFESS("Empty point set supplied to BoundingBoxBase constructor"); typename std::vector<PointClass>::const_iterator it = points.begin(); this->min.x = this->max.x = it->x; this->min.y = this->max.y = it->y; @@ -26,7 +27,8 @@ template <class PointClass> BoundingBox3Base<PointClass>::BoundingBox3Base(const std::vector<PointClass> &points) : BoundingBoxBase<PointClass>(points) { - if (points.empty()) CONFESS("Empty point set supplied to BoundingBox3Base constructor"); + if (points.empty()) + CONFESS("Empty point set supplied to BoundingBox3Base constructor"); typename std::vector<PointClass>::const_iterator it = points.begin(); this->min.z = this->max.z = it->z; for (++it; it != points.end(); ++it) { @@ -39,9 +41,10 @@ template BoundingBox3Base<Pointf3>::BoundingBox3Base(const std::vector<Pointf3> BoundingBox::BoundingBox(const Lines &lines) { Points points; - for (Lines::const_iterator line = lines.begin(); line != lines.end(); ++line) { - points.push_back(line->a); - points.push_back(line->b); + points.reserve(lines.size()); + for (const Line &line : lines) { + points.emplace_back(line.a); + points.emplace_back(line.b); } *this = BoundingBox(points); } @@ -190,9 +193,9 @@ BoundingBox3Base<PointClass>::size() const } template Pointf3 BoundingBox3Base<Pointf3>::size() const; -template <class PointClass> double -BoundingBoxBase<PointClass>::radius() const +template <class PointClass> double BoundingBoxBase<PointClass>::radius() const { + assert(this->defined); double x = this->max.x - this->min.x; double y = this->max.y - this->min.y; return 0.5 * sqrt(x*x+y*y); @@ -200,8 +203,7 @@ BoundingBoxBase<PointClass>::radius() const template double BoundingBoxBase<Point>::radius() const; template double BoundingBoxBase<Pointf>::radius() const; -template <class PointClass> double -BoundingBox3Base<PointClass>::radius() const +template <class PointClass> double BoundingBox3Base<PointClass>::radius() const { double x = this->max.x - this->min.x; double y = this->max.y - this->min.y; diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index b8954cc07..728ed608b 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -1,6 +1,6 @@ #include "Config.hpp" +#include "Utils.hpp" #include <assert.h> -#include <ctime> #include <fstream> #include <iostream> #include <exception> // std::runtime_error @@ -16,7 +16,6 @@ #include <boost/nowide/cenv.hpp> #include <boost/nowide/fstream.hpp> #include <boost/property_tree/ini_parser.hpp> -#include <boost/property_tree/ptree.hpp> #include <string.h> #if defined(_WIN32) && !defined(setenv) && defined(_putenv_s) @@ -235,13 +234,16 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con { 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); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *optdef = def->get(opt_key); if (optdef == nullptr) { // If we didn't find an option, look for any other option having this as an alias. - for (const auto &opt : this->def()->options) { + for (const auto &opt : def->options) { for (const t_config_option_key &opt_key2 : opt.second.aliases) { if (opt_key2 == opt_key) { - opt_key = opt_key2; + opt_key = opt.first; optdef = &opt.second; break; } @@ -278,10 +280,16 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const return static_cast<const ConfigOptionFloat*>(raw_opt)->value; if (raw_opt->type() == coFloatOrPercent) { // Get option definition. - const ConfigOptionDef *def = this->def()->get(opt_key); - assert(def != nullptr); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *opt_def = def->get(opt_key); + assert(opt_def != nullptr); // Compute absolute value over the absolute value of the base option. - return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(def->ratio_over)); + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + return opt_def->ratio_over.empty() ? 0. : + static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over)); } throw std::runtime_error("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()"); } @@ -321,11 +329,23 @@ void ConfigBase::setenv_() void ConfigBase::load(const std::string &file) { - namespace pt = boost::property_tree; - pt::ptree tree; + if (boost::iends_with(file, ".gcode") || boost::iends_with(file, ".g")) + this->load_from_gcode(file); + else + this->load_from_ini(file); +} + +void ConfigBase::load_from_ini(const std::string &file) +{ + boost::property_tree::ptree tree; boost::nowide::ifstream ifs(file); - pt::read_ini(ifs, tree); - for (const pt::ptree::value_type &v : tree) { + boost::property_tree::read_ini(ifs, tree); + this->load(tree); +} + +void ConfigBase::load(const boost::property_tree::ptree &tree) +{ + for (const boost::property_tree::ptree::value_type &v : tree) { try { t_config_option_key opt_key = v.first; this->set_deserialize(opt_key, v.second.get_value<std::string>()); @@ -414,18 +434,24 @@ void ConfigBase::save(const std::string &file) const { boost::nowide::ofstream c; c.open(file, std::ios::out | std::ios::trunc); - { - std::time_t now; - time(&now); - char buf[sizeof "0000-00-00 00:00:00"]; - strftime(buf, sizeof(buf), "%F %T", gmtime(&now)); - c << "# generated by Slic3r " << SLIC3R_VERSION << " on " << buf << std::endl; - } + c << "# " << Slic3r::header_slic3r_generated() << std::endl; for (const std::string &opt_key : this->keys()) c << opt_key << " = " << this->serialize(opt_key) << std::endl; c.close(); } +bool DynamicConfig::operator==(const DynamicConfig &rhs) const +{ + t_options_map::const_iterator it1 = this->options.begin(); + t_options_map::const_iterator it1_end = this->options.end(); + t_options_map::const_iterator it2 = rhs.options.begin(); + t_options_map::const_iterator it2_end = rhs.options.end(); + for (; it1 != it1_end && it2 != it2_end; ++ it1, ++ it2) + if (*it1->second != *it2->second) + return false; + return it1 == it1_end && it2 == it2_end; +} + ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool create) { t_options_map::iterator it = options.find(opt_key); @@ -436,7 +462,10 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre // 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); + const ConfigDef *def = this->def(); + if (def == nullptr) + throw NoDefinitionException(); + const ConfigOptionDef *optdef = 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(). diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index e4968a24b..c203f3be2 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -13,6 +13,8 @@ #include "libslic3r.h" #include "Point.hpp" +#include <boost/property_tree/ptree.hpp> + namespace Slic3r { // Name of the configuration option. @@ -27,41 +29,42 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector< // Type of a configuration value. enum ConfigOptionType { - coNone, + coVectorType = 0x4000, + coNone = 0, // single float - coFloat, + coFloat = 1, // vector of floats - coFloats, + coFloats = coFloat + coVectorType, // single int - coInt, + coInt = 2, // vector of ints - coInts, + coInts = coInt + coVectorType, // single string - coString, + coString = 3, // vector of strings - coStrings, + coStrings = coString + coVectorType, // percent value. Currently only used for infill. - coPercent, + coPercent = 4, // percents value. Currently used for retract before wipe only. - coPercents, + coPercents = coPercent + coVectorType, // a fraction or an absolute value - coFloatOrPercent, + coFloatOrPercent = 5, // single 2d point. Currently not used. - coPoint, + coPoint = 6, // vector of 2d points. Currently used for the definition of the print bed and for the extruder offsets. - coPoints, + coPoints = coPoint + coVectorType, // single boolean value - coBool, + coBool = 7, // vector of boolean values - coBools, + coBools = coBool + coVectorType, // a generic enum - coEnum, + coEnum = 8, }; // A generic value of a configuration option. class ConfigOption { public: - virtual ~ConfigOption() {}; + virtual ~ConfigOption() {} virtual ConfigOptionType type() const = 0; virtual std::string serialize() const = 0; @@ -75,8 +78,13 @@ public: 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); } + bool is_scalar() const { return (int(this->type()) & int(coVectorType)) == 0; } + bool is_vector() const { return ! this->is_scalar(); } }; +typedef ConfigOption* ConfigOptionPtr; +typedef const ConfigOption* ConfigOptionConstPtr; + // Value of a single valued option (bool, int, float, string, point, enum) template <class T> class ConfigOptionSingle : public ConfigOption { @@ -91,7 +99,7 @@ public: throw std::runtime_error("ConfigOptionSingle: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs)); this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; - }; + } bool operator==(const ConfigOption &rhs) const override { @@ -110,6 +118,25 @@ class ConfigOptionVectorBase : public ConfigOption { public: // Currently used only to initialize the PlaceholderParser. virtual std::vector<std::string> vserialize() const = 0; + // Set from a vector of ConfigOptions. + // If the rhs ConfigOption is scalar, then its value is used, + // otherwise for each of rhs, the first value of a vector is used. + // This function is useful to collect values for multiple extrder / filament settings. + virtual void set(const std::vector<const ConfigOption*> &rhs) = 0; + // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. + // This function is useful to split values from multiple extrder / filament settings into separate configurations. + virtual void set_at(const ConfigOption *rhs, size_t i, size_t j) = 0; + + virtual void resize(size_t n, const ConfigOption *opt_default = nullptr) = 0; + + // Get size of this vector. + virtual size_t size() const = 0; + // Is this vector empty? + virtual bool empty() const = 0; + +protected: + // Used to verify type compatibility when assigning to / from a scalar ConfigOption. + ConfigOptionType scalar_type() const { return static_cast<ConfigOptionType>(this->type() - coVectorType); } }; // Value of a vector valued option (bools, ints, floats, strings, points), template @@ -117,6 +144,11 @@ template <class T> class ConfigOptionVector : public ConfigOptionVectorBase { public: + ConfigOptionVector() {} + explicit ConfigOptionVector(size_t n, const T &value) : values(n, value) {} + explicit ConfigOptionVector(std::initializer_list<T> il) : values(std::move(il)) {} + explicit ConfigOptionVector(const std::vector<T> &values) : values(values) {} + explicit ConfigOptionVector(std::vector<T> &&values) : values(std::move(values)) {} std::vector<T> values; void set(const ConfigOption *rhs) override @@ -125,16 +157,88 @@ public: throw std::runtime_error("ConfigOptionVector: Assigning an incompatible type"); assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs)); this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values; - }; + } + + // Set from a vector of ConfigOptions. + // If the rhs ConfigOption is scalar, then its value is used, + // otherwise for each of rhs, the first value of a vector is used. + // This function is useful to collect values for multiple extrder / filament settings. + void set(const std::vector<const ConfigOption*> &rhs) override + { + this->values.clear(); + this->values.reserve(rhs.size()); + for (const ConfigOption *opt : rhs) { + if (opt->type() == this->type()) { + auto other = static_cast<const ConfigOptionVector<T>*>(opt); + if (other->values.empty()) + throw std::runtime_error("ConfigOptionVector::set(): Assigning from an empty vector"); + this->values.emplace_back(other->values.front()); + } else if (opt->type() == this->scalar_type()) + this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value); + else + throw std::runtime_error("ConfigOptionVector::set():: Assigning an incompatible type"); + } + } + + // Set a single vector item from either a scalar option or the first value of a vector option.vector of ConfigOptions. + // This function is useful to split values from multiple extrder / filament settings into separate configurations. + void set_at(const ConfigOption *rhs, size_t i, size_t j) override + { + // It is expected that the vector value has at least one value, which is the default, if not overwritten. + assert(! this->values.empty()); + if (this->values.size() <= i) { + // Resize this vector, fill in the new vector fields with the copy of the first field. + T v = this->values.front(); + this->values.resize(i + 1, v); + } + if (rhs->type() == this->type()) { + // Assign the first value of the rhs vector. + auto other = static_cast<const ConfigOptionVector<T>*>(rhs); + if (other->values.empty()) + throw std::runtime_error("ConfigOptionVector::set_at(): Assigning from an empty vector"); + this->values[i] = other->get_at(j); + } else if (rhs->type() == this->scalar_type()) + this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value; + else + throw std::runtime_error("ConfigOptionVector::set_at(): Assigning an incompatible type"); + } 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<ConfigOptionVector<T>*>(this)->get_at(i); } + // Resize this vector by duplicating the last value. + // If the current vector is empty, the default value is used instead. + void resize(size_t n, const ConfigOption *opt_default = nullptr) override + { + assert(opt_default == nullptr || opt_default->is_vector()); +// assert(opt_default == nullptr || dynamic_cast<ConfigOptionVector<T>>(opt_default)); + assert(! this->values.empty() || opt_default != nullptr); + if (n == 0) + this->values.clear(); + else if (n < this->values.size()) + this->values.erase(this->values.begin() + n, this->values.end()); + else if (n > this->values.size()) { + if (this->values.empty()) { + if (opt_default == nullptr) + throw std::runtime_error("ConfigOptionVector::resize(): No default value provided."); + if (opt_default->type() != this->type()) + throw std::runtime_error("ConfigOptionVector::resize(): Extending with an incompatible type."); + this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front()); + } else { + // Resize by duplicating the last value. + this->values.resize(n, this->values.back()); + } + } + } + + size_t size() const override { return this->values.size(); } + bool empty() const override { return this->values.empty(); } + bool operator==(const ConfigOption &rhs) const override { if (rhs.type() != this->type()) @@ -150,13 +254,14 @@ public: class ConfigOptionFloat : public ConfigOptionSingle<double> { public: - ConfigOptionFloat() : ConfigOptionSingle<double>(0) {}; - explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_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; } + ConfigOptionFloat() : ConfigOptionSingle<double>(0) {} + explicit ConfigOptionFloat(double _value) : ConfigOptionSingle<double>(_value) {} + + static ConfigOptionType static_type() { return coFloat; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -183,9 +288,14 @@ public: class ConfigOptionFloats : public ConfigOptionVector<double> { 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; } + ConfigOptionFloats() : ConfigOptionVector<double>() {} + explicit ConfigOptionFloats(size_t n, double value) : ConfigOptionVector<double>(n, value) {} + explicit ConfigOptionFloats(std::initializer_list<double> il) : ConfigOptionVector<double>(std::move(il)) {} + + static ConfigOptionType static_type() { return coFloats; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionFloats(*this); } + bool operator==(const ConfigOptionFloats &rhs) const { return this->values == rhs.values; } std::string serialize() const override { @@ -195,7 +305,7 @@ public: ss << *it; } return ss.str(); - }; + } std::vector<std::string> vserialize() const override { @@ -234,15 +344,16 @@ public: class ConfigOptionInt : public ConfigOptionSingle<int> { public: - ConfigOptionInt() : ConfigOptionSingle<int>(0) {}; - explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {}; - explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(int(floor(_value + 0.5))) {}; + ConfigOptionInt() : ConfigOptionSingle<int>(0) {} + explicit ConfigOptionInt(int value) : ConfigOptionSingle<int>(value) {} + explicit ConfigOptionInt(double _value) : ConfigOptionSingle<int>(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; } + static ConfigOptionType static_type() { return coInt; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -269,10 +380,15 @@ public: class ConfigOptionInts : public ConfigOptionVector<int> { 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; } + ConfigOptionInts() : ConfigOptionVector<int>() {} + explicit ConfigOptionInts(size_t n, int value) : ConfigOptionVector<int>(n, value) {} + explicit ConfigOptionInts(std::initializer_list<int> il) : ConfigOptionVector<int>(std::move(il)) {} + + static ConfigOptionType static_type() { return coInts; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionInts(*this); } + ConfigOptionInts& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionInts &rhs) const { return this->values == rhs.values; } std::string serialize() const override { std::ostringstream ss; @@ -314,13 +430,14 @@ public: class ConfigOptionString : public ConfigOptionSingle<std::string> { public: - ConfigOptionString() : ConfigOptionSingle<std::string>("") {}; - explicit ConfigOptionString(std::string _value) : ConfigOptionSingle<std::string>(_value) {}; + ConfigOptionString() : ConfigOptionSingle<std::string>("") {} + explicit ConfigOptionString(const std::string &value) : ConfigOptionSingle<std::string>(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; } + static ConfigOptionType static_type() { return coString; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -338,10 +455,17 @@ public: class ConfigOptionStrings : public ConfigOptionVector<std::string> { 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; } + ConfigOptionStrings() : ConfigOptionVector<std::string>() {} + explicit ConfigOptionStrings(size_t n, const std::string &value) : ConfigOptionVector<std::string>(n, value) {} + explicit ConfigOptionStrings(const std::vector<std::string> &values) : ConfigOptionVector<std::string>(values) {} + explicit ConfigOptionStrings(std::vector<std::string> &&values) : ConfigOptionVector<std::string>(std::move(values)) {} + explicit ConfigOptionStrings(std::initializer_list<std::string> il) : ConfigOptionVector<std::string>(std::move(il)) {} + + static ConfigOptionType static_type() { return coStrings; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -364,14 +488,15 @@ public: class ConfigOptionPercent : public ConfigOptionFloat { public: - ConfigOptionPercent() : ConfigOptionFloat(0) {}; - explicit ConfigOptionPercent(double _value) : ConfigOptionFloat(_value) {}; + 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; } + static ConfigOptionType static_type() { return coPercent; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -395,10 +520,15 @@ public: 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; } + ConfigOptionPercents() : ConfigOptionFloats() {} + explicit ConfigOptionPercents(size_t n, double value) : ConfigOptionFloats(n, value) {} + explicit ConfigOptionPercents(std::initializer_list<double> il) : ConfigOptionFloats(std::move(il)) {} + + static ConfigOptionType static_type() { return coPercents; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionPercents(*this); } + ConfigOptionPercents& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionPercents &rhs) const { return this->values == rhs.values; } std::string serialize() const override { @@ -445,10 +575,11 @@ 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; } + ConfigOptionFloatOrPercent() : ConfigOptionPercent(0), percent(false) {} + explicit ConfigOptionFloatOrPercent(double _value, bool _percent) : ConfigOptionPercent(_value), percent(_percent) {} + + static ConfigOptionType static_type() { return coFloatOrPercent; } + ConfigOptionType type() const override { return static_type(); } ConfigOption* clone() const override { return new ConfigOptionFloatOrPercent(*this); } ConfigOptionFloatOrPercent& operator=(const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionFloatOrPercent &rhs) const @@ -485,13 +616,14 @@ public: class ConfigOptionPoint : public ConfigOptionSingle<Pointf> { public: - ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {}; - explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(value) {}; + ConfigOptionPoint() : ConfigOptionSingle<Pointf>(Pointf(0,0)) {} + explicit ConfigOptionPoint(const Pointf &value) : ConfigOptionSingle<Pointf>(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; } + static ConfigOptionType static_type() { return coPoint; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -517,10 +649,15 @@ public: class ConfigOptionPoints : public ConfigOptionVector<Pointf> { 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; } + ConfigOptionPoints() : ConfigOptionVector<Pointf>() {} + explicit ConfigOptionPoints(size_t n, const Pointf &value) : ConfigOptionVector<Pointf>(n, value) {} + explicit ConfigOptionPoints(std::initializer_list<Pointf> il) : ConfigOptionVector<Pointf>(std::move(il)) {} + + static ConfigOptionType static_type() { return coPoints; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -570,14 +707,15 @@ public: class ConfigOptionBool : public ConfigOptionSingle<bool> { public: - ConfigOptionBool() : ConfigOptionSingle<bool>(false) {}; - explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_value) {}; + ConfigOptionBool() : ConfigOptionSingle<bool>(false) {} + explicit ConfigOptionBool(bool _value) : ConfigOptionSingle<bool>(_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; } + static ConfigOptionType static_type() { return coBool; } + ConfigOptionType type() const override { return static_type(); } + 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 { @@ -595,10 +733,15 @@ public: class ConfigOptionBools : public ConfigOptionVector<unsigned char> { 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; } + ConfigOptionBools() : ConfigOptionVector<unsigned char>() {} + explicit ConfigOptionBools(size_t n, bool value) : ConfigOptionVector<unsigned char>(n, (unsigned char)value) {} + explicit ConfigOptionBools(std::initializer_list<bool> il) { values.reserve(il.size()); for (bool b : il) values.emplace_back((unsigned char)b); } + + static ConfigOptionType static_type() { return coBools; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionBools(*this); } + ConfigOptionBools& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionBools &rhs) const { return this->values == rhs.values; } bool& get_at(size_t i) { assert(! this->values.empty()); @@ -652,13 +795,14 @@ class ConfigOptionEnum : public ConfigOptionSingle<T> { public: // by default, use the first value (0) of the T enum type - ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {}; - explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {}; + ConfigOptionEnum() : ConfigOptionSingle<T>(static_cast<T>(0)) {} + explicit ConfigOptionEnum(T _value) : ConfigOptionSingle<T>(_value) {} - ConfigOptionType type() const override { return coEnum; } - ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); } - ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; } - bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; } + static ConfigOptionType static_type() { return coEnum; } + ConfigOptionType type() const override { return static_type(); } + ConfigOption* clone() const override { return new ConfigOptionEnum<T>(*this); } + ConfigOptionEnum<T>& operator=(const ConfigOption *opt) { this->set(opt); return *this; } + bool operator==(const ConfigOptionEnum<T> &rhs) const { return this->value == rhs.value; } std::string serialize() const override { @@ -717,8 +861,9 @@ public: const t_config_enum_values* keys_map; - ConfigOptionType type() const override { return coEnum; } - ConfigOption* clone() const override { return new ConfigOptionEnumGeneric(*this); } + static ConfigOptionType static_type() { return coEnum; } + ConfigOptionType type() const override { return static_type(); } + 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; } @@ -874,6 +1019,16 @@ public: { return const_cast<ConfigBase*>(this)->option(opt_key, false); } ConfigOption* option(const t_config_option_key &opt_key, bool create = false) { return this->optptr(opt_key, create); } + template<typename TYPE> + TYPE* option(const t_config_option_key &opt_key, bool create = false) + { + ConfigOption *opt = this->optptr(opt_key, create); + assert(opt == nullptr || opt->type() == TYPE::static_type()); + return (opt == nullptr || opt->type() != TYPE::static_type()) ? nullptr : static_cast<TYPE*>(opt); + } + template<typename TYPE> + const TYPE* option(const t_config_option_key &opt_key) const + { return const_cast<ConfigBase*>(this)->option<TYPE>(opt_key, 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. @@ -893,7 +1048,9 @@ public: double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_(); void load(const std::string &file); + void load_from_ini(const std::string &file); void load_from_gcode(const std::string &file); + void load(const boost::property_tree::ptree &tree); void save(const std::string &file) const; private: @@ -906,26 +1063,77 @@ private: class DynamicConfig : public virtual ConfigBase { public: + DynamicConfig() {} DynamicConfig(const DynamicConfig& other) { *this = other; } DynamicConfig(DynamicConfig&& other) : options(std::move(other.options)) { other.options.clear(); } virtual ~DynamicConfig() { clear(); } - DynamicConfig& operator=(const DynamicConfig &other) + // Copy a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator=(const DynamicConfig &rhs) { + assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); - for (const auto &kvp : other.options) + for (const auto &kvp : rhs.options) this->options[kvp.first] = kvp.second->clone(); return *this; } - DynamicConfig& operator=(DynamicConfig &&other) + // Move a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator=(DynamicConfig &&rhs) { + assert(this->def() == nullptr || this->def() == rhs.def()); this->clear(); - this->options = std::move(other.options); - other.options.clear(); + this->options = std::move(rhs.options); + rhs.options.clear(); + return *this; + } + + // Add a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator+=(const DynamicConfig &rhs) + { + assert(this->def() == nullptr || this->def() == rhs.def()); + for (const auto &kvp : rhs.options) { + auto it = this->options.find(kvp.first); + if (it == this->options.end()) + this->options[kvp.first] = kvp.second->clone(); + else { + assert(it->second->type() == kvp.second->type()); + if (it->second->type() == kvp.second->type()) + *it->second = *kvp.second; + else { + delete it->second; + it->second = kvp.second->clone(); + } + } + } + return *this; + } + + // Move a content of one DynamicConfig to another DynamicConfig. + // If rhs.def() is not null, then it has to be equal to this->def(). + DynamicConfig& operator+=(DynamicConfig &&rhs) + { + assert(this->def() == nullptr || this->def() == rhs.def()); + for (const auto &kvp : rhs.options) { + auto it = this->options.find(kvp.first); + if (it == this->options.end()) { + this->options[kvp.first] = kvp.second; + } else { + assert(it->second->type() == kvp.second->type()); + delete it->second; + it->second = kvp.second; + } + } + rhs.options.clear(); return *this; } + bool operator==(const DynamicConfig &rhs) const; + bool operator!=(const DynamicConfig &rhs) const { return ! (*this == rhs); } + void swap(DynamicConfig &other) { std::swap(this->options, other.options); @@ -948,30 +1156,49 @@ public: return true; } - template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) + // Allow DynamicConfig to be instantiated on ints own without a definition. + // If the definition is not defined, the method requiring the definition will throw NoDefinitionException. + const ConfigDef* def() const override { return nullptr; }; + template<class T> T* opt(const t_config_option_key &opt_key, bool create = false) { return dynamic_cast<T*>(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<ConfigOptionString*>(this->option(opt_key, create))->value; } + // Set a value for an opt_key. Returns true if the value did not exist yet. + // This DynamicConfig will take ownership of opt. + // Be careful, as this method does not test the existence of opt_key in this->def(). + bool set_key_value(const std::string &opt_key, ConfigOption *opt) + { + auto it = this->options.find(opt_key); + if (it == this->options.end()) { + this->options[opt_key] = opt; + return true; + } else { + delete it->second; + it->second = opt; + return false; + } + } + + std::string& opt_string(const t_config_option_key &opt_key, bool create = false) { return this->option<ConfigOptionString>(opt_key, create)->value; } const std::string& opt_string(const t_config_option_key &opt_key) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key); } - std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionStrings*>(this->option(opt_key))->get_at(idx); } + std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionStrings>(opt_key)->get_at(idx); } const std::string& opt_string(const t_config_option_key &opt_key, unsigned int idx) const { return const_cast<DynamicConfig*>(this)->opt_string(opt_key, idx); } - double& opt_float(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionFloat*>(this->option(opt_key))->value; } + double& opt_float(const t_config_option_key &opt_key) { return this->option<ConfigOptionFloat>(opt_key)->value; } const double opt_float(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionFloat*>(this->option(opt_key))->value; } - double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); } + double& opt_float(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionFloats>(opt_key)->get_at(idx); } const double opt_float(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionFloats*>(this->option(opt_key))->get_at(idx); } - int& opt_int(const t_config_option_key &opt_key) { return dynamic_cast<ConfigOptionInt*>(this->option(opt_key))->value; } + int& opt_int(const t_config_option_key &opt_key) { return this->option<ConfigOptionInt>(opt_key)->value; } const int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast<const ConfigOptionInt*>(this->option(opt_key))->value; } - int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return dynamic_cast<ConfigOptionInts*>(this->option(opt_key))->get_at(idx); } + int& opt_int(const t_config_option_key &opt_key, unsigned int idx) { return this->option<ConfigOptionInts>(opt_key)->get_at(idx); } const int opt_int(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast<const ConfigOptionInts*>(this->option(opt_key))->get_at(idx); } -protected: - DynamicConfig() {} + bool opt_bool(const t_config_option_key &opt_key) const { return this->option<ConfigOptionBool>(opt_key)->value != 0; } + bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; } private: typedef std::map<t_config_option_key,ConfigOption*> t_options_map; @@ -1001,6 +1228,13 @@ public: const char* what() const noexcept override { return "Unknown config option"; } }; +/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null). +class NoDefinitionException : public std::exception +{ +public: + const char* what() const noexcept override { return "No config definition"; } +}; + } #endif diff --git a/xs/src/libslic3r/ExPolygon.hpp b/xs/src/libslic3r/ExPolygon.hpp index af34694b7..f4782ba55 100644 --- a/xs/src/libslic3r/ExPolygon.hpp +++ b/xs/src/libslic3r/ExPolygon.hpp @@ -149,7 +149,6 @@ inline Polylines to_polylines(const ExPolygons &src) return polylines; } -#if SLIC3R_CPPVER >= 11 inline Polylines to_polylines(ExPolygon &&src) { Polylines polylines; @@ -166,6 +165,7 @@ inline Polylines to_polylines(ExPolygon &&src) assert(idx == polylines.size()); return polylines; } + inline Polylines to_polylines(ExPolygons &&src) { Polylines polylines; @@ -184,7 +184,6 @@ inline Polylines to_polylines(ExPolygons &&src) assert(idx == polylines.size()); return polylines; } -#endif inline Polygons to_polygons(const ExPolygon &src) { diff --git a/xs/src/libslic3r/Fill/Fill.cpp b/xs/src/libslic3r/Fill/Fill.cpp index ffae7fa5b..5333fcfec 100644 --- a/xs/src/libslic3r/Fill/Fill.cpp +++ b/xs/src/libslic3r/Fill/Fill.cpp @@ -149,8 +149,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // ); } - for (Surfaces::const_iterator surface_it = surfaces.begin(); surface_it != surfaces.end(); ++ surface_it) { - const Surface &surface = *surface_it; + for (const Surface &surface : surfaces) { if (surface.surface_type == stInternalVoid) continue; InfillPattern fill_pattern = layerm.region()->config.fill_pattern.value; @@ -262,10 +261,10 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out) // Unpacks the collection, creates multiple collections per path. // The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection. // Why the paths are unpacked? - for (ExtrusionEntitiesPtr::iterator thin_fill = layerm.thin_fills.entities.begin(); thin_fill != layerm.thin_fills.entities.end(); ++ thin_fill) { + for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) { ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection()); out.entities.push_back(&collection); - collection.entities.push_back((*thin_fill)->clone()); + collection.entities.push_back(thin_fill->clone()); } } diff --git a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp index d03c1f208..aa9774784 100644 --- a/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/Fill3DHoneycomb.cpp @@ -176,11 +176,7 @@ void Fill3DHoneycomb::_fill_surface_single( } } Polylines chained = PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER >= 11 std::move(polylines), -#else - polylines, -#endif PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { @@ -199,12 +195,7 @@ void Fill3DHoneycomb::_fill_surface_single( } } // The lines cannot be connected. -#if SLIC3R_CPPVER >= 11 - polylines_out.push_back(std::move(*it_polyline)); -#else - polylines_out.push_back(Polyline()); - std::swap(polylines_out.back(), *it_polyline); -#endif + polylines_out.emplace_back(std::move(*it_polyline)); first = false; } } diff --git a/xs/src/libslic3r/Fill/FillHoneycomb.cpp b/xs/src/libslic3r/Fill/FillHoneycomb.cpp index 22dea85da..aa0e0f6b0 100644 --- a/xs/src/libslic3r/Fill/FillHoneycomb.cpp +++ b/xs/src/libslic3r/Fill/FillHoneycomb.cpp @@ -17,12 +17,7 @@ void FillHoneycomb::_fill_surface_single( CacheID cache_id(params.density, this->spacing); Cache::iterator it_m = this->cache.find(cache_id); if (it_m == this->cache.end()) { -#if 0 -// #if SLIC3R_CPPVER > 11 - it_m = this->cache.emplace_hint(it_m); -#else it_m = this->cache.insert(it_m, std::pair<CacheID, CacheData>(cache_id, CacheData())); -#endif CacheData &m = it_m->second; coord_t min_spacing = scale_(this->spacing); m.distance = min_spacing / params.density; @@ -99,11 +94,7 @@ void FillHoneycomb::_fill_surface_single( // connect paths if (! paths.empty()) { // prevent calling leftmost_point() on empty collections Polylines chained = PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER >= 11 std::move(paths), -#else - paths, -#endif PolylineCollection::leftmost_point(paths), false); assert(paths.empty()); paths.clear(); diff --git a/xs/src/libslic3r/Fill/FillRectilinear.cpp b/xs/src/libslic3r/Fill/FillRectilinear.cpp index 991adc0b3..5ba30ba51 100644 --- a/xs/src/libslic3r/Fill/FillRectilinear.cpp +++ b/xs/src/libslic3r/Fill/FillRectilinear.cpp @@ -93,11 +93,7 @@ void FillRectilinear::_fill_surface_single( } } Polylines chained = PolylineCollection::chained_path_from( -#if SLIC3R_CPPVER >= 11 std::move(polylines), -#else - polylines, -#endif PolylineCollection::leftmost_point(polylines), false); // reverse allowed bool first = true; for (Polylines::iterator it_polyline = chained.begin(); it_polyline != chained.end(); ++ it_polyline) { @@ -118,12 +114,7 @@ void FillRectilinear::_fill_surface_single( } } // The lines cannot be connected. -#if SLIC3R_CPPVER >= 11 - polylines_out.push_back(std::move(*it_polyline)); -#else - polylines_out.push_back(Polyline()); - std::swap(polylines_out.back(), *it_polyline); -#endif + polylines_out.emplace_back(std::move(*it_polyline)); first = false; } } diff --git a/xs/src/libslic3r/Flow.cpp b/xs/src/libslic3r/Flow.cpp index 25a44ea8f..b60e26dcc 100644 --- a/xs/src/libslic3r/Flow.cpp +++ b/xs/src/libslic3r/Flow.cpp @@ -8,39 +8,6 @@ namespace Slic3r { // This static method returns a sane extrusion width default. static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, float height) { -#if 0 - // Here we calculate a sane default by matching the flow speed (at the nozzle) and the feed rate. - // shape: rectangle with semicircles at the ends - // This "sane" extrusion width gives the following results for a 0.4mm dmr nozzle: - // Layer Calculated Calculated width - // heigh extrusion over nozzle - // width diameter - // 0.40 0.40 1.00 - // 0.35 0.43 1.09 - // 0.30 0.48 1.21 - // 0.25 0.56 1.39 - // 0.20 0.67 1.68 - // 0.15 0.87 2.17 - // 0.10 1.28 3.20 - // 0.05 2.52 6.31 - // - float width = float(0.25 * (nozzle_diameter * nozzle_diameter) * PI / height + height * (1.0 - 0.25 * PI)); - - switch (role) { - case frExternalPerimeter: - case frSupportMaterial: - case frSupportMaterialInterface: - return nozzle_diameter; - case frPerimeter: - case frSolidInfill: - case frTopSolidInfill: - // do not limit width for sparse infill so that we use full native flow for it - return std::min(std::max(width, nozzle_diameter * 1.05f), nozzle_diameter * 1.7f); - case frInfill: - default: - return std::max(width, nozzle_diameter * 1.05f); - } -#else switch (role) { case frSupportMaterial: case frSupportMaterialInterface: @@ -53,7 +20,6 @@ static inline float auto_extrusion_width(FlowRole role, float nozzle_diameter, f case frInfill: return 1.125f * nozzle_diameter; } -#endif } // This constructor builds a Flow object from an extrusion width config setting @@ -154,10 +120,11 @@ Flow support_material_flow(const PrintObject *object, float layer_height) Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height) { + const auto &width = (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width; return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. - (object->print()->config.first_layer_extrusion_width.value > 0) ? object->print()->config.first_layer_extrusion_width : object->config.support_material_extrusion_width, + (width.value > 0) ? width : object->config.extrusion_width, float(object->print()->config.nozzle_diameter.get_at(object->config.support_material_extruder-1)), (layer_height > 0.f) ? layer_height : float(object->config.first_layer_height.get_abs_value(object->config.layer_height.value)), false); diff --git a/xs/src/libslic3r/Flow.hpp b/xs/src/libslic3r/Flow.hpp index a058cc8b7..516490b35 100644 --- a/xs/src/libslic3r/Flow.hpp +++ b/xs/src/libslic3r/Flow.hpp @@ -52,6 +52,9 @@ public: coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }; static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio); + // Create a flow from the spacing of extrusion lines. + // This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale + // to fit a region with integer number of lines. static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge); }; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index cf826ff68..0e85c55a8 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -4,6 +4,7 @@ #include "Geometry.hpp" #include "GCode/PrintExtents.hpp" #include "GCode/WipeTowerPrusaMM.hpp" +#include "Utils.hpp" #include <algorithm> #include <cstdlib> @@ -11,7 +12,6 @@ #include <boost/algorithm/string.hpp> #include <boost/algorithm/string/find.hpp> -#include <boost/date_time/local_time/local_time.hpp> #include <boost/foreach.hpp> #include <boost/nowide/iostream.hpp> @@ -30,6 +30,13 @@ #include <assert.h> namespace Slic3r { + +// Only add a newline in case the current G-code does not end with a newline. +static inline void check_add_eol(std::string &gcode) +{ + if (! gcode.empty() && gcode.back() != '\n') + gcode += '\n'; +} // Plan a travel move while minimizing the number of perimeter crossings. // point is in unscaled coordinates, in the coordinate system of the current active object @@ -157,6 +164,8 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T { std::string gcode; + // Disable linear advance for the wipe tower operations. + gcode += "M900 K0\n"; // Move over the wipe tower. // Retract for a tool change, using the toolchange retract value and setting the priming extra length. gcode += gcodegen.retract(true); @@ -171,8 +180,17 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T // Inform the G-code writer about the changes done behind its back. gcode += tcr.gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. - if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) + if (new_extruder_id >= 0 && gcodegen.writer().need_toolchange(new_extruder_id)) { gcodegen.writer().toolchange(new_extruder_id); + // Append the filament start G-code. + const std::string &start_filament_gcode = gcodegen.config().start_filament_gcode.get_at(new_extruder_id); + if (! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the active filament only. + gcodegen.placeholder_parser().set("current_extruder", new_extruder_id); + gcode += gcodegen.placeholder_parser_process("start_filament_gcode", start_filament_gcode, new_extruder_id); + check_add_eol(gcode); + } + } // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Pointf(tcr.end_pos.x, tcr.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, tcr.end_pos)); @@ -199,15 +217,18 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) std::string gcode; if (&m_priming != nullptr && ! m_priming.extrusions.empty()) { + // Disable linear advance for the wipe tower operations. + gcode += "M900 K0\n"; // Let the tool change be executed by the wipe tower class. // Inform the G-code writer about the changes done behind its back. gcode += m_priming.gcode; // Let the m_writer know the current extruder_id, but ignore the generated G-code. - gcodegen.writer().toolchange(m_priming.extrusions.back().tool); + unsigned int current_extruder_id = m_priming.extrusions.back().tool; + gcodegen.writer().toolchange(current_extruder_id); + gcodegen.placeholder_parser().set("current_extruder", current_extruder_id); // A phony move to the end position at the wipe tower. gcodegen.writer().travel_to_xy(Pointf(m_priming.end_pos.x, m_priming.end_pos.y)); gcodegen.set_last_pos(wipe_tower_point_to_object_point(gcodegen, m_priming.end_pos)); - // Prepare a future wipe. gcodegen.m_wipe.path.points.clear(); // Start the wipe at the current position. @@ -220,19 +241,6 @@ std::string WipeTowerIntegration::prime(GCode &gcodegen) return gcode; } -std::string WipeTowerIntegration::prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */) -{ - std::string gcode = "\ -G1 Z0.250 F7200.000\n\ -G1 X50.0 E80.0 F1000.0\n\ -G1 X160.0 E20.0 F1000.0\n\ -G1 Z0.200 F7200.000\n\ -G1 X220.0 E13 F1000.0\n\ -G1 X240.0 E0 F1000.0\n\ -G1 E-4 F1000.0\n"; - return gcode; -} - std::string WipeTowerIntegration::tool_change(GCode &gcodegen, int extruder_id, bool finish_layer) { std::string gcode; @@ -313,10 +321,15 @@ inline void write_format(FILE* file, const char* format, ...) write(file, buffer); } +// Write a string into a file. Add a newline, if the string does not end with a newline already. +// Used to export a custom G-code section processed by the PlaceholderParser. inline void writeln(FILE *file, const std::string &what) { if (! what.empty()) { - write_format(file, "%s\n", what.c_str()); + if (what.back() != '\n') + write_format(file, "%s\n", what.c_str()); + else + write(file, what); } } @@ -399,7 +412,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec return layers_to_print; } -bool GCode::do_export(Print *print, const char *path) +void GCode::do_export(Print *print, const char *path) { // Remove the old g-code if it exists. boost::nowide::remove(path); @@ -409,24 +422,37 @@ bool GCode::do_export(Print *print, const char *path) FILE *file = boost::nowide::fopen(path_tmp.c_str(), "wb"); if (file == nullptr) - return false; + throw std::runtime_error(std::string("G-code export to ") + path + " failed.\nCannot open the file for writing.\n"); - bool result = this->_do_export(*print, file); + this->m_placeholder_parser_failed_templates.clear(); + this->_do_export(*print, file); + fflush(file); + if (ferror(file)) { + fclose(file); + boost::nowide::remove(path_tmp.c_str()); + throw std::runtime_error(std::string("G-code export to ") + path + " failed\nIs the disk full?\n"); + } fclose(file); - - if (result && boost::nowide::rename(path_tmp.c_str(), path) != 0) { - boost::nowide::cerr << "Failed to remove the output G-code file from " << path_tmp << " to " << path - << ". Is " << path_tmp << " locked?" << std::endl; - result = false; + if (! this->m_placeholder_parser_failed_templates.empty()) { + // G-code export proceeded, but some of the PlaceholderParser substitutions failed. + std::string msg = std::string("G-code export to ") + path + " failed due to invalid custom G-code sections:\n\n"; + for (const std::string &name : this->m_placeholder_parser_failed_templates) + msg += std::string("\t") + name + "\n"; + msg += "\nPlease inspect the file "; + msg += path_tmp + " for error messages enclosed between\n"; + msg += " !!!!! Failed to process the custom G-code template ...\n"; + msg += "and\n"; + msg += " !!!!! End of an error report for the custom G-code template ...\n"; + throw std::runtime_error(msg); } - if (! result) - boost::nowide::remove(path_tmp.c_str()); - - return result; + if (boost::nowide::rename(path_tmp.c_str(), path) != 0) + throw std::runtime_error( + std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + path + '\n' + + "Is " + path_tmp + " locked?" + '\n'); } -bool GCode::_do_export(Print &print, FILE *file) +void GCode::_do_export(Print &print, FILE *file) { // resets time estimator m_time_estimator.reset(); @@ -515,15 +541,7 @@ bool GCode::_do_export(Print &print, FILE *file) m_enable_extrusion_role_markers = (bool)m_pressure_equalizer; // Write information on the generator. - { - const auto now = boost::posix_time::second_clock::local_time(); - const auto date = now.date(); - fprintf(file, "; generated by Slic3r %s on %04d-%02d-%02d at %02d:%02d:%02d\n\n", - SLIC3R_VERSION, - // Local date in an ANSII format. - int(now.date().year()), int(now.date().month()), int(now.date().day()), - int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds())); - } + fprintf(file, "; %s\n\n", Slic3r::header_slic3r_generated().c_str()); // Write notes (content of the Print Settings tab -> Notes) { std::list<std::string> lines; @@ -541,6 +559,7 @@ bool GCode::_do_export(Print &print, FILE *file) { const PrintObject *first_object = print.objects.front(); const double layer_height = first_object->config.layer_height.value; + const double first_layer_height = first_object->config.first_layer_height.get_abs_value(layer_height); for (size_t region_id = 0; region_id < print.regions.size(); ++ region_id) { auto region = print.regions[region_id]; fprintf(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width); @@ -551,7 +570,7 @@ bool GCode::_do_export(Print &print, FILE *file) if (print.has_support_material()) fprintf(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width); if (print.config.first_layer_extrusion_width.value > 0) - fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, true, -1., *first_object).width); + fprintf(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width); fprintf(file, "\n"); } } @@ -566,6 +585,7 @@ bool GCode::_do_export(Print &print, FILE *file) unsigned int initial_extruder_id = (unsigned int)-1; unsigned int final_extruder_id = (unsigned int)-1; size_t initial_print_object_id = 0; + bool has_wipe_tower = false; if (print.config.complete_objects.value) { // Find the 1st printing object, find its tool ordering and the initial extruder ID. for (; initial_print_object_id < print.objects.size(); ++initial_print_object_id) { @@ -580,6 +600,7 @@ bool GCode::_do_export(Print &print, FILE *file) ToolOrdering(print, initial_extruder_id) : print.m_tool_ordering; initial_extruder_id = tool_ordering.first_extruder(); + has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); } if (initial_extruder_id == (unsigned int)-1) { // Nothing to print! @@ -596,28 +617,35 @@ bool GCode::_do_export(Print &print, FILE *file) if (! print.config.cooling.get_at(initial_extruder_id) || print.config.disable_fan_first_layers.get_at(initial_extruder_id)) write(file, m_writer.set_fan(0, true)); - // Set bed temperature if the start G-code does not contain any bed temp control G-codes. - { - // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if - // the custom start G-code emited these. - //FIXME Should one parse the custom G-code to initialize the "current" bed temp state at m_writer? - std::string gcode = m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id), true); - if (boost::ifind_first(print.config.start_gcode.value, std::string("M140")).empty() && - boost::ifind_first(print.config.start_gcode.value, std::string("M190")).empty()) - write(file, gcode); - } - - // Set extruder(s) temperature before and after start G-code. - this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false); // Let the start-up script prime the 1st printing tool. m_placeholder_parser.set("initial_tool", initial_extruder_id); m_placeholder_parser.set("initial_extruder", initial_extruder_id); m_placeholder_parser.set("current_extruder", initial_extruder_id); - writeln(file, m_placeholder_parser.process(print.config.start_gcode.value, initial_extruder_id)); + // Useful for sequential prints. + m_placeholder_parser.set("current_object_idx", 0); + // For the start / end G-code to do the priming and final filament pull in case there is no wipe tower provided. + m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); + std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config.start_gcode.value, initial_extruder_id); + + // Set bed temperature if the start G-code does not contain any bed temp control G-codes. + this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); + // Set extruder(s) temperature before and after start G-code. + this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, false); + // Write the custom start G-code + writeln(file, start_gcode); // Process filament-specific gcode in extruder order. - for (const std::string &start_gcode : print.config.start_filament_gcode.values) - writeln(file, m_placeholder_parser.process(start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); - this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, true); + if (print.config.single_extruder_multi_material) { + if (has_wipe_tower) { + // Wipe tower will control the extruder switching, it will call the start_filament_gcode. + } else { + // Only initialize the initial extruder. + writeln(file, this->placeholder_parser_process("start_filament_gcode", print.config.start_filament_gcode.values[initial_extruder_id], initial_extruder_id)); + } + } else { + for (const std::string &start_gcode : print.config.start_filament_gcode.values) + writeln(file, this->placeholder_parser_process("start_gcode", start_gcode, (unsigned int)(&start_gcode - &print.config.start_filament_gcode.values.front()))); + } + this->_print_first_layer_extruder_temperatures(file, print, start_gcode, initial_extruder_id, true); // Set other general things. write(file, this->preamble()); @@ -707,9 +735,12 @@ bool GCode::_do_export(Print &print, FILE *file) // Ff we are printing the bottom layer of an object, and we have already finished // another one, set first layer temperatures. This happens before the Z move // is triggered, so machine has more time to reach such temperatures. - write(file, m_writer.set_bed_temperature(print.config.first_layer_bed_temperature.get_at(initial_extruder_id))); - // Set first layer extruder. - this->_print_first_layer_extruder_temperatures(file, print, initial_extruder_id, false); + m_placeholder_parser.set("current_object_idx", int(finished_objects)); + std::string between_objects_gcode = this->placeholder_parser_process("between_objects_gcode", print.config.between_objects_gcode.value, initial_extruder_id); + // Set first layer bed and extruder temperatures, don't wait for it to reach the temperature. + this->_print_first_layer_bed_temperature(file, print, between_objects_gcode, initial_extruder_id, false); + this->_print_first_layer_extruder_temperatures(file, print, between_objects_gcode, initial_extruder_id, false); + writeln(file, between_objects_gcode); } // Reset the cooling buffer internal state (the current position, feed rate, accelerations). m_cooling_buffer->reset(); @@ -740,33 +771,29 @@ bool GCode::_do_export(Print &print, FILE *file) // All extrusion moves with the same top layer height are extruded uninterrupted. std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print = collect_layers_to_print(print); // Prusa Multi-Material wipe tower. - if (print.has_wipe_tower() && ! layers_to_print.empty()) { - if (tool_ordering.has_wipe_tower()) { - m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); - write(file, m_wipe_tower->prime(*this)); - // Verify, whether the print overaps the priming extrusions. - BoundingBoxf bbox_print(get_print_extrusions_extents(print)); - coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; - for (const PrintObject *print_object : print.objects) - bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); - bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); - BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); - bbox_prime.offset(0.5f); - // Beep for 500ms, tone 800Hz. Yet better, play some Morse. - write(file, this->retract()); - write(file, "M300 S800 P500\n"); - if (bbox_prime.overlap(bbox_print)) { - // Wait for the user to remove the priming extrusions, otherwise they would - // get covered by the print. - write(file, "M1 Remove priming towers and click button.\n"); + if (has_wipe_tower && ! layers_to_print.empty()) { + m_wipe_tower.reset(new WipeTowerIntegration(print.config, *print.m_wipe_tower_priming.get(), print.m_wipe_tower_tool_changes, *print.m_wipe_tower_final_purge.get())); + write(file, m_wipe_tower->prime(*this)); + // Verify, whether the print overaps the priming extrusions. + BoundingBoxf bbox_print(get_print_extrusions_extents(print)); + coordf_t twolayers_printz = ((layers_to_print.size() == 1) ? layers_to_print.front() : layers_to_print[1]).first + EPSILON; + for (const PrintObject *print_object : print.objects) + bbox_print.merge(get_print_object_extrusions_extents(*print_object, twolayers_printz)); + bbox_print.merge(get_wipe_tower_extrusions_extents(print, twolayers_printz)); + BoundingBoxf bbox_prime(get_wipe_tower_priming_extrusions_extents(print)); + bbox_prime.offset(0.5f); + // Beep for 500ms, tone 800Hz. Yet better, play some Morse. + write(file, this->retract()); + write(file, "M300 S800 P500\n"); + if (bbox_prime.overlap(bbox_print)) { + // Wait for the user to remove the priming extrusions, otherwise they would + // get covered by the print. + write(file, "M1 Remove priming towers and click button.\n"); + } else { + // Just wait for a bit to let the user check, that the priming succeeded. + //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. + write(file, "M1 S10\n"); } - else { - // Just wait for a bit to let the user check, that the priming succeeded. - //TODO Add a message explaining what the printer is waiting for. This needs a firmware fix. - write(file, "M1 S10\n"); - } - } else - write(file, WipeTowerIntegration::prime_single_color_print(print, initial_extruder_id, *this)); } // Extrude the layers. for (auto &layer : layers_to_print) { @@ -786,9 +813,14 @@ bool GCode::_do_export(Print &print, FILE *file) write(file, this->retract()); write(file, m_writer.set_fan(false)); // Process filament-specific gcode in extruder order. - for (const std::string &end_gcode : print.config.end_filament_gcode.values) - writeln(file, m_placeholder_parser.process(end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); - writeln(file, m_placeholder_parser.process(print.config.end_gcode, m_writer.extruder()->id())); + if (print.config.single_extruder_multi_material) { + // Process the end_filament_gcode for the active filament only. + writeln(file, this->placeholder_parser_process("end_filament_gcode", print.config.end_filament_gcode.get_at(m_writer.extruder()->id()), m_writer.extruder()->id())); + } else { + for (const std::string &end_gcode : print.config.end_filament_gcode.values) + writeln(file, this->placeholder_parser_process("end_gcode", end_gcode, (unsigned int)(&end_gcode - &print.config.end_filament_gcode.values.front()))); + } + writeln(file, this->placeholder_parser_process("end_gcode", print.config.end_gcode, m_writer.extruder()->id())); write(file, m_writer.update_progress(m_layer_count, m_layer_count, true)); // 100% write(file, m_writer.postamble()); @@ -830,22 +862,117 @@ bool GCode::_do_export(Print &print, FILE *file) for (size_t i = 0; i < sizeof(configs) / sizeof(configs[0]); ++ i) { StaticPrintConfig *cfg = configs[i]; for (const std::string &key : cfg->keys()) - fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str()); + if (key != "compatible_printers") + fprintf(file, "; %s = %s\n", key.c_str(), cfg->serialize(key).c_str()); } } +} - return true; +std::string GCode::placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) +{ + try { + return m_placeholder_parser.process(templ, current_extruder_id, config_override); + } catch (std::runtime_error &err) { + // Collect the names of failed template substitutions for error reporting. + this->m_placeholder_parser_failed_templates.insert(name); + // Insert the macro error message into the G-code. + return + std::string("\n!!!!! Failed to process the custom G-code template ") + name + "\n" + + err.what() + + "!!!!! End of an error report for the custom G-code template " + name + "\n\n"; + } +} + +// Parse the custom G-code, try to find mcode_set_temp_dont_wait and mcode_set_temp_and_wait inside the custom G-code. +// Returns true if one of the temp commands are found, and try to parse the target temperature value into temp_out. +static bool custom_gcode_sets_temperature(const std::string &gcode, const int mcode_set_temp_dont_wait, const int mcode_set_temp_and_wait, int &temp_out) +{ + temp_out = -1; + if (gcode.empty()) + return false; + + const char *ptr = gcode.data(); + bool temp_set_by_gcode = false; + while (*ptr != 0) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'M') { + // Line starts with 'M'. It is a machine command. + ++ ptr; + // Parse the M code value. + char *endptr = nullptr; + int mcode = int(strtol(ptr, &endptr, 10)); + if (endptr != nullptr && endptr != ptr && (mcode == mcode_set_temp_dont_wait || mcode == mcode_set_temp_and_wait)) { + // M104/M109 or M140/M190 found. + ptr = endptr; + // Let the caller know that the custom G-code sets the temperature. + temp_set_by_gcode = true; + // Now try to parse the temperature value. + // While not at the end of the line: + while (strchr(";\r\n\0", *ptr) == nullptr) { + // Skip whitespaces. + for (; *ptr == ' ' || *ptr == '\t'; ++ ptr); + if (*ptr == 'S') { + // Skip whitespaces. + for (++ ptr; *ptr == ' ' || *ptr == '\t'; ++ ptr); + // Parse an int. + endptr = nullptr; + long temp_parsed = strtol(ptr, &endptr, 10); + if (endptr > ptr) { + ptr = endptr; + temp_out = temp_parsed; + } + } else { + // Skip this word. + for (; strchr(" \t;\r\n\0", *ptr) == nullptr; ++ ptr); + } + } + } + } + // Skip the rest of the line. + for (; *ptr != 0 && *ptr != '\r' && *ptr != '\n'; ++ ptr); + // Skip the end of line indicators. + for (; *ptr == '\r' || *ptr == '\n'; ++ ptr); + } + return temp_set_by_gcode; +} + +// Write 1st layer bed temperatures into the G-code. +// Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. +// M140 - Set Extruder Temperature +// M190 - Set Extruder Temperature and Wait +void GCode::_print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) +{ + // Initial bed temperature based on the first extruder. + int temp = print.config.first_layer_bed_temperature.get_at(first_printing_extruder_id); + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + bool temp_set_by_gcode = custom_gcode_sets_temperature(gcode, 140, 190, temp_by_gcode); + if (temp_set_by_gcode && temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; + // Always call m_writer.set_bed_temperature() so it will set the internal "current" state of the bed temp as if + // the custom start G-code emited these. + std::string set_temp_gcode = m_writer.set_bed_temperature(temp, wait); + if (! temp_set_by_gcode) + write(file, set_temp_gcode); } // Write 1st layer extruder temperatures into the G-code. // Only do that if the start G-code does not already contain any M-code controlling an extruder temperature. -// FIXME this does not work correctly for multi-extruder, single heater configuration as it emits multiple preheat commands for the same heater. // M104 - Set Extruder Temperature // M109 - Set Extruder Temperature and Wait -void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait) +void GCode::_print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait) { - if (boost::ifind_first(print.config.start_gcode.value, std::string("M104")).empty() && - boost::ifind_first(print.config.start_gcode.value, std::string("M109")).empty()) { + // Is the bed temperature set by the provided custom G-code? + int temp_by_gcode = -1; + if (custom_gcode_sets_temperature(gcode, 104, 109, temp_by_gcode)) { + // Set the extruder temperature at m_writer, but throw away the generated G-code as it will be written with the custom G-code. + int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); + if (temp_by_gcode >= 0 && temp_by_gcode < 1000) + temp = temp_by_gcode; + m_writer.set_temperature(temp_by_gcode, wait, first_printing_extruder_id); + } else { + // Custom G-code does not set the extruder temperature. Do it now. if (print.config.single_extruder_multi_material.value) { // Set temperature of the first printing extruder only. int temp = print.config.first_layer_temperature.get_at(first_printing_extruder_id); @@ -953,18 +1080,22 @@ void GCode::process_layer( // Set new layer - this will change Z and force a retraction if retract_layer_change is enabled. if (! print.config.before_layer_gcode.value.empty()) { - PlaceholderParser pp(m_placeholder_parser); - pp.set("layer_num", m_layer_index + 1); - pp.set("layer_z", print_z); - gcode += pp.process(print.config.before_layer_gcode.value, m_writer.extruder()->id()) + "\n"; + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index + 1)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("before_layer_gcode", + print.config.before_layer_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; } gcode += this->change_layer(print_z); // this will increase m_layer_index m_layer = &layer; if (! print.config.layer_gcode.value.empty()) { - PlaceholderParser pp(m_placeholder_parser); - pp.set("layer_num", m_layer_index); - pp.set("layer_z", print_z); - gcode += pp.process(print.config.layer_gcode.value, m_writer.extruder()->id()) + "\n"; + DynamicConfig config; + config.set_key_value("layer_num", new ConfigOptionInt(m_layer_index)); + config.set_key_value("layer_z", new ConfigOptionFloat(print_z)); + gcode += this->placeholder_parser_process("layer_gcode", + print.config.layer_gcode.value, m_writer.extruder()->id(), &config) + + "\n"; } if (! first_layer && ! m_second_layer_things_done) { @@ -2141,13 +2272,14 @@ GCode::retract(bool toolchange) std::string GCode::set_extruder(unsigned int extruder_id) { - m_placeholder_parser.set("current_extruder", extruder_id); if (!m_writer.need_toolchange(extruder_id)) return ""; // if we are running a single-extruder setup, just set the extruder and return nothing - if (!m_writer.multiple_extruders) + if (!m_writer.multiple_extruders) { + m_placeholder_parser.set("current_extruder", extruder_id); return m_writer.toolchange(extruder_id); + } // prepend retraction on the current extruder std::string gcode = this->retract(true); @@ -2155,21 +2287,41 @@ std::string GCode::set_extruder(unsigned int extruder_id) // Always reset the extrusion path, even if the tool change retract is set to zero. m_wipe.reset_path(); - // append custom toolchange G-code - if (m_writer.extruder() != nullptr && !m_config.toolchange_gcode.value.empty()) { - PlaceholderParser pp = m_placeholder_parser; - pp.set("previous_extruder", m_writer.extruder()->id()); - pp.set("next_extruder", extruder_id); - gcode += pp.process(m_config.toolchange_gcode.value, extruder_id) + '\n'; + if (m_writer.extruder() != nullptr) { + // Process the custom end_filament_gcode in case of single_extruder_multi_material. + unsigned int old_extruder_id = m_writer.extruder()->id(); + const std::string &end_filament_gcode = m_config.end_filament_gcode.get_at(old_extruder_id); + if (m_config.single_extruder_multi_material && ! end_filament_gcode.empty()) { + gcode += placeholder_parser_process("end_filament_gcode", end_filament_gcode, old_extruder_id); + check_add_eol(gcode); + } + } + + m_placeholder_parser.set("current_extruder", extruder_id); + + if (m_writer.extruder() != nullptr && ! m_config.toolchange_gcode.value.empty()) { + // Process the custom toolchange_gcode. + DynamicConfig config; + config.set_key_value("previous_extruder", new ConfigOptionInt((int)m_writer.extruder()->id())); + config.set_key_value("next_extruder", new ConfigOptionInt((int)extruder_id)); + gcode += placeholder_parser_process("toolchange_gcode", m_config.toolchange_gcode.value, extruder_id, &config); + check_add_eol(gcode); } - // if ooze prevention is enabled, park current extruder in the nearest - // standby point and set it to the standby temperature + // If ooze prevention is enabled, park current extruder in the nearest + // standby point and set it to the standby temperature. if (m_ooze_prevention.enable && m_writer.extruder() != nullptr) gcode += m_ooze_prevention.pre_toolchange(*this); - // append the toolchange command + // Append the toolchange command. gcode += m_writer.toolchange(extruder_id); - // set the new extruder to the operating temperature + // Append the filament start G-code for single_extruder_multi_material. + const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id); + if (m_config.single_extruder_multi_material && ! start_filament_gcode.empty()) { + // Process the start_filament_gcode for the active filament only. + gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id); + check_add_eol(gcode); + } + // Set the new extruder to the operating temperature. if (m_ooze_prevention.enable) gcode += m_ooze_prevention.post_toolchange(*this); diff --git a/xs/src/libslic3r/GCode.hpp b/xs/src/libslic3r/GCode.hpp index b29a6ed2f..bb40c4ea3 100644 --- a/xs/src/libslic3r/GCode.hpp +++ b/xs/src/libslic3r/GCode.hpp @@ -91,7 +91,6 @@ public: m_brim_done(false) {} std::string prime(GCode &gcodegen); - static std::string prime_single_color_print(const Print & /* print */, unsigned int initial_tool, GCode & /* gcodegen */); void next_layer() { ++ m_layer_idx; m_tool_change_idx = 0; } std::string tool_change(GCode &gcodegen, int extruder_id, bool finish_layer); std::string finalize(GCode &gcodegen); @@ -132,7 +131,8 @@ public: {} ~GCode() {} - bool do_export(Print *print, const char *path); + // throws std::runtime_exception + void do_export(Print *print, const char *path); // Exported for the helper classes (OozePrevention, Wipe) and for the Perl binding for unit tests. const Pointf& origin() const { return m_origin; } @@ -144,6 +144,10 @@ public: const FullPrintConfig &config() const { return m_config; } const Layer* layer() const { return m_layer; } GCodeWriter& writer() { return m_writer; } + PlaceholderParser& placeholder_parser() { return m_placeholder_parser; } + // Process a template through the placeholder parser, collect error messages to be reported + // inside the generated string and after the G-code export finishes. + std::string placeholder_parser_process(const std::string &name, const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr); bool enable_cooling_markers() const { return m_enable_cooling_markers; } // For Perl bindings, to be used exclusively by unit tests. @@ -152,7 +156,7 @@ public: void apply_print_config(const PrintConfig &print_config); protected: - bool _do_export(Print &print, FILE *file); + void _do_export(Print &print, FILE *file); // Object and support extrusions of the same PrintObject at the same print_z. struct LayerToPrint @@ -224,6 +228,8 @@ protected: FullPrintConfig m_config; GCodeWriter m_writer; PlaceholderParser m_placeholder_parser; + // Collection of templates, on which the placeholder substitution failed. + std::set<std::string> m_placeholder_parser_failed_templates; OozePrevention m_ooze_prevention; Wipe m_wipe; AvoidCrossingPerimeters m_avoid_crossing_perimeters; @@ -272,7 +278,8 @@ protected: GCodeTimeEstimator m_time_estimator; std::string _extrude(const ExtrusionPath &path, std::string description = "", double speed = -1); - void _print_first_layer_extruder_temperatures(FILE *file, Print &print, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_bed_temperature(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); + void _print_first_layer_extruder_temperatures(FILE *file, Print &print, const std::string &gcode, unsigned int first_printing_extruder_id, bool wait); // this flag triggers first layer speeds bool on_first_layer() const { return m_layer != nullptr && m_layer->id() == 0; } diff --git a/xs/src/libslic3r/GCode/ToolOrdering.hpp b/xs/src/libslic3r/GCode/ToolOrdering.hpp index fe2c394c5..abf1aa6b2 100644 --- a/xs/src/libslic3r/GCode/ToolOrdering.hpp +++ b/xs/src/libslic3r/GCode/ToolOrdering.hpp @@ -53,14 +53,14 @@ public: void clear() { m_layer_tools.clear(); } - // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. + // Get the first extruder printing, including the extruder priming areas, returns -1 if there is no layer printed. unsigned int first_extruder() const { return m_first_printing_extruder; } // Get the first extruder printing the layer_tools, returns -1 if there is no layer printed. unsigned int last_extruder() const { return m_last_printing_extruder; } // For a multi-material print, the printing extruders are ordered in the order they shall be primed. - std::vector<unsigned int> all_extruders() const { return m_all_printing_extruders; } + const std::vector<unsigned int>& all_extruders() const { return m_all_printing_extruders; } // Find LayerTools with the closest print_z. LayerTools& tools_for_layer(coordf_t print_z); diff --git a/xs/src/libslic3r/GCode/WipeTower.hpp b/xs/src/libslic3r/GCode/WipeTower.hpp index c5fa27392..2c92e16e6 100644 --- a/xs/src/libslic3r/GCode/WipeTower.hpp +++ b/xs/src/libslic3r/GCode/WipeTower.hpp @@ -21,8 +21,8 @@ public: xy operator-(const xy &rhs) const { xy out(*this); out.x -= rhs.x; out.y -= rhs.y; return out; } xy& operator+=(const xy &rhs) { x += rhs.x; y += rhs.y; return *this; } xy& operator-=(const xy &rhs) { x -= rhs.x; y -= rhs.y; return *this; } - bool operator==(const xy &rhs) { return x == rhs.x && y == rhs.y; } - bool operator!=(const xy &rhs) { return x != rhs.x || y != rhs.y; } + bool operator==(const xy &rhs) const { return x == rhs.x && y == rhs.y; } + bool operator!=(const xy &rhs) const { return x != rhs.x || y != rhs.y; } float x; float y; }; @@ -109,7 +109,7 @@ public: // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - std::vector<unsigned int> tools, + const std::vector<unsigned int> &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp index 951d71075..b81a8b04b 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.cpp @@ -368,7 +368,7 @@ WipeTower::ToolChangeResult WipeTowerPrusaMM::prime( // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - std::vector<unsigned int> tools, + const std::vector<unsigned int> &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, diff --git a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp index 15a96d367..b8c7ab31f 100644 --- a/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp +++ b/xs/src/libslic3r/GCode/WipeTowerPrusaMM.hpp @@ -112,7 +112,7 @@ public: // print_z of the first layer. float first_layer_height, // Extruder indices, in the order to be primed. The last extruder will later print the wipe tower brim, print brim and the object. - std::vector<unsigned int> tools, + const std::vector<unsigned int> &tools, // If true, the last priming are will be the same as the other priming areas, and the rest of the wipe will be performed inside the wipe tower. // If false, the last priming are will be large enough to wipe the last extruder sufficiently. bool last_wipe_inside_wipe_tower, diff --git a/xs/src/libslic3r/PlaceholderParser.cpp b/xs/src/libslic3r/PlaceholderParser.cpp index e25ec9526..77ef64e8d 100644 --- a/xs/src/libslic3r/PlaceholderParser.cpp +++ b/xs/src/libslic3r/PlaceholderParser.cpp @@ -21,11 +21,42 @@ #endif #endif +#include <boost/algorithm/string.hpp> +// Unicode iterator to iterate over utf8. +#include <boost/regex/pending/unicode_iterator.hpp> + +// Spirit v2.5 allows you to suppress automatic generation +// of predefined terminals to speed up complation. With +// BOOST_SPIRIT_NO_PREDEFINED_TERMINALS defined, you are +// responsible in creating instances of the terminals that +// you need (e.g. see qi::uint_type uint_ below). +//#define BOOST_SPIRIT_NO_PREDEFINED_TERMINALS + +#define BOOST_RESULT_OF_USE_DECLTYPE +#define BOOST_SPIRIT_USE_PHOENIX_V3 +#include <boost/config/warning_disable.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/spirit/include/qi.hpp> +#include <boost/spirit/include/qi_lit.hpp> +#include <boost/spirit/include/phoenix_core.hpp> +#include <boost/spirit/include/phoenix_operator.hpp> +#include <boost/spirit/include/phoenix_fusion.hpp> +#include <boost/spirit/include/phoenix_stl.hpp> +#include <boost/spirit/include/phoenix_object.hpp> +#include <boost/fusion/include/adapt_struct.hpp> +#include <boost/spirit/repository/include/qi_distinct.hpp> +#include <boost/spirit/repository/include/qi_iter_pos.hpp> +#include <boost/variant/recursive_variant.hpp> +#include <boost/phoenix/bind/bind_function.hpp> + +#include <iostream> +#include <string> + namespace Slic3r { PlaceholderParser::PlaceholderParser() { - this->set("version", SLIC3R_VERSION); + this->set("version", std::string(SLIC3R_VERSION)); this->apply_env_variables(); this->update_timestamp(); } @@ -61,130 +92,880 @@ void PlaceholderParser::update_timestamp() // are expected to be addressed by the extruder ID, therefore // if a vector configuration value is addressed without an index, // a current extruder ID is used. -void PlaceholderParser::apply_config(const DynamicPrintConfig &config) +void PlaceholderParser::apply_config(const DynamicPrintConfig &rhs) { - for (const t_config_option_key &opt_key : config.keys()) { - const ConfigOptionDef* def = config.def()->get(opt_key); - if (def->multiline || opt_key == "post_process") + const ConfigDef *def = rhs.def(); + for (const t_config_option_key &opt_key : rhs.keys()) { + const ConfigOptionDef *opt_def = def->get(opt_key); + if ((opt_def->multiline && boost::ends_with(opt_key, "_gcode")) || opt_key == "post_process") continue; - - const ConfigOption* opt = config.option(opt_key); - const ConfigOptionVectorBase* optv = dynamic_cast<const ConfigOptionVectorBase*>(opt); - if (optv != nullptr && opt_key != "bed_shape") { - // set placeholders for options with multiple values - this->set(opt_key, optv->vserialize()); - } else if (const ConfigOptionPoint* optp = dynamic_cast<const ConfigOptionPoint*>(opt)) { - this->set(opt_key, optp->serialize()); - Pointf val = *optp; - this->set(opt_key + "_X", val.x); - this->set(opt_key + "_Y", val.y); - } else { - // set single-value placeholders - this->set(opt_key, opt->serialize()); - } + const ConfigOption *opt = rhs.option(opt_key); + // Store a copy of the config option. + // Convert FloatOrPercent values to floats first. + //FIXME there are some ratio_over chains, which end with empty ratio_with. + // For example, XXX_extrusion_width parameters are not handled by get_abs_value correctly. + this->set(opt_key, (opt->type() == coFloatOrPercent) ? + new ConfigOptionFloat(rhs.get_abs_value(opt_key)) : + opt->clone()); } } void PlaceholderParser::apply_env_variables() { - for (char** env = environ; *env; env++) { + for (char** env = environ; *env; ++ env) { if (strncmp(*env, "SLIC3R_", 7) == 0) { std::stringstream ss(*env); std::string key, value; std::getline(ss, key, '='); ss >> value; - this->set(key, value); } } } -void PlaceholderParser::set(const std::string &key, const std::string &value) -{ - m_single[key] = value; - m_multiple.erase(key); -} +namespace spirit = boost::spirit; +namespace qi = boost::spirit::qi; +namespace px = boost::phoenix; -void PlaceholderParser::set(const std::string &key, int value) +namespace client { - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + template<typename Iterator> + struct OptWithPos { + OptWithPos() {} + OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range<Iterator> it_range) : opt(opt), it_range(it_range) {} + ConfigOptionConstPtr opt = nullptr; + boost::iterator_range<Iterator> it_range; + }; -void PlaceholderParser::set(const std::string &key, unsigned int value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + template<typename ITERATOR> + std::ostream& operator<<(std::ostream& os, OptWithPos<ITERATOR> const& opt) + { + os << std::string(opt.it_range.begin(), opt.it_range.end()); + return os; + } -void PlaceholderParser::set(const std::string &key, double value) -{ - std::ostringstream ss; - ss << value; - this->set(key, ss.str()); -} + template<typename Iterator> + struct expr + { + expr() : type(TYPE_EMPTY) {} + explicit expr(bool b) : type(TYPE_BOOL) { data.b = b; } + explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_BOOL), it_range(it_begin, it_end) { data.b = b; } + explicit expr(int i) : type(TYPE_INT) { data.i = i; } + explicit expr(int i, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_INT), it_range(it_begin, it_end) { data.i = i; } + explicit expr(double d) : type(TYPE_DOUBLE) { data.d = d; } + explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : type(TYPE_DOUBLE), it_range(it_begin, it_end) { data.d = d; } + explicit expr(const char *s) : type(TYPE_STRING) { data.s = new std::string(s); } + explicit expr(const std::string &s) : type(TYPE_STRING) { data.s = new std::string(s); } + explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : + type(TYPE_STRING), it_range(it_begin, it_end) { data.s = new std::string(s); } + expr(const expr &rhs) : type(rhs.type), it_range(rhs.it_range) + { if (rhs.type == TYPE_STRING) data.s = new std::string(*rhs.data.s); else data.set(rhs.data); } + explicit expr(expr &&rhs) : type(rhs.type), it_range(rhs.it_range) + { data.set(rhs.data); rhs.type = TYPE_EMPTY; } + explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : type(rhs.type), it_range(it_begin, it_end) + { data.set(rhs.data); rhs.type = TYPE_EMPTY; } + ~expr() { this->reset(); } -void PlaceholderParser::set(const std::string &key, std::vector<std::string> values) -{ - m_single.erase(key); - if (values.empty()) - m_multiple.erase(key); - else - m_multiple[key] = values; -} + expr &operator=(const expr &rhs) + { + this->type = rhs.type; + this->it_range = rhs.it_range; + if (rhs.type == TYPE_STRING) + this->data.s = new std::string(*rhs.data.s); + else + this->data.set(rhs.data); + return *this; + } -std::string PlaceholderParser::process(std::string str, unsigned int current_extruder_id) const -{ - char key[2048]; + expr &operator=(expr &&rhs) + { + type = rhs.type; + this->it_range = rhs.it_range; + data.set(rhs.data); + rhs.type = TYPE_EMPTY; + return *this; + } + + void reset() + { + if (this->type == TYPE_STRING) + delete data.s; + this->type = TYPE_EMPTY; + } - // Replace extruder independent single options, like [foo]. - for (const auto &key_value : m_single) { - sprintf(key, "[%s]", key_value.first.c_str()); - const std::string &replace = key_value.second; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - str.replace(i, key_value.first.size() + 2, replace); - i += replace.size(); + bool& b() { return data.b; } + bool b() const { return data.b; } + void set_b(bool v) { this->reset(); this->data.b = v; this->type = TYPE_BOOL; } + int& i() { return data.i; } + int i() const { return data.i; } + void set_i(int v) { this->reset(); this->data.i = v; this->type = TYPE_INT; } + int as_i() const { return (this->type == TYPE_INT) ? this->i() : int(this->d()); } + double& d() { return data.d; } + double d() const { return data.d; } + void set_d(double v) { this->reset(); this->data.d = v; this->type = TYPE_DOUBLE; } + double as_d() const { return (this->type == TYPE_DOUBLE) ? this->d() : double(this->i()); } + std::string& s() { return *data.s; } + const std::string& s() const { return *data.s; } + void set_s(const std::string &s) { this->reset(); this->data.s = new std::string(s); this->type = TYPE_STRING; } + void set_s(std::string &&s) { this->reset(); this->data.s = new std::string(std::move(s)); this->type = TYPE_STRING; } + + std::string to_string() const + { + std::string out; + switch (type) { + case TYPE_BOOL: out = boost::to_string(data.b); break; + case TYPE_INT: out = boost::to_string(data.i); break; + case TYPE_DOUBLE: out = boost::to_string(data.d); break; + case TYPE_STRING: out = *data.s; break; + default: break; + } + return out; } + + union Data { + // Raw image of the other data members. + // The C++ compiler will consider a possible aliasing of char* with any other union member, + // therefore copying the raw data is safe. + char raw[8]; + bool b; + int i; + double d; + std::string *s; + + // Copy the largest member variable through char*, which will alias with all other union members by default. + void set(const Data &rhs) { memcpy(this->raw, rhs.raw, sizeof(rhs.raw)); } + } data; + + enum Type { + TYPE_EMPTY = 0, + TYPE_BOOL, + TYPE_INT, + TYPE_DOUBLE, + TYPE_STRING, + }; + + Type type; + + // Range of input iterators covering this expression. + // Used for throwing parse exceptions. + boost::iterator_range<Iterator> it_range; + + expr unary_minus(const Iterator start_pos) const + { + switch (this->type) { + case TYPE_INT : + return expr<Iterator>(- this->i(), start_pos, this->it_range.end()); + case TYPE_DOUBLE: + return expr<Iterator>(- this->d(), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot apply unary minus operator."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + + expr unary_not(const Iterator start_pos) const + { + switch (this->type) { + case TYPE_BOOL : + return expr<Iterator>(! this->b(), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot apply a not operator."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + + expr &operator+=(const expr &rhs) + { + if (this->type == TYPE_STRING) { + // Convert the right hand side to string and append. + *this->data.s += rhs.to_string(); + } else if (rhs.type == TYPE_STRING) { + // Conver the left hand side to string, append rhs. + this->data.s = new std::string(this->to_string() + rhs.s()); + this->type = TYPE_STRING; + } else { + const char *err_msg = "Cannot add non-numeric types."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() + rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i += rhs.i(); + } + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + expr &operator-=(const expr &rhs) + { + const char *err_msg = "Cannot subtract non-numeric types."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() - rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i -= rhs.i(); + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + expr &operator*=(const expr &rhs) + { + const char *err_msg = "Cannot multiply with non-numeric type."; + this->throw_if_not_numeric(err_msg); + rhs.throw_if_not_numeric(err_msg); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() * rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i *= rhs.i(); + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + expr &operator/=(const expr &rhs) + { + this->throw_if_not_numeric("Cannot divide a non-numeric type."); + rhs.throw_if_not_numeric("Cannot divide with a non-numeric type."); + if ((this->type == TYPE_INT) ? (rhs.i() == 0) : (rhs.d() == 0.)) + rhs.throw_exception("Division by zero"); + if (this->type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) { + double d = this->as_d() / rhs.as_d(); + this->data.d = d; + this->type = TYPE_DOUBLE; + } else + this->data.i /= rhs.i(); + this->it_range = boost::iterator_range<Iterator>(this->it_range.begin(), rhs.it_range.end()); + return *this; + } + + static void to_string2(expr &self, std::string &out) + { + out = self.to_string(); + } + + static void evaluate_boolean(expr &self, bool &out) + { + if (self.type != TYPE_BOOL) + self.throw_exception("Not a boolean expression"); + out = self.b(); + } + + // Is lhs==rhs? Store the result into lhs. + static void compare_op(expr &lhs, expr &rhs, char op) + { + bool value = false; + if ((lhs.type == TYPE_INT || lhs.type == TYPE_DOUBLE) && + (rhs.type == TYPE_INT || rhs.type == TYPE_DOUBLE)) { + // Both types are numeric. + value = (lhs.type == TYPE_DOUBLE || rhs.type == TYPE_DOUBLE) ? + (lhs.as_d() == rhs.as_d()) : (lhs.i() == rhs.i()); + } else if (lhs.type == TYPE_BOOL && rhs.type == TYPE_BOOL) { + // Both type are bool. + value = lhs.b() == rhs.b(); + } else if (lhs.type == TYPE_STRING || rhs.type == TYPE_STRING) { + // One type is string, the other could be converted to string. + value = lhs.to_string() == rhs.to_string(); + } else { + boost::throw_exception(qi::expectation_failure<Iterator>( + lhs.it_range.begin(), rhs.it_range.end(), spirit::info("*Cannot compare the types."))); + } + lhs.type = TYPE_BOOL; + lhs.data.b = (op == '=') ? value : !value; + } + static void equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '='); } + static void not_equal(expr &lhs, expr &rhs) { compare_op(lhs, rhs, '!'); } + + static void set_if(bool &cond, bool ¬_yet_consumed, std::string &str_in, std::string &str_out) + { + if (cond && not_yet_consumed) { + str_out = str_in; + not_yet_consumed = false; + } + } + + void throw_exception(const char *message) const + { + boost::throw_exception(qi::expectation_failure<Iterator>( + this->it_range.begin(), this->it_range.end(), spirit::info(std::string("*") + message))); + } + + void throw_if_not_numeric(const char *message) const + { + if (this->type != TYPE_INT && this->type != TYPE_DOUBLE) + this->throw_exception(message); + } + }; + + template<typename ITERATOR> + std::ostream& operator<<(std::ostream &os, const expr<ITERATOR> &expression) + { + typedef expr<ITERATOR> Expr; + os << std::string(expression.it_range.begin(), expression.it_range.end()) << " - "; + switch (expression.type) { + case Expr::TYPE_EMPTY: os << "empty"; break; + case Expr::TYPE_BOOL: os << "bool (" << expression.b() << ")"; break; + case Expr::TYPE_INT: os << "int (" << expression.i() << ")"; break; + case Expr::TYPE_DOUBLE: os << "double (" << expression.d() << ")"; break; + case Expr::TYPE_STRING: os << "string (" << expression.s() << ")"; break; + default: os << "unknown"; + }; + return os; } - // Replace extruder dependent single options with the value for the active extruder. - // For example, [temperature] will be replaced with the current extruder temperature. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s]", key_value.first.c_str()); - const std::string &replace = key_value.second[(current_extruder_id < key_value.second.size()) ? current_extruder_id : 0]; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - str.replace(i, key_value.first.size() + 2, replace); - i += replace.size(); + struct MyContext { + const PlaceholderParser *pp = nullptr; + const DynamicConfig *config_override = nullptr; + const size_t current_extruder_id = 0; + std::string error_message; + + const ConfigOption* resolve_symbol(const std::string &opt_key) const + { + const ConfigOption *opt = nullptr; + if (config_override != nullptr) + opt = config_override->option(opt_key); + if (opt == nullptr) + opt = pp->option(opt_key); + return opt; } + + template <typename Iterator> + static void legacy_variable_expansion( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + std::string &output) + { + std::string opt_key_str(opt_key.begin(), opt_key.end()); + const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); + size_t idx = ctx->current_extruder_id; + if (opt == nullptr) { + // Check whether this is a legacy vector indexing. + idx = opt_key_str.rfind('_'); + if (idx != std::string::npos) { + opt = ctx->resolve_symbol(opt_key_str.substr(0, idx)); + if (opt != nullptr) { + if (! opt->is_vector()) + ctx->throw_exception("Trying to index a scalar variable", opt_key); + char *endptr = nullptr; + idx = strtol(opt_key_str.c_str() + idx + 1, &endptr, 10); + if (endptr == nullptr || *endptr != 0) + ctx->throw_exception("Invalid vector index", boost::iterator_range<Iterator>(opt_key.begin() + idx + 1, opt_key.end())); + } + } + } + if (opt == nullptr) + ctx->throw_exception("Variable does not exist", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end())); + if (opt->is_scalar()) + output = opt->serialize(); + else { + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt_key); + output = vec->vserialize()[(idx >= vec->size()) ? 0 : idx]; + } + } + + template <typename Iterator> + static void legacy_variable_expansion2( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + boost::iterator_range<Iterator> &opt_vector_index, + std::string &output) + { + std::string opt_key_str(opt_key.begin(), opt_key.end()); + const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); + if (opt == nullptr) { + // Check whether the opt_key ends with '_'. + if (opt_key_str.back() == '_') + opt_key_str.resize(opt_key_str.size() - 1); + opt = ctx->resolve_symbol(opt_key_str); + } + if (! opt->is_vector()) + ctx->throw_exception("Trying to index a scalar variable", opt_key); + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", boost::iterator_range<Iterator>(opt_key.begin(), opt_key.end())); + const ConfigOption *opt_index = ctx->resolve_symbol(std::string(opt_vector_index.begin(), opt_vector_index.end())); + if (opt_index == nullptr) + ctx->throw_exception("Variable does not exist", opt_key); + if (opt_index->type() != coInt) + ctx->throw_exception("Indexing variable has to be integer", opt_key); + int idx = opt_index->getInt(); + if (idx < 0) + ctx->throw_exception("Negative vector index", opt_key); + output = vec->vserialize()[(idx >= (int)vec->size()) ? 0 : idx]; + } + + template <typename Iterator> + static void resolve_variable( + const MyContext *ctx, + boost::iterator_range<Iterator> &opt_key, + OptWithPos<Iterator> &output) + { + const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); + if (opt == nullptr) + ctx->throw_exception("Not a variable name", opt_key); + output.opt = opt; + output.it_range = opt_key; + } + + template <typename Iterator> + static void scalar_variable_reference( + const MyContext *ctx, + OptWithPos<Iterator> &opt, + expr<Iterator> &output) + { + if (opt.opt->is_vector()) + ctx->throw_exception("Referencing a scalar variable in a vector context", opt.it_range); + switch (opt.opt->type()) { + case coFloat: output.set_d(opt.opt->getFloat()); break; + case coInt: output.set_i(opt.opt->getInt()); break; + case coString: output.set_s(static_cast<const ConfigOptionString*>(opt.opt)->value); break; + case coPercent: output.set_d(opt.opt->getFloat()); break; + case coPoint: output.set_s(opt.opt->serialize()); break; + case coBool: output.set_b(opt.opt->getBool()); break; + case coFloatOrPercent: + ctx->throw_exception("FloatOrPercent variables are not supported", opt.it_range); + default: + ctx->throw_exception("Unknown scalar variable type", opt.it_range); + } + output.it_range = opt.it_range; + } + + template <typename Iterator> + static void vector_variable_reference( + const MyContext *ctx, + OptWithPos<Iterator> &opt, + int &index, + Iterator it_end, + expr<Iterator> &output) + { + if (opt.opt->is_scalar()) + ctx->throw_exception("Referencing a vector variable in a scalar context", opt.it_range); + const ConfigOptionVectorBase *vec = static_cast<const ConfigOptionVectorBase*>(opt.opt); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", opt.it_range); + size_t idx = (index < 0) ? 0 : (index >= int(vec->size())) ? 0 : size_t(index); + switch (opt.opt->type()) { + case coFloats: output.set_d(static_cast<const ConfigOptionFloats *>(opt.opt)->values[idx]); break; + case coInts: output.set_i(static_cast<const ConfigOptionInts *>(opt.opt)->values[idx]); break; + case coStrings: output.set_s(static_cast<const ConfigOptionStrings *>(opt.opt)->values[idx]); break; + case coPercents: output.set_d(static_cast<const ConfigOptionPercents*>(opt.opt)->values[idx]); break; + case coPoints: output.set_s(static_cast<const ConfigOptionPoints *>(opt.opt)->values[idx].dump_perl()); break; + case coBools: output.set_b(static_cast<const ConfigOptionBools *>(opt.opt)->values[idx] != 0); break; + default: + ctx->throw_exception("Unknown vector variable type", opt.it_range); + } + output.it_range = boost::iterator_range<Iterator>(opt.it_range.begin(), it_end); + } + + // Verify that the expression returns an integer, which may be used + // to address a vector. + template <typename Iterator> + static void evaluate_index(expr<Iterator> &expr_index, int &output) + { + if (expr_index.type != expr<Iterator>::TYPE_INT) + expr_index.throw_exception("Non-integer index is not allowed to address a vector variable."); + output = expr_index.i(); + } + + template <typename Iterator> + static void throw_exception(const std::string &msg, const boost::iterator_range<Iterator> &it_range) + { + // An asterix is added to the start of the string to differentiate the boost::spirit::info::tag content + // between the grammer terminal / non-terminal symbol name and a free-form error message. + boost::throw_exception(qi::expectation_failure<Iterator>(it_range.begin(), it_range.end(), spirit::info(std::string("*") + msg))); + } + + template <typename Iterator> + static void process_error_message(const MyContext *context, const boost::spirit::info &info, const Iterator &it_begin, const Iterator &it_end, const Iterator &it_error) + { + std::string &msg = const_cast<MyContext*>(context)->error_message; + std::string first(it_begin, it_error); + std::string last(it_error, it_end); + auto first_pos = first.rfind('\n'); + auto last_pos = last.find('\n'); + int line_nr = 1; + if (first_pos == std::string::npos) + first_pos = 0; + else { + // Calculate the current line number. + for (size_t i = 0; i <= first_pos; ++ i) + if (first[i] == '\n') + ++ line_nr; + ++ first_pos; + } + auto error_line = std::string(first, first_pos) + std::string(last, 0, last_pos); + // Position of the it_error from the start of its line. + auto error_pos = (it_error - it_begin) - first_pos; + msg += "Parsing error at line " + std::to_string(line_nr); + if (! info.tag.empty() && info.tag.front() == '*') { + // The gat contains an explanatory string. + msg += ": "; + msg += info.tag.substr(1); + } else { + // A generic error report based on the nonterminal or terminal symbol name. + msg += ". Expecting tag "; + msg += info.tag; + } + msg += '\n'; + msg += error_line; + msg += '\n'; + for (size_t i = 0; i < error_pos; ++ i) + msg += ' '; + msg += "^\n"; + } + }; + + // For debugging the boost::spirit parsers. Print out the string enclosed in it_range. + template<typename Iterator> + std::ostream& operator<<(std::ostream& os, const boost::iterator_range<Iterator> &it_range) + { + os << std::string(it_range.begin(), it_range.end()); + return os; } - // Replace multiple options like [foo_0]. - for (const auto &key_value : m_multiple) { - sprintf(key, "[%s_", key_value.first.c_str()); - const std::vector<std::string> &values = key_value.second; - for (size_t i = 0; (i = str.find(key, i)) != std::string::npos;) { - size_t k = str.find(']', i + key_value.first.size() + 2); - if (k != std::string::npos) { - // Parse the key index and the closing bracket. - ++ k; - int idx = 0; - if (sscanf(str.c_str() + i + key_value.first.size() + 2, "%d]", &idx) == 1 && idx >= 0) { - if (idx >= int(values.size())) - idx = 0; - str.replace(i, k - i, values[idx]); - i += values[idx].size(); - continue; + // Disable parsing int numbers (without decimals) and Inf/NaN symbols by the double parser. + struct strict_real_policies_without_nan_inf : public qi::strict_real_policies<double> + { + template <typename It, typename Attr> static bool parse_nan(It&, It const&, Attr&) { return false; } + template <typename It, typename Attr> static bool parse_inf(It&, It const&, Attr&) { return false; } + }; + + // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. + // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. + struct utf8_char_skipper_parser : qi::primitive_parser<utf8_char_skipper_parser> + { + // Define the attribute type exposed by this parser component + template <typename Context, typename Iterator> + struct attribute + { + typedef wchar_t type; + }; + + // This function is called during the actual parsing process + template <typename Iterator, typename Context , typename Skipper, typename Attribute> + bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const + { + // The skipper shall always be empty, any white space will be accepted. + // skip_over(first, last, skipper); + if (first == last) + return false; + // Iterator over the UTF-8 sequence. + auto it = first; + // Read the first byte of the UTF-8 sequence. + unsigned char c = static_cast<boost::uint8_t>(*it ++); + unsigned int cnt = 0; + // UTF-8 sequence must not start with a continuation character: + if ((c & 0xC0) == 0x80) + goto err; + // Skip high surrogate first if there is one. + // If the most significant bit with a zero in it is in position + // 8-N then there are N bytes in this UTF-8 sequence: + { + unsigned char mask = 0x80u; + unsigned int result = 0; + while (c & mask) { + ++ result; + mask >>= 1; } + cnt = (result == 0) ? 1 : ((result > 4) ? 4 : result); + } + // Since we haven't read in a value, we need to validate the code points: + for (-- cnt; cnt > 0; -- cnt) { + if (it == last) + goto err; + c = static_cast<boost::uint8_t>(*it ++); + // We must have a continuation byte: + if (cnt > 1 && (c & 0xC0) != 0x80) + goto err; } - // The key does not match the pattern [foo_%d]. Skip just [foo_.] with the hope that there was a missing ']', - // so an opening '[' may be found somewhere before the position k. - i += key_value.first.size() + 3; + first = it; + return true; + err: + MyContext::throw_exception("Invalid utf8 sequence", boost::iterator_range<Iterator>(first, last)); + return false; } + + // This function is called during error handling to create a human readable string for the error context. + template <typename Context> + spirit::info what(Context&) const + { + return spirit::info("unicode_char"); + } + }; + + /////////////////////////////////////////////////////////////////////////// + // Our calculator grammar + /////////////////////////////////////////////////////////////////////////// + // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html + template <typename Iterator> + struct calculator : qi::grammar<Iterator, std::string(const MyContext*), spirit::ascii::space_type> + { + calculator() : calculator::base_type(start) + { + using namespace qi::labels; + qi::alpha_type alpha; + qi::alnum_type alnum; + qi::eps_type eps; + qi::raw_type raw; + qi::lit_type lit; + qi::lexeme_type lexeme; + qi::no_skip_type no_skip; + qi::real_parser<double, strict_real_policies_without_nan_inf> strict_double; + spirit::ascii::char_type char_; + utf8_char_skipper_parser utf8char; + spirit::bool_type bool_; + spirit::int_type int_; + spirit::double_type double_; + spirit::ascii::string_type string; + spirit::repository::qi::iter_pos_type iter_pos; + auto kw = spirit::repository::qi::distinct(qi::copy(alnum | '_')); + + qi::_val_type _val; + qi::_1_type _1; + qi::_2_type _2; + qi::_3_type _3; + qi::_4_type _4; + qi::_a_type _a; + qi::_b_type _b; + qi::_r1_type _r1; + + // Starting symbol of the grammer. + // The leading eps is required by the "expectation point" operator ">". + // Without it, some of the errors would not trigger the error handler. + start = eps > text_block(_r1); + start.name("start"); + qi::on_error<qi::fail>(start, px::bind(&MyContext::process_error_message<Iterator>, _r1, _4, _1, _2, _3)); + + text_block = *( + text [_val+=_1] + // Allow back tracking after '{' in case of a text_block embedded inside a condition. + // In that case the inner-most {else} wins and the {if}/{elsif}/{else} shall be paired. + // {elsif}/{else} without an {if} will be allowed to back track from the embedded text_block. + | (lit('{') >> macro(_r1) [_val+=_1] > '}') + | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']') + ); + text_block.name("text_block"); + + // Free-form text up to a first brace, including spaces and newlines. + // The free-form text will be inserted into the processed text without a modification. + text = no_skip[raw[+(utf8char - char_('[') - char_('{'))]]; + text.name("text"); + + // New style of macro expansion. + // The macro expansion may contain numeric or string expressions, ifs and cases. + macro = + (kw["if"] > if_else_output(_r1) [_val = _1]) + | (kw["switch"] > switch_output(_r1) [_val = _1]) + | additive_expression(_r1) [ px::bind(&expr<Iterator>::to_string2, _1, _val) ]; + macro.name("macro"); + + // An if expression enclosed in {} (the outmost {} are already parsed by the caller). + if_else_output = + eps[_b=true] > + bool_expr_eval(_r1)[_a=_1] > '}' > + text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{' > + *(kw["elsif"] > bool_expr_eval(_r1)[_a=_1] > '}' > + text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)] > '{') > + -(kw["else"] > lit('}') > + text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)] > '{') > + kw["endif"]; + if_else_output.name("if_else_output"); + // A switch expression enclosed in {} (the outmost {} are already parsed by the caller). +/* + switch_output = + eps[_b=true] > + omit[expr(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if_equal, _a, _b, _1, _val)] > '{' > + *("elsif" > omit[bool_expr_eval(_r1)[_a=_1]] > '}' > text_block(_r1)[px::bind(&expr<Iterator>::set_if, _a, _b, _1, _val)]) >> + -("else" > '}' >> text_block(_r1)[px::bind(&expr<Iterator>::set_if, _b, _b, _1, _val)]) > + "endif"; +*/ + + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + legacy_variable_expansion = + (identifier >> &lit(']')) + [ px::bind(&MyContext::legacy_variable_expansion<Iterator>, _r1, _1, _val) ] + | (identifier > lit('[') > identifier > ']') + [ px::bind(&MyContext::legacy_variable_expansion2<Iterator>, _r1, _1, _2, _val) ] + ; + legacy_variable_expansion.name("legacy_variable_expansion"); + + identifier = + ! kw[keywords] >> + raw[lexeme[(alpha | '_') >> *(alnum | '_')]]; + identifier.name("identifier"); + + bool_expr = + additive_expression(_r1) [_val = _1] + >> *( ("==" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::equal, _val, _1)] + | ("!=" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)] + | ("<>" > additive_expression(_r1) ) [px::bind(&expr<Iterator>::not_equal, _val, _1)] + ); + bool_expr.name("bool expression"); + + // Evaluate a boolean expression stored as expr into a boolean value. + // Throw if the bool_expr does not produce a expr of boolean type. + bool_expr_eval = bool_expr(_r1) [ px::bind(&expr<Iterator>::evaluate_boolean, _1, _val) ]; + bool_expr_eval.name("bool_expr_eval"); + + additive_expression = + term(_r1) [_val = _1] + >> *( (lit('+') > term(_r1) ) [_val += _1] + | (lit('-') > term(_r1) ) [_val -= _1] + ); + additive_expression.name("additive_expression"); + + term = + factor(_r1) [_val = _1] + >> *( (lit('*') > factor(_r1) ) [_val *= _1] + | (lit('/') > factor(_r1) ) [_val /= _1] + ); + term.name("term"); + + struct FactorActions { + static void set_start_pos(Iterator &start_pos, expr<Iterator> &out) + { out.it_range = boost::iterator_range<Iterator>(start_pos, start_pos); } + static void int_(int &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(value, out.it_range.begin(), end_pos); } + static void double_(double &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(value, out.it_range.begin(), end_pos); } + static void bool_(bool &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(value, out.it_range.begin(), end_pos); } + static void string_(boost::iterator_range<Iterator> &it_range, expr<Iterator> &out) + { out = expr<Iterator>(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); } + static void expr_(expr<Iterator> &value, Iterator &end_pos, expr<Iterator> &out) + { out = expr<Iterator>(std::move(value), out.it_range.begin(), end_pos); } + static void minus_(expr<Iterator> &value, expr<Iterator> &out) + { out = value.unary_minus(out.it_range.begin()); } + static void not_(expr<Iterator> &value, expr<Iterator> &out) + { out = value.unary_not(out.it_range.begin()); } + }; + factor = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( + scalar_variable_reference(_r1) [ _val = _1 ] + | (lit('(') > additive_expression(_r1) > ')' > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | (lit('-') > factor(_r1) ) [ px::bind(&FactorActions::minus_, _1, _val) ] + | (lit('+') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::expr_, _1, _2, _val) ] + | ((kw["not"] | '!') > factor(_r1) > iter_pos) [ px::bind(&FactorActions::not_, _1, _val) ] + | (strict_double > iter_pos) [ px::bind(&FactorActions::double_, _1, _2, _val) ] + | (int_ > iter_pos) [ px::bind(&FactorActions::int_, _1, _2, _val) ] + | (kw[bool_] > iter_pos) [ px::bind(&FactorActions::bool_, _1, _2, _val) ] + | raw[lexeme['"' > *((utf8char - char_('\\') - char_('"')) | ('\\' > char_)) > '"']] + [ px::bind(&FactorActions::string_, _1, _val) ] + ); + factor.name("factor"); + + scalar_variable_reference = + variable_reference(_r1)[_a=_1] >> + ( + ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index<Iterator>, _1, _b)] > ']' > + iter_pos[px::bind(&MyContext::vector_variable_reference<Iterator>, _r1, _a, _b, _1, _val)]) + | eps[px::bind(&MyContext::scalar_variable_reference<Iterator>, _r1, _a, _val)] + ); + scalar_variable_reference.name("scalar variable reference"); + + variable_reference = identifier + [ px::bind(&MyContext::resolve_variable<Iterator>, _r1, _1, _val) ]; + variable_reference.name("variable reference"); + + keywords.add + ("and") + ("if") + //("inf") + ("else") + ("elsif") + ("endif") + ("false") + ("not") + ("or") + ("true"); + + if (0) { + debug(start); + debug(text); + debug(text_block); + debug(macro); + debug(if_else_output); + debug(switch_output); + debug(legacy_variable_expansion); + debug(identifier); + debug(bool_expr); + debug(bool_expr_eval); + debug(additive_expression); + debug(term); + debug(factor); + debug(scalar_variable_reference); + debug(variable_reference); + } + } + + // The start of the grammar. + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> start; + // A free-form text. + qi::rule<Iterator, std::string(), spirit::ascii::space_type> text; + // A free-form text, possibly empty, possibly containing macro expansions. + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> text_block; + // Statements enclosed in curely braces {} + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> macro; + // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. + qi::rule<Iterator, std::string(const MyContext*), spirit::ascii::space_type> legacy_variable_expansion; + // Parsed identifier name. + qi::rule<Iterator, boost::iterator_range<Iterator>(), spirit::ascii::space_type> identifier; + // Math expression consisting of +- operators over terms. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> additive_expression; + // Boolean expressions over expressions. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> bool_expr; + // Evaluate boolean expression into bool. + qi::rule<Iterator, bool(const MyContext*), spirit::ascii::space_type> bool_expr_eval; + // Math expression consisting of */ operators over factors. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> term; + // Number literals, functions, braced expressions, variable references, variable indexing references. + qi::rule<Iterator, expr<Iterator>(const MyContext*), spirit::ascii::space_type> factor; + // Reference of a scalar variable, or reference to a field of a vector variable. + qi::rule<Iterator, expr<Iterator>(const MyContext*), qi::locals<OptWithPos<Iterator>, int>, spirit::ascii::space_type> scalar_variable_reference; + // Rule to translate an identifier to a ConfigOption, or to fail. + qi::rule<Iterator, OptWithPos<Iterator>(const MyContext*), spirit::ascii::space_type> variable_reference; + + qi::rule<Iterator, std::string(const MyContext*), qi::locals<bool, bool>, spirit::ascii::space_type> if_else_output; + qi::rule<Iterator, std::string(const MyContext*), qi::locals<expr<Iterator>, bool, std::string>, spirit::ascii::space_type> switch_output; + + qi::symbols<char> keywords; + }; +} + +std::string PlaceholderParser::process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override) const +{ + typedef std::string::const_iterator iterator_type; + typedef client::calculator<iterator_type> calculator; + + // Our whitespace skipper. + spirit::ascii::space_type space; + // Our grammar. + calculator calc; + // Iterators over the source template. + std::string::const_iterator iter = templ.begin(); + std::string::const_iterator end = templ.end(); + // Accumulator for the processed template. + std::string output; + client::MyContext context; + context.pp = this; + context.config_override = config_override; + bool res = phrase_parse(iter, end, calc(&context), space, output); + if (! context.error_message.empty()) { + if (context.error_message.back() != '\n' && context.error_message.back() != '\r') + context.error_message += '\n'; + throw std::runtime_error(context.error_message); } - - return str; + return output; } } diff --git a/xs/src/libslic3r/PlaceholderParser.hpp b/xs/src/libslic3r/PlaceholderParser.hpp index a8abf8871..25264d461 100644 --- a/xs/src/libslic3r/PlaceholderParser.hpp +++ b/xs/src/libslic3r/PlaceholderParser.hpp @@ -11,23 +11,28 @@ namespace Slic3r { class PlaceholderParser { -public: - std::map<std::string, std::string> m_single; - std::map<std::string, std::vector<std::string>> m_multiple; - +public: PlaceholderParser(); + void update_timestamp(); void apply_config(const DynamicPrintConfig &config); void apply_env_variables(); - void set(const std::string &key, const std::string &value); - void set(const std::string &key, int value); - void set(const std::string &key, unsigned int value); - void set(const std::string &key, double value); - void set(const std::string &key, std::vector<std::string> values); - std::string process(std::string str, unsigned int current_extruder_id) const; + + // Add new ConfigOption values to m_config. + void set(const std::string &key, const std::string &value) { this->set(key, new ConfigOptionString(value)); } + void set(const std::string &key, int value) { this->set(key, new ConfigOptionInt(value)); } + void set(const std::string &key, unsigned int value) { this->set(key, int(value)); } + void set(const std::string &key, bool value) { this->set(key, new ConfigOptionBool(value)); } + void set(const std::string &key, double value) { this->set(key, new ConfigOptionFloat(value)); } + void set(const std::string &key, const std::vector<std::string> &values) { this->set(key, new ConfigOptionStrings(values)); } + void set(const std::string &key, ConfigOption *opt) { m_config.set_key_value(key, opt); } + const ConfigOption* option(const std::string &key) const { return m_config.option(key); } + + // Fill in the template. + std::string process(const std::string &templ, unsigned int current_extruder_id, const DynamicConfig *config_override = nullptr) const; private: - bool find_and_replace(std::string &source, std::string const &find, std::string const &replace) const; + DynamicConfig m_config; }; } diff --git a/xs/src/libslic3r/Polygon.hpp b/xs/src/libslic3r/Polygon.hpp index 6ea300cee..f36abc185 100644 --- a/xs/src/libslic3r/Polygon.hpp +++ b/xs/src/libslic3r/Polygon.hpp @@ -154,7 +154,6 @@ inline Polylines to_polylines(const Polygons &polys) return polylines; } -#if SLIC3R_CPPVER >= 11 inline Polylines to_polylines(Polygons &&polys) { Polylines polylines; @@ -168,7 +167,6 @@ inline Polylines to_polylines(Polygons &&polys) assert(idx == polylines.size()); return polylines; } -#endif } // Slic3r diff --git a/xs/src/libslic3r/Print.cpp b/xs/src/libslic3r/Print.cpp index cbf0a3b46..190fbfe6e 100644 --- a/xs/src/libslic3r/Print.cpp +++ b/xs/src/libslic3r/Print.cpp @@ -81,80 +81,80 @@ bool Print::invalidate_state_by_config_options(const std::vector<t_config_option // Cache the plenty of parameters, which influence the G-code generator only, // or they are only notes not influencing the generated G-code. - static std::unordered_set<std::string> steps_ignore; - if (steps_ignore.empty()) { - steps_ignore.insert("avoid_crossing_perimeters"); - steps_ignore.insert("bed_shape"); - steps_ignore.insert("bed_temperature"); - steps_ignore.insert("before_layer_gcode"); - steps_ignore.insert("bridge_acceleration"); - steps_ignore.insert("bridge_fan_speed"); - steps_ignore.insert("cooling"); - steps_ignore.insert("default_acceleration"); - steps_ignore.insert("deretract_speed"); - steps_ignore.insert("disable_fan_first_layers"); - steps_ignore.insert("duplicate_distance"); - steps_ignore.insert("end_gcode"); - steps_ignore.insert("end_filament_gcode"); - steps_ignore.insert("extrusion_axis"); - steps_ignore.insert("extruder_clearance_height"); - steps_ignore.insert("extruder_clearance_radius"); - steps_ignore.insert("extruder_colour"); - steps_ignore.insert("extruder_offset"); - steps_ignore.insert("extrusion_multiplier"); - steps_ignore.insert("fan_always_on"); - steps_ignore.insert("fan_below_layer_time"); - steps_ignore.insert("filament_colour"); - steps_ignore.insert("filament_diameter"); - steps_ignore.insert("filament_density"); - steps_ignore.insert("filament_notes"); - steps_ignore.insert("filament_cost"); - steps_ignore.insert("filament_max_volumetric_speed"); - steps_ignore.insert("first_layer_acceleration"); - steps_ignore.insert("first_layer_bed_temperature"); - steps_ignore.insert("first_layer_speed"); - steps_ignore.insert("gcode_comments"); - steps_ignore.insert("gcode_flavor"); - steps_ignore.insert("infill_acceleration"); - steps_ignore.insert("infill_first"); - steps_ignore.insert("layer_gcode"); - steps_ignore.insert("min_fan_speed"); - steps_ignore.insert("max_fan_speed"); - steps_ignore.insert("min_print_speed"); - steps_ignore.insert("max_print_speed"); - steps_ignore.insert("max_volumetric_speed"); - steps_ignore.insert("max_volumetric_extrusion_rate_slope_positive"); - steps_ignore.insert("max_volumetric_extrusion_rate_slope_negative"); - steps_ignore.insert("notes"); - steps_ignore.insert("only_retract_when_crossing_perimeters"); - steps_ignore.insert("output_filename_format"); - steps_ignore.insert("perimeter_acceleration"); - steps_ignore.insert("post_process"); - steps_ignore.insert("printer_notes"); - steps_ignore.insert("retract_before_travel"); - steps_ignore.insert("retract_before_wipe"); - steps_ignore.insert("retract_layer_change"); - steps_ignore.insert("retract_length"); - steps_ignore.insert("retract_length_toolchange"); - steps_ignore.insert("retract_lift"); - steps_ignore.insert("retract_lift_above"); - steps_ignore.insert("retract_lift_below"); - steps_ignore.insert("retract_restart_extra"); - steps_ignore.insert("retract_restart_extra_toolchange"); - steps_ignore.insert("retract_speed"); - steps_ignore.insert("slowdown_below_layer_time"); - steps_ignore.insert("standby_temperature_delta"); - steps_ignore.insert("start_gcode"); - steps_ignore.insert("start_filament_gcode"); - steps_ignore.insert("toolchange_gcode"); - steps_ignore.insert("threads"); - steps_ignore.insert("travel_speed"); - steps_ignore.insert("use_firmware_retraction"); - steps_ignore.insert("use_relative_e_distances"); - steps_ignore.insert("use_volumetric_e"); - steps_ignore.insert("variable_layer_height"); - steps_ignore.insert("wipe"); - } + static std::unordered_set<std::string> steps_ignore = { + "avoid_crossing_perimeters", + "bed_shape", + "bed_temperature", + "before_layer_gcode", + "between_objects_gcode", + "bridge_acceleration", + "bridge_fan_speed", + "cooling", + "default_acceleration", + "deretract_speed", + "disable_fan_first_layers", + "duplicate_distance", + "end_gcode", + "end_filament_gcode", + "extrusion_axis", + "extruder_clearance_height", + "extruder_clearance_radius", + "extruder_colour", + "extruder_offset", + "extrusion_multiplier", + "fan_always_on", + "fan_below_layer_time", + "filament_colour", + "filament_diameter", + "filament_density", + "filament_notes", + "filament_cost", + "filament_max_volumetric_speed", + "first_layer_acceleration", + "first_layer_bed_temperature", + "first_layer_speed", + "gcode_comments", + "gcode_flavor", + "infill_acceleration", + "infill_first", + "layer_gcode", + "min_fan_speed", + "max_fan_speed", + "min_print_speed", + "max_print_speed", + "max_volumetric_speed", + "max_volumetric_extrusion_rate_slope_positive", + "max_volumetric_extrusion_rate_slope_negative", + "notes", + "only_retract_when_crossing_perimeters", + "output_filename_format", + "perimeter_acceleration", + "post_process", + "printer_notes", + "retract_before_travel", + "retract_before_wipe", + "retract_layer_change", + "retract_length", + "retract_length_toolchange", + "retract_lift", + "retract_lift_above", + "retract_lift_below", + "retract_restart_extra", + "retract_restart_extra_toolchange", + "retract_speed", + "slowdown_below_layer_time", + "standby_temperature_delta", + "start_gcode", + "start_filament_gcode", + "toolchange_gcode", + "threads", + "travel_speed", + "use_firmware_retraction", + "use_relative_e_distances", + "use_volumetric_e", + "variable_layer_height", + "wipe" + }; std::vector<PrintStep> steps; std::vector<PrintObjectStep> osteps; @@ -707,7 +707,10 @@ double Print::skirt_first_layer_height() const Flow Print::brim_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; - if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->objects.front()->config.extrusion_width; /* We currently use a random region's perimeter extruder. While this works for most cases, we should probably consider all of the perimeter @@ -726,7 +729,10 @@ Flow Print::brim_flow() const Flow Print::skirt_flow() const { ConfigOptionFloatOrPercent width = this->config.first_layer_extrusion_width; - if (width.value == 0) width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->regions.front()->config.perimeter_extrusion_width; + if (width.value == 0) + width = this->objects.front()->config.extrusion_width; /* We currently use a random object's support material extruder. While this works for most cases, we should probably consider all of the support material @@ -968,7 +974,6 @@ void Print::_make_wipe_tower() // Let the ToolOrdering class know there will be initial priming extrusions at the start of the print. m_tool_ordering = ToolOrdering(*this, (unsigned int)-1, true); - unsigned int initial_extruder_id = m_tool_ordering.first_extruder(); if (! m_tool_ordering.has_wipe_tower()) // Don't generate any wipe tower. return; @@ -977,7 +982,7 @@ void Print::_make_wipe_tower() WipeTowerPrusaMM wipe_tower( float(this->config.wipe_tower_x.value), float(this->config.wipe_tower_y.value), float(this->config.wipe_tower_width.value), float(this->config.wipe_tower_per_color_wipe.value), - initial_extruder_id); + m_tool_ordering.first_extruder()); //wipe_tower.set_retract(); //wipe_tower.set_zhop(); @@ -1000,7 +1005,8 @@ void Print::_make_wipe_tower() // Generate the wipe tower layers. m_wipe_tower_tool_changes.reserve(m_tool_ordering.layer_tools().size()); - unsigned int current_extruder_id = initial_extruder_id; + // Set current_extruder_id to the last extruder primed. + unsigned int current_extruder_id = m_tool_ordering.all_extruders().back(); for (const ToolOrdering::LayerTools &layer_tools : m_tool_ordering.layer_tools()) { if (! layer_tools.has_wipe_tower) // This is a support only layer, or the wipe tower does not reach to this height. @@ -1015,7 +1021,11 @@ void Print::_make_wipe_tower() last_layer); std::vector<WipeTower::ToolChangeResult> tool_changes; for (unsigned int extruder_id : layer_tools.extruders) - if ((first_layer && extruder_id == initial_extruder_id) || extruder_id != current_extruder_id) { + // Call the wipe_tower.tool_change() at the first layer for the initial extruder + // to extrude the wipe tower brim, + if ((first_layer && extruder_id == m_tool_ordering.all_extruders().back()) || + // or when an extruder shall be switched. + extruder_id != current_extruder_id) { tool_changes.emplace_back(wipe_tower.tool_change(extruder_id, extruder_id == layer_tools.extruders.back(), WipeTower::PURPOSE_EXTRUDE)); current_extruder_id = extruder_id; } @@ -1065,7 +1075,11 @@ void Print::_make_wipe_tower() std::string Print::output_filename() { this->placeholder_parser.update_timestamp(); - return this->placeholder_parser.process(this->config.output_filename_format.value, 0); + try { + return this->placeholder_parser.process(this->config.output_filename_format.value, 0); + } catch (std::runtime_error &err) { + throw std::runtime_error(std::string("Failed processing of the output_filename_format template.\n") + err.what()); + } } std::string Print::output_filepath(const std::string &path) @@ -1079,13 +1093,13 @@ std::string Print::output_filepath(const std::string &path) if (! input_file.empty()) break; } - return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).string(); + return (boost::filesystem::path(input_file).parent_path() / this->output_filename()).make_preferred().string(); } // if we were supplied a directory, use it and append our automatically generated filename boost::filesystem::path p(path); if (boost::filesystem::is_directory(p)) - return (p / this->output_filename()).string(); + return (p / this->output_filename()).make_preferred().string(); // if we were supplied a file which is not a directory, use it return path; diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index c8a87022e..ed54d9b6c 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -1,7 +1,10 @@ #include "PrintConfig.hpp" + +#include <set> #include <boost/algorithm/string/replace.hpp> #include <boost/lexical_cast.hpp> #include <boost/thread.hpp> + #include <float.h> namespace Slic3r { @@ -14,43 +17,46 @@ PrintConfigDef::PrintConfigDef() def = this->add("avoid_crossing_perimeters", coBool); def->label = "Avoid crossing perimeters"; - def->tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation."; + def->tooltip = "Optimize travel moves in order to minimize the crossing of perimeters. " + "This is mostly useful with Bowden extruders which suffer from oozing. " + "This feature slows down both the print and the G-code generation."; def->cli = "avoid-crossing-perimeters!"; def->default_value = new ConfigOptionBool(false); def = this->add("bed_shape", coPoints); def->label = "Bed shape"; - { - ConfigOptionPoints* opt = new ConfigOptionPoints(); - opt->values.push_back(Pointf(0,0)); - opt->values.push_back(Pointf(200,0)); - opt->values.push_back(Pointf(200,200)); - opt->values.push_back(Pointf(0,200)); - def->default_value = opt; - } + def->default_value = new ConfigOptionPoints { Pointf(0,0), Pointf(200,0), Pointf(200,200), Pointf(0,200) }; def = this->add("bed_temperature", coInts); def->label = "Other layers"; - def->tooltip = "Bed temperature for layers after the first one. Set this to zero to disable bed temperature control commands in the output."; + def->tooltip = "Bed temperature for layers after the first one. " + "Set this to zero to disable bed temperature control commands in the output."; def->cli = "bed-temperature=i@"; def->full_label = "Bed temperature"; def->min = 0; def->max = 300; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 0 }; def = this->add("before_layer_gcode", coString); def->label = "Before layer change G-code"; - def->tooltip = "This custom code is inserted at every layer change, right before the Z move. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; + def->tooltip = "This custom code is inserted at every layer change, right before the Z move. " + "Note that you can use placeholder variables for all Slic3r settings as well " + "as [layer_num] and [layer_z]."; def->cli = "before-layer-gcode=s"; def->multiline = true; def->full_width = true; def->height = 50; def->default_value = new ConfigOptionString(""); + def = this->add("between_objects_gcode", coString); + def->label = "Between objects G-code"; + def->tooltip = "This code is inserted between objects when using sequential printing. By default extruder and bed temperature are reset using non-wait command; however if M104, M109, M140 or M190 are detected in this custom code, Slic3r will not add temperature commands. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want."; + def->cli = "between-objects-gcode=s"; + def->multiline = true; + def->full_width = true; + def->height = 120; + def->default_value = new ConfigOptionString(""); + def = this->add("bottom_solid_layers", coInt); def->label = "Bottom"; def->category = "Layers and Perimeters"; @@ -62,7 +68,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("bridge_acceleration", coFloat); def->label = "Bridge"; - def->tooltip = "This is the acceleration your printer will use for bridges. Set zero to disable acceleration control for bridges."; + def->tooltip = "This is the acceleration your printer will use for bridges. " + "Set zero to disable acceleration control for bridges."; def->sidetext = "mm/s²"; def->cli = "bridge-acceleration=f"; def->min = 0; @@ -71,7 +78,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("bridge_angle", coFloat); def->label = "Bridging angle"; def->category = "Infill"; - def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated automatically. Otherwise the provided angle will be used for all bridges. Use 180° for zero angle."; + def->tooltip = "Bridging angle override. If left to zero, the bridging angle will be calculated " + "automatically. Otherwise the provided angle will be used for all bridges. " + "Use 180° for zero angle."; def->sidetext = "°"; def->cli = "bridge-angle=f"; def->min = 0; @@ -84,16 +93,15 @@ PrintConfigDef::PrintConfigDef() def->cli = "bridge-fan-speed=i@"; def->min = 0; def->max = 100; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(100); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 100 }; def = this->add("bridge_flow_ratio", coFloat); def->label = "Bridge flow ratio"; def->category = "Advanced"; - def->tooltip = "This factor affects the amount of plastic for bridging. You can decrease it slightly to pull the extrudates and prevent sagging, although default settings are usually good and you should experiment with cooling (use a fan) before tweaking this."; + def->tooltip = "This factor affects the amount of plastic for bridging. " + "You can decrease it slightly to pull the extrudates and prevent sagging, " + "although default settings are usually good and you should experiment " + "with cooling (use a fan) before tweaking this."; def->cli = "bridge-flow-ratio=f"; def->min = 0; def->default_value = new ConfigOptionFloat(1); @@ -118,7 +126,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("clip_multipart_objects", coBool); def->label = "Clip multi-part objects"; - def->tooltip = "When printing multi-material objects, this settings will make slic3r to clip the overlapping object parts one by the other (2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)."; + def->tooltip = "When printing multi-material objects, this settings will make slic3r " + "to clip the overlapping object parts one by the other " + "(2nd part will be clipped by the 1st, 3rd part will be clipped by the 1st and 2nd etc)."; def->cli = "clip-multipart-objects!"; def->default_value = new ConfigOptionBool(false); @@ -128,23 +138,25 @@ PrintConfigDef::PrintConfigDef() def = this->add("complete_objects", coBool); def->label = "Complete individual objects"; - def->tooltip = "When printing multiple objects or copies, this feature will complete each object before moving onto next one (and starting it from its bottom layer). This feature is useful to avoid the risk of ruined prints. Slic3r should warn and prevent you from extruder collisions, but beware."; + def->tooltip = "When printing multiple objects or copies, this feature will complete " + "each object before moving onto next one (and starting it from its bottom layer). " + "This feature is useful to avoid the risk of ruined prints. " + "Slic3r should warn and prevent you from extruder collisions, but beware."; def->cli = "complete-objects!"; def->default_value = new ConfigOptionBool(false); def = this->add("cooling", coBools); def->label = "Enable auto cooling"; - def->tooltip = "This flag enables the automatic cooling logic that adjusts print speed and fan speed according to layer printing time."; + def->tooltip = "This flag enables the automatic cooling logic that adjusts print speed " + "and fan speed according to layer printing time."; def->cli = "cooling!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(true); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { true }; def = this->add("default_acceleration", coFloat); def->label = "Default"; - def->tooltip = "This is the acceleration your printer will be reset to after the role-specific acceleration values are used (perimeter/infill). Set zero to prevent resetting acceleration at all."; + def->tooltip = "This is the acceleration your printer will be reset to after " + "the role-specific acceleration values are used (perimeter/infill). " + "Set zero to prevent resetting acceleration at all."; def->sidetext = "mm/s²"; def->cli = "default-acceleration=f"; def->min = 0; @@ -152,21 +164,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("disable_fan_first_layers", coInts); def->label = "Disable fan for the first"; - def->tooltip = "You can set this to a positive value to disable fan at all during the first layers, so that it does not make adhesion worse."; + def->tooltip = "You can set this to a positive value to disable fan at all " + "during the first layers, so that it does not make adhesion worse."; def->sidetext = "layers"; def->cli = "disable-fan-first-layers=i@"; def->min = 0; def->max = 1000; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(3); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 3 }; def = this->add("dont_support_bridges", coBool); def->label = "Don't support bridges"; def->category = "Support material"; - def->tooltip = "Experimental option for preventing support material from being generated under bridged areas."; + def->tooltip = "Experimental option for preventing support material from being generated " + "under bridged areas."; def->cli = "dont-support-bridges!"; def->default_value = new ConfigOptionBool(true); @@ -182,7 +192,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("elefant_foot_compensation", coFloat); def->label = "Elefant foot compensation"; def->category = "Advanced"; - def->tooltip = "The first layer will be shrunk in the XY plane by the configured value to compensate for the 1st layer squish aka an Elefant Foot effect."; + def->tooltip = "The first layer will be shrunk in the XY plane by the configured value " + "to compensate for the 1st layer squish aka an Elefant Foot effect."; def->sidetext = "mm"; def->cli = "elefant-foot-compensation=f"; def->min = 0; @@ -190,7 +201,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("end_gcode", coString); def->label = "End G-code"; - def->tooltip = "This end procedure is inserted at the end of the output file. Note that you can use placeholder variables for all Slic3r settings."; + def->tooltip = "This end procedure is inserted at the end of the output file. " + "Note that you can use placeholder variables for all Slic3r settings."; def->cli = "end-gcode=s"; def->multiline = true; def->full_width = true; @@ -199,28 +211,28 @@ PrintConfigDef::PrintConfigDef() def = this->add("end_filament_gcode", coStrings); def->label = "End G-code"; - def->tooltip = "This end procedure is inserted at the end of the output file, before the printer end gcode. Note that you can use placeholder variables for all Slic3r settings. If you have multiple extruders, the gcode is processed in extruder order."; + def->tooltip = "This end procedure is inserted at the end of the output file, before the printer end gcode. " + "Note that you can use placeholder variables for all Slic3r settings. " + "If you have multiple extruders, the gcode is processed in extruder order."; def->cli = "end-filament-gcode=s@"; def->multiline = true; def->full_width = true; def->height = 120; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("; Filament-specific end gcode \n;END gcode for filament\n"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "; Filament-specific end gcode \n;END gcode for filament\n" }; def = this->add("ensure_vertical_shell_thickness", coBool); def->label = "Ensure vertical shell thickness"; def->category = "Layers and Perimeters"; - def->tooltip = "Add solid infill near sloping surfaces to guarantee the vertical shell thickness (top+bottom solid layers)."; + def->tooltip = "Add solid infill near sloping surfaces to guarantee the vertical shell thickness " + "(top+bottom solid layers)."; def->cli = "ensure-vertical-shell-thickness!"; def->default_value = new ConfigOptionBool(false); def = this->add("external_fill_pattern", coEnum); def->label = "Top/bottom fill pattern"; 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->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<InfillPattern>::get_enum_values(); def->enum_values.push_back("rectilinear"); @@ -233,13 +245,16 @@ PrintConfigDef::PrintConfigDef() def->enum_labels.push_back("Hilbert Curve"); def->enum_labels.push_back("Archimedean Chords"); def->enum_labels.push_back("Octagram Spiral"); + // solid_fill_pattern is an obsolete equivalent to external_fill_pattern. def->aliases.push_back("solid_fill_pattern"); def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear); def = this->add("external_perimeter_extrusion_width", coFloatOrPercent); def->label = "External perimeters"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for external perimeters. If left zero, an automatic value will be used that maximizes accuracy of the external visible surfaces. If expressed as percentage (for example 200%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for external perimeters. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "If expressed as percentage (for example 200%), it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "external-perimeter-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -247,7 +262,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("external_perimeter_speed", coFloatOrPercent); def->label = "External perimeters"; def->category = "Speed"; - def->tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; + def->tooltip = "This separate setting will affect the speed of external perimeters (the visible ones). " + "If expressed as percentage (for example: 80%) it will be calculated " + "on the perimeters speed setting above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "external-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; @@ -257,14 +274,17 @@ PrintConfigDef::PrintConfigDef() def = this->add("external_perimeters_first", coBool); def->label = "External perimeters first"; def->category = "Layers and Perimeters"; - def->tooltip = "Print contour perimeters from the outermost one to the innermost one instead of the default inverse order."; + def->tooltip = "Print contour perimeters from the outermost one to the innermost one " + "instead of the default inverse order."; def->cli = "external-perimeters-first!"; def->default_value = new ConfigOptionBool(false); def = this->add("extra_perimeters", coBool); def->label = "Extra perimeters if needed"; def->category = "Layers and Perimeters"; - def->tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls. Slic3r keeps adding perimeters, until more than 70% of the loop immediately above is supported."; + def->tooltip = "Add more perimeters when needed for avoiding gaps in sloping walls. " + "Slic3r keeps adding perimeters, until more than 70% of the loop immediately above " + "is supported."; def->cli = "extra-perimeters!"; def->default_value = new ConfigOptionBool(true); @@ -272,7 +292,8 @@ PrintConfigDef::PrintConfigDef() def->gui_type = "i_enum_open"; def->label = "Extruder"; def->category = "Extruders"; - def->tooltip = "The extruder to use (unless more specific extruder settings are specified). This value overrides perimeter and infill extruders, but not the support extruders."; + def->tooltip = "The extruder to use (unless more specific extruder settings are specified). " + "This value overrides perimeter and infill extruders, but not the support extruders."; def->cli = "extruder=i"; def->min = 0; // 0 = inherit defaults def->enum_labels.push_back("default"); // override label for item 0 @@ -283,7 +304,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("extruder_clearance_height", coFloat); def->label = "Height"; - def->tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. In other words, this is the height of the clearance cylinder around your extruder, and it represents the maximum depth the extruder can peek before colliding with other printed objects."; + def->tooltip = "Set this to the vertical distance between your nozzle tip and (usually) the X carriage rods. " + "In other words, this is the height of the clearance cylinder around your extruder, " + "and it represents the maximum depth the extruder can peek before colliding with " + "other printed objects."; def->sidetext = "mm"; def->cli = "extruder-clearance-height=f"; def->min = 0; @@ -291,7 +315,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("extruder_clearance_radius", coFloat); def->label = "Radius"; - def->tooltip = "Set this to the clearance radius around your extruder. If the extruder is not centered, choose the largest value for safety. This setting is used to check for collisions and to display the graphical preview in the plater."; + def->tooltip = "Set this to the clearance radius around your extruder. " + "If the extruder is not centered, choose the largest value for safety. " + "This setting is used to check for collisions and to display the graphical preview " + "in the plater."; def->sidetext = "mm"; def->cli = "extruder-clearance-radius=f"; def->min = 0; @@ -302,83 +329,70 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "This is only used in the Slic3r interface as a visual help."; def->cli = "extruder-color=s@"; def->gui_type = "color"; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - // Empty string means no color assigned yet. -// opt->values.push_back("#FFFFFF"); - opt->values.push_back(""); - def->default_value = opt; - } + // Empty string means no color assigned yet. + def->default_value = new ConfigOptionStrings { "" }; def = this->add("extruder_offset", coPoints); def->label = "Extruder offset"; - def->tooltip = "If your firmware doesn't handle the extruder displacement you need the G-code to take it into account. This option lets you specify the displacement of each extruder with respect to the first one. It expects positive coordinates (they will be subtracted from the XY coordinate)."; + def->tooltip = "If your firmware doesn't handle the extruder displacement you need the G-code " + "to take it into account. This option lets you specify the displacement of each extruder " + "with respect to the first one. It expects positive coordinates (they will be subtracted " + "from the XY coordinate)."; def->sidetext = "mm"; def->cli = "extruder-offset=s@"; - { - ConfigOptionPoints* opt = new ConfigOptionPoints(); - opt->values.push_back(Pointf(0,0)); - def->default_value = opt; - } + def->default_value = new ConfigOptionPoints { Pointf(0,0) }; def = this->add("extrusion_axis", coString); def->label = "Extrusion axis"; - def->tooltip = "Use this option to set the axis letter associated to your printer's extruder (usually E but some printers use A)."; + def->tooltip = "Use this option to set the axis letter associated to your printer's extruder " + "(usually E but some printers use A)."; def->cli = "extrusion-axis=s"; def->default_value = new ConfigOptionString("E"); def = this->add("extrusion_multiplier", coFloats); def->label = "Extrusion multiplier"; - def->tooltip = "This factor changes the amount of flow proportionally. You may need to tweak this setting to get nice surface finish and correct single wall widths. Usual values are between 0.9 and 1.1. If you think you need to change this more, check filament diameter and your firmware E steps."; + def->tooltip = "This factor changes the amount of flow proportionally. You may need to tweak " + "this setting to get nice surface finish and correct single wall widths. " + "Usual values are between 0.9 and 1.1. If you think you need to change this more, " + "check filament diameter and your firmware E steps."; def->cli = "extrusion-multiplier=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(1); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 1. }; def = this->add("extrusion_width", coFloatOrPercent); def->label = "Default extrusion width"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width. If left to zero, Slic3r calculates a width automatically. If expressed as percentage (for example: 230%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to allow a manual extrusion width. " + "If left to zero, Slic3r derives extrusion widths from the nozzle diameter " + "(see the tooltips for perimeter extrusion width, infill extrusion width etc). " + "If expressed as percentage (for example: 230%), it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for auto)"; def->cli = "extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); def = this->add("fan_always_on", coBools); def->label = "Keep fan always on"; - def->tooltip = "If this is enabled, fan will never be disabled and will be kept running at least at its minimum speed. Useful for PLA, harmful for ABS."; + def->tooltip = "If this is enabled, fan will never be disabled and will be kept running at least " + "at its minimum speed. Useful for PLA, harmful for ABS."; def->cli = "fan-always-on!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("fan_below_layer_time", coInts); def->label = "Enable fan if layer print time is below"; - def->tooltip = "If layer print time is estimated below this number of seconds, fan will be enabled and its speed will be calculated by interpolating the minimum and maximum speeds."; + def->tooltip = "If layer print time is estimated below this number of seconds, fan will be enabled " + "and its speed will be calculated by interpolating the minimum and maximum speeds."; def->sidetext = "approximate seconds"; def->cli = "fan-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(60); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 60 }; def = this->add("filament_colour", coStrings); def->label = "Color"; def->tooltip = "This is only used in the Slic3r interface as a visual help."; def->cli = "filament-color=s@"; def->gui_type = "color"; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("#FFFFFF"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "#29b2b2" }; def = this->add("filament_notes", coStrings); def->label = "Filament notes"; @@ -387,51 +401,43 @@ PrintConfigDef::PrintConfigDef() def->multiline = true; def->full_width = true; def->height = 130; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back(""); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "" }; def = this->add("filament_max_volumetric_speed", coFloats); def->label = "Max volumetric speed"; - def->tooltip = "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric speed of a print to the minimum of print and filament volumetric speed. Set to zero for no limit."; + def->tooltip = "Maximum volumetric speed allowed for this filament. Limits the maximum volumetric " + "speed of a print to the minimum of print and filament volumetric speed. " + "Set to zero for no limit."; def->sidetext = "mm³/s"; def->cli = "filament-max-volumetric-speed=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.f); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_diameter", coFloats); def->label = "Diameter"; - def->tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average."; + def->tooltip = "Enter your filament diameter here. Good precision is required, so use a caliper " + "and do multiple measurements along the filament, then compute the average."; def->sidetext = "mm"; def->cli = "filament-diameter=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(3); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 3. }; def = this->add("filament_density", coFloats); def->label = "Density"; - def->tooltip = "Enter your filament density here. This is only for statistical information. A decent way is to weigh a known length of filament and compute the ratio of the length to volume. Better is to calculate the volume directly through displacement."; + def->tooltip = "Enter your filament density here. This is only for statistical information. " + "A decent way is to weigh a known length of filament and compute the ratio " + "of the length to volume. Better is to calculate the volume directly through displacement."; def->sidetext = "g/cm^3"; def->cli = "filament-density=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("filament_type", coStrings); def->label = "Filament type"; - def->tooltip = "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables."; + def->tooltip = "If you want to process the output G-code through custom scripts, just list their " + "absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed " + "the absolute path to the G-code file as the first argument, and they can access " + "the Slic3r config settings by reading environment variables."; def->cli = "filament_type=s@"; def->gui_type = "f_enum_open"; def->gui_flags = "show_value"; @@ -444,21 +450,13 @@ PrintConfigDef::PrintConfigDef() def->enum_values.push_back("EDGE"); def->enum_values.push_back("NGEN"); def->enum_values.push_back("PVA"); - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("PLA"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "PLA" }; def = this->add("filament_soluble", coBools); def->label = "Soluble material"; def->tooltip = "Soluble material is most likely used for a soluble support."; def->cli = "filament-soluble!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("filament_cost", coFloats); def->label = "Cost"; @@ -466,19 +464,17 @@ PrintConfigDef::PrintConfigDef() def->sidetext = "money/kg"; def->cli = "filament-cost=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } - - def = this->add("filament_settings_id", coString); - def->default_value = new ConfigOptionString(""); + def->default_value = new ConfigOptionFloats { 0. }; + def = this->add("filament_settings_id", coStrings); + def->default_value = new ConfigOptionStrings { "" }; + def = this->add("fill_angle", coFloat); def->label = "Fill angle"; def->category = "Infill"; - def->tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. Bridges will be infilled using the best direction Slic3r can detect, so this setting does not affect them."; + def->tooltip = "Default base angle for infill orientation. Cross-hatching will be applied to this. " + "Bridges will be infilled using the best direction Slic3r can detect, so this setting " + "does not affect them."; def->sidetext = "°"; def->cli = "fill-angle=f"; def->min = 0; @@ -559,7 +555,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_acceleration", coFloat); def->label = "First layer"; - def->tooltip = "This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer."; + def->tooltip = "This is the acceleration your printer will use for first layer. Set zero " + "to disable acceleration control for first layer."; def->sidetext = "mm/s²"; def->cli = "first-layer-acceleration=f"; def->min = 0; @@ -567,20 +564,20 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_bed_temperature", coInts); def->label = "First layer"; - def->tooltip = "Heated build plate temperature for the first layer. Set this to zero to disable bed temperature control commands in the output."; + def->tooltip = "Heated build plate temperature for the first layer. Set this to zero to disable " + "bed temperature control commands in the output."; def->cli = "first-layer-bed-temperature=i@"; def->max = 0; def->max = 300; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 0 }; def = this->add("first_layer_extrusion_width", coFloatOrPercent); def->label = "First layer"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. You can use this to force fatter extrudates for better adhesion. If expressed as percentage (for example 120%) it will be computed over first layer height. If set to zero, it will use the Default Extrusion Width."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for first layer. " + "You can use this to force fatter extrudates for better adhesion. If expressed " + "as percentage (for example 120%) it will be computed over first layer height. " + "If set to zero, it will use the default extrusion width."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "first-layer-extrusion-width=s"; def->ratio_over = "first_layer_height"; @@ -589,7 +586,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_height", coFloatOrPercent); def->label = "First layer height"; def->category = "Layers and Perimeters"; - def->tooltip = "When printing with very low layer heights, you might still want to print a thicker bottom layer to improve adhesion and tolerance for non perfect build plates. This can be expressed as an absolute value or as a percentage (for example: 150%) over the default layer height."; + def->tooltip = "When printing with very low layer heights, you might still want to print a thicker " + "bottom layer to improve adhesion and tolerance for non perfect build plates. " + "This can be expressed as an absolute value or as a percentage (for example: 150%) " + "over the default layer height."; def->sidetext = "mm or %"; def->cli = "first-layer-height=s"; def->ratio_over = "layer_height"; @@ -597,7 +597,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_speed", coFloatOrPercent); def->label = "First layer speed"; - def->tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves of the first layer, regardless of their type. If expressed as a percentage (for example: 40%) it will scale the default speeds."; + def->tooltip = "If expressed as absolute value in mm/s, this speed will be applied to all the print moves " + "of the first layer, regardless of their type. If expressed as a percentage " + "(for example: 40%) it will scale the default speeds."; def->sidetext = "mm/s or %"; def->cli = "first-layer-speed=s"; def->min = 0; @@ -605,20 +607,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("first_layer_temperature", coInts); def->label = "First layer"; - def->tooltip = "Extruder temperature for first layer. If you want to control temperature manually during print, set this to zero to disable temperature control commands in the output file."; + def->tooltip = "Extruder temperature for first layer. If you want to control temperature manually " + "during print, set this to zero to disable temperature control commands in the output file."; def->cli = "first-layer-temperature=i@"; def->min = 0; def->max = 500; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(200); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 200 }; def = this->add("gap_fill_speed", coFloat); def->label = "Gap fill"; def->category = "Speed"; - def->tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; + def->tooltip = "Speed for filling small gaps using short zigzag moves. Keep this reasonably low " + "to avoid too much shaking and resonance issues. Set zero to disable gaps filling."; def->sidetext = "mm/s"; def->cli = "gap-fill-speed=f"; def->min = 0; @@ -626,13 +626,17 @@ PrintConfigDef::PrintConfigDef() def = this->add("gcode_comments", coBool); def->label = "Verbose G-code"; - def->tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. If you print from SD card, the additional weight of the file could make your firmware slow down."; + def->tooltip = "Enable this to get a commented G-code file, with each line explained by a descriptive text. " + "If you print from SD card, the additional weight of the file could make your firmware " + "slow down."; def->cli = "gcode-comments!"; def->default_value = new ConfigOptionBool(0); def = this->add("gcode_flavor", coEnum); 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->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<GCodeFlavor>::get_enum_values(); def->enum_values.push_back("reprap"); @@ -657,7 +661,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_acceleration", coFloat); def->label = "Infill"; - def->tooltip = "This is the acceleration your printer will use for infill. Set zero to disable acceleration control for infill."; + def->tooltip = "This is the acceleration your printer will use for infill. Set zero to disable " + "acceleration control for infill."; def->sidetext = "mm/s²"; def->cli = "infill-acceleration=f"; def->min = 0; @@ -666,7 +671,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_every_layers", coInt); def->label = "Combine infill every"; def->category = "Infill"; - def->tooltip = "This feature allows to combine infill and speed up your print by extruding thicker infill layers while preserving thin perimeters, thus accuracy."; + def->tooltip = "This feature allows to combine infill and speed up your print by extruding thicker " + "infill layers while preserving thin perimeters, thus accuracy."; def->sidetext = "layers"; def->cli = "infill-every-layers=i"; def->full_label = "Combine infill every n layers"; @@ -684,7 +690,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_extrusion_width", coFloatOrPercent); def->label = "Infill"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. You may want to use fatter extrudates to speed up the infill and make your parts stronger. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "You may want to use fatter extrudates to speed up the infill and make your parts stronger. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -698,14 +707,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("infill_only_where_needed", coBool); def->label = "Only infill where needed"; def->category = "Infill"; - def->tooltip = "This option will limit infill to the areas actually needed for supporting ceilings (it will act as internal support material). If enabled, slows down the G-code generation due to the multiple checks involved."; + def->tooltip = "This option will limit infill to the areas actually needed for supporting ceilings " + "(it will act as internal support material). If enabled, slows down the G-code generation " + "due to the multiple checks involved."; def->cli = "infill-only-where-needed!"; def->default_value = new ConfigOptionBool(false); def = this->add("infill_overlap", coFloatOrPercent); def->label = "Infill/perimeters overlap"; def->category = "Advanced"; - def->tooltip = "This setting applies an additional overlap between infill and perimeters for better bonding. Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed as percentage (example: 15%) it is calculated over perimeter extrusion width."; + def->tooltip = "This setting applies an additional overlap between infill and perimeters for better bonding. " + "Theoretically this shouldn't be needed, but backlash might cause gaps. If expressed " + "as percentage (example: 15%) it is calculated over perimeter extrusion width."; def->sidetext = "mm or %"; def->cli = "infill-overlap=s"; def->ratio_over = "perimeter_extrusion_width"; @@ -724,14 +737,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("interface_shells", coBool); def->label = "Interface shells"; - def->tooltip = "Force the generation of solid shells between adjacent materials/volumes. Useful for multi-extruder prints with translucent materials or manual soluble support material."; + def->tooltip = "Force the generation of solid shells between adjacent materials/volumes. " + "Useful for multi-extruder prints with translucent materials or manual soluble " + "support material."; def->cli = "interface-shells!"; def->category = "Layers and Perimeters"; def->default_value = new ConfigOptionBool(false); def = this->add("layer_gcode", coString); def->label = "After layer change G-code"; - def->tooltip = "This custom code is inserted at every layer change, right after the Z move and before the extruder moves to the first layer point. Note that you can use placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; + def->tooltip = "This custom code is inserted at every layer change, right after the Z move " + "and before the extruder moves to the first layer point. Note that you can use " + "placeholder variables for all Slic3r settings as well as [layer_num] and [layer_z]."; def->cli = "after-layer-gcode|layer-gcode=s"; def->multiline = true; def->full_width = true; @@ -741,7 +758,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("layer_height", coFloat); def->label = "Layer height"; def->category = "Layers and Perimeters"; - def->tooltip = "This setting controls the height (and thus the total number) of the slices/layers. Thinner layers give better accuracy but take more time to print."; + def->tooltip = "This setting controls the height (and thus the total number) of the slices/layers. " + "Thinner layers give better accuracy but take more time to print."; def->sidetext = "mm"; def->cli = "layer-height=f"; def->min = 0; @@ -754,27 +772,24 @@ PrintConfigDef::PrintConfigDef() def->cli = "max-fan-speed=i@"; def->min = 0; def->max = 100; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(100); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 100 }; def = this->add("max_layer_height", coFloats); def->label = "Max"; - def->tooltip = "This is the highest printable layer height for this extruder, used to cap the variable layer height and support layer height. Maximum recommended layer height is 75% of the extrusion width to achieve reasonable inter-layer adhesion. If set to 0, layer height is limited to 75% of the nozzle diameter."; + def->tooltip = "This is the highest printable layer height for this extruder, used to cap " + "the variable layer height and support layer height. Maximum recommended layer height " + "is 75% of the extrusion width to achieve reasonable inter-layer adhesion. " + "If set to 0, layer height is limited to 75% of the nozzle diameter."; def->sidetext = "mm"; def->cli = "max-layer-height=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("max_print_speed", coFloat); def->label = "Max print speed"; - def->tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed in order to keep constant extruder pressure. This experimental setting is used to set the highest print speed you want to allow."; + def->tooltip = "When setting other speed settings to 0 Slic3r will autocalculate the optimal speed " + "in order to keep constant extruder pressure. This experimental setting is used " + "to set the highest print speed you want to allow."; def->sidetext = "mm/s"; def->cli = "max-print-speed=f"; def->min = 1; @@ -782,7 +797,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_speed", coFloat); def->label = "Max volumetric speed"; - def->tooltip = "This experimental setting is used to set the maximum volumetric speed your extruder supports."; + def->tooltip = "This experimental setting is used to set the maximum volumetric speed your " + "extruder supports."; def->sidetext = "mm³/s"; def->cli = "max-volumetric-speed=f"; def->min = 0; @@ -790,8 +806,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_extrusion_rate_slope_positive", coFloat); def->label = "Max volumetric slope positive"; - def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; + def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. " + "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " + "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " + "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; def->sidetext = "mm³/s²"; def->cli = "max-volumetric-extrusion-rate-slope-positive=f"; def->min = 0; @@ -799,8 +817,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("max_volumetric_extrusion_rate_slope_negative", coFloat); def->label = "Max volumetric slope negative"; - def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " - "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; + def->tooltip = "This experimental setting is used to limit the speed of change in extrusion rate. " + "A value of 1.8 mm³/s² ensures, that a change from the extrusion rate " + "of 1.8 mm³/s (0.45mm extrusion width, 0.2mm extrusion height, feedrate 20 mm/s) " + "to 5.4 mm³/s (feedrate 60 mm/s) will take at least 2 seconds."; def->sidetext = "mm³/s²"; def->cli = "max-volumetric-extrusion-rate-slope-negative=f"; def->min = 0; @@ -813,23 +833,16 @@ PrintConfigDef::PrintConfigDef() def->cli = "min-fan-speed=i@"; def->min = 0; def->max = 100; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(35); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 35 }; def = this->add("min_layer_height", coFloats); def->label = "Min"; - def->tooltip = "This is the lowest printable layer height for this extruder and limits the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm."; + def->tooltip = "This is the lowest printable layer height for this extruder and limits " + "the resolution for variable layer height. Typical values are between 0.05 mm and 0.1 mm."; def->sidetext = "mm"; def->cli = "min-layer-height=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.07); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0.07 }; def = this->add("min_print_speed", coFloats); def->label = "Min print speed"; @@ -837,15 +850,13 @@ PrintConfigDef::PrintConfigDef() def->sidetext = "mm/s"; def->cli = "min-print-speed=f@"; def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(10.); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 10. }; def = this->add("min_skirt_length", coFloat); def->label = "Minimum extrusion length"; - def->tooltip = "Generate no less than the number of skirt loops required to consume the specified amount of filament on the bottom layer. For multi-extruder machines, this minimum applies to each extruder."; + def->tooltip = "Generate no less than the number of skirt loops required to consume " + "the specified amount of filament on the bottom layer. For multi-extruder machines, " + "this minimum applies to each extruder."; def->sidetext = "mm"; def->cli = "min-skirt-length=f"; def->min = 0; @@ -853,7 +864,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("notes", coString); def->label = "Configuration notes"; - def->tooltip = "You can put here your personal notes. This text will be added to the G-code header comments."; + def->tooltip = "You can put here your personal notes. This text will be added to the G-code " + "header comments."; def->cli = "notes=s"; def->multiline = true; def->full_width = true; @@ -865,39 +877,43 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "This is the diameter of your extruder nozzle (for example: 0.5, 0.35 etc.)"; def->sidetext = "mm"; def->cli = "nozzle-diameter=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.5); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0.5 }; def = this->add("octoprint_apikey", coString); def->label = "API Key"; - def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the API Key required for authentication."; + def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain " + "the API Key required for authentication."; def->cli = "octoprint-apikey=s"; def->default_value = new ConfigOptionString(""); def = this->add("octoprint_host", coString); def->label = "Host or IP"; - def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain the hostname or IP address of the OctoPrint instance."; + def->tooltip = "Slic3r can upload G-code files to OctoPrint. This field should contain " + "the hostname or IP address of the OctoPrint instance."; def->cli = "octoprint-host=s"; def->default_value = new ConfigOptionString(""); def = this->add("only_retract_when_crossing_perimeters", coBool); def->label = "Only retract when crossing perimeters"; - def->tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters (and thus any ooze will be probably invisible)."; + def->tooltip = "Disables retraction when the travel path does not exceed the upper layer's perimeters " + "(and thus any ooze will be probably invisible)."; def->cli = "only-retract-when-crossing-perimeters!"; def->default_value = new ConfigOptionBool(true); def = this->add("ooze_prevention", coBool); def->label = "Enable"; - def->tooltip = "This option will drop the temperature of the inactive extruders to prevent oozing. It will enable a tall skirt automatically and move extruders outside such skirt when changing temperatures."; + def->tooltip = "This option will drop the temperature of the inactive extruders to prevent oozing. " + "It will enable a tall skirt automatically and move extruders outside such " + "skirt when changing temperatures."; def->cli = "ooze-prevention!"; def->default_value = new ConfigOptionBool(false); def = this->add("output_filename_format", coString); def->label = "Output filename format"; - def->tooltip = "You can use all configuration options as variables inside this template. For example: [layer_height], [fill_density] etc. You can also use [timestamp], [year], [month], [day], [hour], [minute], [second], [version], [input_filename], [input_filename_base]."; + def->tooltip = "You can use all configuration options as variables inside this template. " + "For example: [layer_height], [fill_density] etc. You can also use [timestamp], " + "[year], [month], [day], [hour], [minute], [second], [version], [input_filename], " + "[input_filename_base]."; def->cli = "output-filename-format=s"; def->full_width = true; def->default_value = new ConfigOptionString("[input_filename_base].gcode"); @@ -905,13 +921,16 @@ PrintConfigDef::PrintConfigDef() def = this->add("overhangs", coBool); def->label = "Detect bridging perimeters"; def->category = "Layers and Perimeters"; - def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), to apply bridge speed to them and enable fan."; + def->tooltip = "Experimental option to adjust flow for overhangs (bridge flow will be used), " + "to apply bridge speed to them and enable fan."; def->cli = "overhangs!"; def->default_value = new ConfigOptionBool(true); def = this->add("perimeter_acceleration", coFloat); def->label = "Perimeters"; - def->tooltip = "This is the acceleration your printer will use for perimeters. A high value like 9000 usually gives good results if your hardware is up to the job. Set zero to disable acceleration control for perimeters."; + def->tooltip = "This is the acceleration your printer will use for perimeters. " + "A high value like 9000 usually gives good results if your hardware is up to the job. " + "Set zero to disable acceleration control for perimeters."; def->sidetext = "mm/s²"; def->cli = "perimeter-acceleration=f"; def->default_value = new ConfigOptionFloat(0); @@ -928,7 +947,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("perimeter_extrusion_width", coFloatOrPercent); def->label = "Perimeters"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. You may want to use thinner extrudates to get more accurate surfaces. If expressed as percentage (for example 200%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for perimeters. " + "You may want to use thinner extrudates to get more accurate surfaces. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "If expressed as percentage (for example 200%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "perimeter-extrusion-width=s"; def->aliases.push_back("perimeters_extrusion_width"); @@ -947,7 +969,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("perimeters", coInt); def->label = "Perimeters"; def->category = "Layers and Perimeters"; - def->tooltip = "This option sets the number of perimeters to generate for each layer. Note that Slic3r may increase this number automatically when it detects sloping surfaces which benefit from a higher number of perimeters if the Extra Perimeters option is enabled."; + def->tooltip = "This option sets the number of perimeters to generate for each layer. " + "Note that Slic3r may increase this number automatically when it detects " + "sloping surfaces which benefit from a higher number of perimeters " + "if the Extra Perimeters option is enabled."; def->sidetext = "(minimum)"; def->cli = "perimeters=i"; def->aliases.push_back("perimeter_offsets"); @@ -956,7 +981,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("post_process", coStrings); def->label = "Post-processing scripts"; - def->tooltip = "If you want to process the output G-code through custom scripts, just list their absolute paths here. Separate multiple scripts with a semicolon. Scripts will be passed the absolute path to the G-code file as the first argument, and they can access the Slic3r config settings by reading environment variables."; + def->tooltip = "If you want to process the output G-code through custom scripts, " + "just list their absolute paths here. Separate multiple scripts with a semicolon. " + "Scripts will be passed the absolute path to the G-code file as the first argument, " + "and they can access the Slic3r config settings by reading environment variables."; def->cli = "post-process=s@"; def->gui_flags = "serialized"; def->multiline = true; @@ -981,7 +1009,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("raft_layers", coInt); def->label = "Raft layers"; def->category = "Support material"; - def->tooltip = "The object will be raised by this number of layers, and support material will be generated under it."; + def->tooltip = "The object will be raised by this number of layers, and support material " + "will be generated under it."; def->sidetext = "layers"; def->cli = "raft-layers=i"; def->min = 0; @@ -989,7 +1018,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("resolution", coFloat); def->label = "Resolution"; - def->tooltip = "Minimum detail resolution, used to simplify the input file for speeding up the slicing job and reducing memory usage. High-resolution models often carry more detail than printers can render. Set to zero to disable any simplification and use full resolution from input."; + def->tooltip = "Minimum detail resolution, used to simplify the input file for speeding up " + "the slicing job and reducing memory usage. High-resolution models often carry " + "more detail than printers can render. Set to zero to disable any simplification " + "and use full resolution from input."; def->sidetext = "mm"; def->cli = "resolution=f"; def->min = 0; @@ -1000,113 +1032,84 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "Retraction is not triggered when travel moves are shorter than this length."; def->sidetext = "mm"; def->cli = "retract-before-travel=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(2); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_before_wipe", coPercents); def->label = "Retract amount before wipe"; - def->tooltip = "With bowden extruders, it may be wise to do some amount of quick retract before doing the wipe movement."; + def->tooltip = "With bowden extruders, it may be wise to do some amount of quick retract " + "before doing the wipe movement."; def->sidetext = "%"; def->cli = "retract-before-wipe=s@"; - { - ConfigOptionPercents* opt = new ConfigOptionPercents(); - opt->values.push_back(0.f); - def->default_value = opt; - } + def->default_value = new ConfigOptionPercents { 0. }; def = this->add("retract_layer_change", coBools); def->label = "Retract on layer change"; def->tooltip = "This flag enforces a retraction whenever a Z move is done."; def->cli = "retract-layer-change!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("retract_length", coFloats); def->label = "Length"; def->full_label = "Retraction Length"; - def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; + def->tooltip = "When retraction is triggered, filament is pulled back by the specified amount " + "(the length is measured on raw filament, before it enters the extruder)."; def->sidetext = "mm (zero to disable)"; def->cli = "retract-length=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(2); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 2. }; def = this->add("retract_length_toolchange", coFloats); def->label = "Length"; def->full_label = "Retraction Length (Toolchange)"; - def->tooltip = "When retraction is triggered before changing tool, filament is pulled back by the specified amount (the length is measured on raw filament, before it enters the extruder)."; + def->tooltip = "When retraction is triggered before changing tool, filament is pulled back " + "by the specified amount (the length is measured on raw filament, before it enters " + "the extruder)."; def->sidetext = "mm (zero to disable)"; def->cli = "retract-length-toolchange=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(10); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 10. }; def = this->add("retract_lift", coFloats); def->label = "Lift Z"; - def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered."; + def->tooltip = "If you set this to a positive value, Z is quickly raised every time a retraction " + "is triggered. When using multiple extruders, only the setting for the first extruder " + "will be considered."; def->sidetext = "mm"; def->cli = "retract-lift=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_above", coFloats); def->label = "Above Z"; def->full_label = "Only lift Z above"; - def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified absolute Z. You can tune this setting for skipping lift on the first layers."; + def->tooltip = "If you set this to a positive value, Z lift will only take place above the specified " + "absolute Z. You can tune this setting for skipping lift on the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-above=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_lift_below", coFloats); def->label = "Below Z"; def->full_label = "Only lift Z below"; - def->tooltip = "If you set this to a positive value, Z lift will only take place below the specified absolute Z. You can tune this setting for limiting lift to the first layers."; + def->tooltip = "If you set this to a positive value, Z lift will only take place below " + "the specified absolute Z. You can tune this setting for limiting lift " + "to the first layers."; def->sidetext = "mm"; def->cli = "retract-lift-below=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra", coFloats); def->label = "Extra length on restart"; - def->tooltip = "When the retraction is compensated after the travel move, the extruder will push this additional amount of filament. This setting is rarely needed."; + def->tooltip = "When the retraction is compensated after the travel move, the extruder will push " + "this additional amount of filament. This setting is rarely needed."; def->sidetext = "mm"; def->cli = "retract-restart-extra=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_restart_extra_toolchange", coFloats); def->label = "Extra length on restart"; - def->tooltip = "When the retraction is compensated after changing tool, the extruder will push this additional amount of filament."; + def->tooltip = "When the retraction is compensated after changing tool, the extruder will push " + "this additional amount of filament."; def->sidetext = "mm"; def->cli = "retract-restart-extra-toolchange=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("retract_speed", coFloats); def->label = "Retraction Speed"; @@ -1114,23 +1117,16 @@ PrintConfigDef::PrintConfigDef() def->tooltip = "The speed for retractions (it only applies to the extruder motor)."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(40); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 40. }; def = this->add("deretract_speed", coFloats); def->label = "Deretraction Speed"; def->full_label = "Deretraction Speed"; - def->tooltip = "The speed for loading of a filament into extruder after retraction (it only applies to the extruder motor). If left to zero, the retraction speed is used."; + def->tooltip = "The speed for loading of a filament into extruder after retraction " + "(it only applies to the extruder motor). If left to zero, the retraction speed is used."; def->sidetext = "mm/s"; def->cli = "retract-speed=f@"; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0); - def->default_value = opt; - } + def->default_value = new ConfigOptionFloats { 0. }; def = this->add("seam_position", coEnum); def->label = "Seam position"; @@ -1195,7 +1191,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirt_distance", coFloat); def->label = "Distance from object"; - def->tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt to the object(s) and get a brim for better adhesion."; + def->tooltip = "Distance between skirt and object(s). Set this to zero to attach the skirt " + "to the object(s) and get a brim for better adhesion."; def->sidetext = "mm"; def->cli = "skirt-distance=f"; def->min = 0; @@ -1203,7 +1200,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirt_height", coInt); def->label = "Skirt height"; - def->tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt as a shield against drafts."; + def->tooltip = "Height of skirt expressed in layers. Set this to a tall value to use skirt " + "as a shield against drafts."; def->sidetext = "layers"; def->cli = "skirt-height=i"; def->default_value = new ConfigOptionInt(1); @@ -1211,29 +1209,30 @@ PrintConfigDef::PrintConfigDef() def = this->add("skirts", coInt); def->label = "Loops (minimum)"; def->full_label = "Skirt Loops"; - def->tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, the number of loops might be greater than the one configured here. Set this to zero to disable skirt completely."; + def->tooltip = "Number of loops for the skirt. If the Minimum Extrusion Length option is set, " + "the number of loops might be greater than the one configured here. Set this to zero " + "to disable skirt completely."; def->cli = "skirts=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); def = this->add("slowdown_below_layer_time", coInts); def->label = "Slow down if layer print time is below"; - def->tooltip = "If layer print time is estimated below this number of seconds, print moves speed will be scaled down to extend duration to this value."; + def->tooltip = "If layer print time is estimated below this number of seconds, print moves " + "speed will be scaled down to extend duration to this value."; def->sidetext = "approximate seconds"; def->cli = "slowdown-below-layer-time=i@"; def->width = 60; def->min = 0; def->max = 1000; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(5); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 5 }; def = this->add("small_perimeter_speed", coFloatOrPercent); def->label = "Small perimeters"; def->category = "Speed"; - def->tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm (usually holes). If expressed as percentage (for example: 80%) it will be calculated on the perimeters speed setting above. Set to zero for auto."; + def->tooltip = "This separate setting will affect the speed of perimeters having radius <= 6.5mm " + "(usually holes). If expressed as percentage (for example: 80%) it will be calculated " + "on the perimeters speed setting above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "small-perimeter-speed=s"; def->ratio_over = "perimeter_speed"; @@ -1260,7 +1259,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("solid_infill_every_layers", coInt); def->label = "Solid infill every"; def->category = "Infill"; - def->tooltip = "This feature allows to force a solid layer every given number of layers. Zero to disable. You can set this to any value (for example 9999); Slic3r will automatically choose the maximum possible number of layers to combine according to nozzle diameter and layer height."; + def->tooltip = "This feature allows to force a solid layer every given number of layers. " + "Zero to disable. You can set this to any value (for example 9999); " + "Slic3r will automatically choose the maximum possible number of layers " + "to combine according to nozzle diameter and layer height."; def->sidetext = "layers"; def->cli = "solid-infill-every-layers=i"; def->min = 0; @@ -1269,7 +1271,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("solid_infill_extrusion_width", coFloatOrPercent); def->label = "Solid infill"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for solid surfaces. " + "If left zero, default extrusion width will be used if set, otherwise 1.125 x nozzle diameter will be used. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "solid-infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1277,7 +1281,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("solid_infill_speed", coFloatOrPercent); def->label = "Solid infill"; def->category = "Speed"; - def->tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). This can be expressed as a percentage (for example: 80%) over the default infill speed above. Set to zero for auto."; + def->tooltip = "Speed for printing solid regions (top/bottom/internal horizontal shells). " + "This can be expressed as a percentage (for example: 80%) over the default " + "infill speed above. Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "solid-infill-speed=s"; def->ratio_over = "infill_speed"; @@ -1295,13 +1301,18 @@ PrintConfigDef::PrintConfigDef() def = this->add("spiral_vase", coBool); def->label = "Spiral vase"; - def->tooltip = "This feature will raise Z gradually while printing a single-walled object in order to remove any visible seam. This option requires a single perimeter, no infill, no top solid layers and no support material. You can still set any number of bottom solid layers as well as skirt/brim loops. It won't work when printing more than an object."; + def->tooltip = "This feature will raise Z gradually while printing a single-walled object " + "in order to remove any visible seam. This option requires a single perimeter, " + "no infill, no top solid layers and no support material. You can still set " + "any number of bottom solid layers as well as skirt/brim loops. " + "It won't work when printing more than an object."; def->cli = "spiral-vase!"; def->default_value = new ConfigOptionBool(false); def = this->add("standby_temperature_delta", coInt); def->label = "Temperature variation"; - def->tooltip = "Temperature difference to be applied when an extruder is not active. Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; + def->tooltip = "Temperature difference to be applied when an extruder is not active. " + "Enables a full-height \"sacrificial\" skirt on which the nozzles are periodically wiped."; def->sidetext = "∆°C"; def->cli = "standby-temperature-delta=i"; def->min = -500; @@ -1310,7 +1321,13 @@ PrintConfigDef::PrintConfigDef() def = this->add("start_gcode", coString); def->label = "Start G-code"; - def->tooltip = "This start procedure is inserted at the beginning, after bed has reached the target temperature and extruder just started heating, and before extruder has finished heating. If Slic3r detects M104 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want."; + def->tooltip = "This start procedure is inserted at the beginning, after bed has reached " + "the target temperature and extruder just started heating, and before extruder " + "has finished heating. If Slic3r detects M104 or M190 in your custom codes, " + "such commands will not be prepended automatically so you're free to customize " + "the order of heating commands and other custom actions. Note that you can use " + "placeholder variables for all Slic3r settings, so you can put " + "a \"M109 S[first_layer_temperature]\" command wherever you want."; def->cli = "start-gcode=s"; def->multiline = true; def->full_width = true; @@ -1319,16 +1336,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("start_filament_gcode", coStrings); def->label = "Start G-code"; - def->tooltip = "This start procedure is inserted at the beginning, after any printer start gcode. This is used to override settings for a specific filament. If Slic3r detects M104, M109, M140 or M190 in your custom codes, such commands will not be prepended automatically so you're free to customize the order of heating commands and other custom actions. Note that you can use placeholder variables for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command wherever you want. If you have multiple extruders, the gcode is processed in extruder order."; + def->tooltip = "This start procedure is inserted at the beginning, after any printer start gcode. " + "This is used to override settings for a specific filament. If Slic3r detects " + "M104, M109, M140 or M190 in your custom codes, such commands will " + "not be prepended automatically so you're free to customize the order " + "of heating commands and other custom actions. Note that you can use placeholder variables " + "for all Slic3r settings, so you can put a \"M109 S[first_layer_temperature]\" command " + "wherever you want. If you have multiple extruders, the gcode is processed " + "in extruder order."; def->cli = "start-filament-gcode=s@"; def->multiline = true; def->full_width = true; def->height = 120; - { - ConfigOptionStrings* opt = new ConfigOptionStrings(); - opt->values.push_back("; Filament gcode\n"); - def->default_value = opt; - } + def->default_value = new ConfigOptionStrings { "; Filament gcode\n" }; def = this->add("single_extruder_multi_material", coBool); def->label = "Single Extruder Multi Material"; @@ -1346,7 +1366,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_xy_spacing", coFloatOrPercent); def->label = "XY separation between an object and its support"; def->category = "Support material"; - def->tooltip = "XY separation between an object and its support. If expressed as percentage (for example 50%), it will be calculated over external perimeter width."; + def->tooltip = "XY separation between an object and its support. If expressed as percentage " + "(for example 50%), it will be calculated over external perimeter width."; def->sidetext = "mm or %"; def->cli = "support-material-xy-spacing=s"; def->ratio_over = "external_perimeter_extrusion_width"; @@ -1375,7 +1396,9 @@ PrintConfigDef::PrintConfigDef() def->gui_type = "f_enum_open"; def->label = "Contact Z distance"; def->category = "Support material"; - def->tooltip = "The vertical distance between object and support material interface. Setting this to 0 will also prevent Slic3r from using bridge flow and speed for the first object layer."; + def->tooltip = "The vertical distance between object and support material interface. " + "Setting this to 0 will also prevent Slic3r from using bridge flow and speed " + "for the first object layer."; def->sidetext = "mm"; def->cli = "support-material-contact-distance=f"; def->min = 0; @@ -1388,7 +1411,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_enforce_layers", coInt); def->label = "Enforce support for the first"; def->category = "Support material"; - def->tooltip = "Generate support material for the specified number of layers counting from bottom, regardless of whether normal support material is enabled or not and regardless of any angle threshold. This is useful for getting more adhesion of objects having a very thin or poor footprint on the build plate."; + def->tooltip = "Generate support material for the specified number of layers counting from bottom, " + "regardless of whether normal support material is enabled or not and regardless " + "of any angle threshold. This is useful for getting more adhesion of objects " + "having a very thin or poor footprint on the build plate."; def->sidetext = "layers"; def->cli = "support-material-enforce-layers=f"; def->full_label = "Enforce support for the first n layers"; @@ -1398,7 +1424,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_extruder", coInt); def->label = "Support material/raft/skirt extruder"; def->category = "Extruders"; - def->tooltip = "The extruder to use when printing support material, raft and skirt (1+, 0 to use the current extruder to minimize tool changes)."; + def->tooltip = "The extruder to use when printing support material, raft and skirt " + "(1+, 0 to use the current extruder to minimize tool changes)."; def->cli = "support-material-extruder=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); @@ -1406,7 +1433,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_extrusion_width", coFloatOrPercent); def->label = "Support material"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for support material. " + "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "support-material-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1421,7 +1450,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_interface_extruder", coInt); def->label = "Support material/raft interface extruder"; def->category = "Extruders"; - def->tooltip = "The extruder to use when printing support material interface (1+, 0 to use the current extruder to minimize tool changes). This affects raft too."; + def->tooltip = "The extruder to use when printing support material interface " + "(1+, 0 to use the current extruder to minimize tool changes). This affects raft too."; def->cli = "support-material-interface-extruder=i"; def->min = 0; def->default_value = new ConfigOptionInt(1); @@ -1447,7 +1477,8 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_interface_speed", coFloatOrPercent); def->label = "Support material interface"; def->category = "Support material"; - def->tooltip = "Speed for printing support material interface layers. If expressed as percentage (for example 50%) it will be calculated over support material speed."; + def->tooltip = "Speed for printing support material interface layers. If expressed as percentage " + "(for example 50%) it will be calculated over support material speed."; def->sidetext = "mm/s or %"; def->cli = "support-material-interface-speed=s"; def->ratio_over = "support_material_speed"; @@ -1491,14 +1522,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_synchronize_layers", coBool); def->label = "Synchronize with object layers"; def->category = "Support material"; - def->tooltip = "Synchronize support layers with the object print layers. This is useful with multi-material printers, where the extruder switch is expensive."; + def->tooltip = "Synchronize support layers with the object print layers. This is useful " + "with multi-material printers, where the extruder switch is expensive."; def->cli = "support-material-synchronize-layers!"; def->default_value = new ConfigOptionBool(false); def = this->add("support_material_threshold", coInt); def->label = "Overhang threshold"; def->category = "Support material"; - def->tooltip = "Support material will not be generated for overhangs whose slope angle (90° = vertical) is above the given threshold. In other words, this value represent the most horizontal slope (measured from the horizontal plane) that you can print without support material. Set to zero for automatic detection (recommended)."; + def->tooltip = "Support material will not be generated for overhangs whose slope angle " + "(90° = vertical) is above the given threshold. In other words, this value " + "represent the most horizontal slope (measured from the horizontal plane) " + "that you can print without support material. Set to zero for automatic detection " + "(recommended)."; def->sidetext = "°"; def->cli = "support-material-threshold=i"; def->min = 0; @@ -1508,33 +1544,33 @@ PrintConfigDef::PrintConfigDef() def = this->add("support_material_with_sheath", coBool); def->label = "With sheath around the support"; def->category = "Support material"; - def->tooltip = "Add a sheath (a single perimeter line) around the base support. This makes the support more reliable, but also more difficult to remove."; + def->tooltip = "Add a sheath (a single perimeter line) around the base support. This makes " + "the support more reliable, but also more difficult to remove."; def->cli = "support-material-with-sheath!"; def->default_value = new ConfigOptionBool(true); def = this->add("temperature", coInts); def->label = "Other layers"; - def->tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable temperature control commands in the output."; + def->tooltip = "Extruder temperature for layers after the first one. Set this to zero to disable " + "temperature control commands in the output."; def->cli = "temperature=i@"; def->full_label = "Temperature"; def->max = 0; def->max = 500; - { - ConfigOptionInts* opt = new ConfigOptionInts(); - opt->values.push_back(200); - def->default_value = opt; - } + def->default_value = new ConfigOptionInts { 200 }; def = this->add("thin_walls", coBool); def->label = "Detect thin walls"; def->category = "Layers and Perimeters"; - def->tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need to collapse them into a single trace)."; + def->tooltip = "Detect single-width walls (parts where two extrusions don't fit and we need " + "to collapse them into a single trace)."; def->cli = "thin-walls!"; def->default_value = new ConfigOptionBool(true); def = this->add("threads", coInt); def->label = "Threads"; - def->tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number is slightly above the number of available cores/processors."; + def->tooltip = "Threads are used to parallelize long-running tasks. Optimal threads number " + "is slightly above the number of available cores/processors."; def->cli = "threads|j=i"; def->readonly = true; def->min = 1; @@ -1545,7 +1581,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("toolchange_gcode", coString); def->label = "Tool change G-code"; - def->tooltip = "This custom code is inserted right before every extruder change. Note that you can use placeholder variables for all Slic3r settings as well as [previous_extruder] and [next_extruder]."; + def->tooltip = "This custom code is inserted right before every extruder change. " + "Note that you can use placeholder variables for all Slic3r settings as well " + "as [previous_extruder] and [next_extruder]."; def->cli = "toolchange-gcode=s"; def->multiline = true; def->full_width = true; @@ -1555,7 +1593,10 @@ PrintConfigDef::PrintConfigDef() def = this->add("top_infill_extrusion_width", coFloatOrPercent); def->label = "Top solid infill"; def->category = "Extrusion Width"; - def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. If expressed as percentage (for example 90%) it will be computed over layer height."; + def->tooltip = "Set this to a non-zero value to set a manual extrusion width for infill for top surfaces. " + "You may want to use thinner extrudates to fill all narrow regions and get a smoother finish. " + "If left zero, default extrusion width will be used if set, otherwise nozzle diameter will be used. " + "If expressed as percentage (for example 90%) it will be computed over layer height."; def->sidetext = "mm or % (leave 0 for default)"; def->cli = "top-infill-extrusion-width=s"; def->default_value = new ConfigOptionFloatOrPercent(0, false); @@ -1563,7 +1604,11 @@ PrintConfigDef::PrintConfigDef() def = this->add("top_solid_infill_speed", coFloatOrPercent); def->label = "Top solid infill"; def->category = "Speed"; - def->tooltip = "Speed for printing top solid layers (it only applies to the uppermost external layers and not to their internal solid layers). You may want to slow down this to get a nicer surface finish. This can be expressed as a percentage (for example: 80%) over the solid infill speed above. Set to zero for auto."; + def->tooltip = "Speed for printing top solid layers (it only applies to the uppermost " + "external layers and not to their internal solid layers). You may want " + "to slow down this to get a nicer surface finish. This can be expressed " + "as a percentage (for example: 80%) over the solid infill speed above. " + "Set to zero for auto."; def->sidetext = "mm/s or %"; def->cli = "top-solid-infill-speed=s"; def->ratio_over = "solid_infill_speed"; @@ -1590,41 +1635,47 @@ PrintConfigDef::PrintConfigDef() def = this->add("use_firmware_retraction", coBool); def->label = "Use firmware retraction"; - def->tooltip = "This experimental setting uses G10 and G11 commands to have the firmware handle the retraction. This is only supported in recent Marlin."; + def->tooltip = "This experimental setting uses G10 and G11 commands to have the firmware " + "handle the retraction. This is only supported in recent Marlin."; def->cli = "use-firmware-retraction!"; def->default_value = new ConfigOptionBool(false); def = this->add("use_relative_e_distances", coBool); def->label = "Use relative E distances"; - def->tooltip = "If your firmware requires relative E values, check this, otherwise leave it unchecked. Most firmwares use absolute values."; + def->tooltip = "If your firmware requires relative E values, check this, " + "otherwise leave it unchecked. Most firmwares use absolute values."; def->cli = "use-relative-e-distances!"; def->default_value = new ConfigOptionBool(false); def = this->add("use_volumetric_e", coBool); def->label = "Use volumetric E"; - def->tooltip = "This experimental setting uses outputs the E values in cubic millimeters instead of linear millimeters. If your firmware doesn't already know filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' in your start G-code in order to turn volumetric mode on and use the filament diameter associated to the filament selected in Slic3r. This is only supported in recent Marlin."; + def->tooltip = "This experimental setting uses outputs the E values in cubic millimeters " + "instead of linear millimeters. If your firmware doesn't already know " + "filament diameter(s), you can put commands like 'M200 D[filament_diameter_0] T0' " + "in your start G-code in order to turn volumetric mode on and use the filament " + "diameter associated to the filament selected in Slic3r. This is only supported " + "in recent Marlin."; def->cli = "use-volumetric-e!"; def->default_value = new ConfigOptionBool(false); def = this->add("variable_layer_height", coBool); def->label = "Enable variable layer height feature"; - def->tooltip = "Some printers or printer setups may have difficulties printing with a variable layer height. Enabled by default."; + def->tooltip = "Some printers or printer setups may have difficulties printing " + "with a variable layer height. Enabled by default."; def->cli = "variable-layer-height!"; def->default_value = new ConfigOptionBool(true); def = this->add("wipe", coBools); def->label = "Wipe while retracting"; - def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders."; + def->tooltip = "This flag will move the nozzle while retracting to minimize the possible blob " + "on leaky extruders."; def->cli = "wipe!"; - { - ConfigOptionBools* opt = new ConfigOptionBools(); - opt->values.push_back(false); - def->default_value = opt; - } + def->default_value = new ConfigOptionBools { false }; def = this->add("wipe_tower", coBool); def->label = "Enable"; - def->tooltip = "Multi material printers may need to prime or purge extruders on tool changes. Extrude the excess material into the wipe tower."; + def->tooltip = "Multi material printers may need to prime or purge extruders on tool changes. " + "Extrude the excess material into the wipe tower."; def->cli = "wipe-tower!"; def->default_value = new ConfigOptionBool(false); @@ -1651,7 +1702,9 @@ PrintConfigDef::PrintConfigDef() def = this->add("wipe_tower_per_color_wipe", coFloat); def->label = "Per color change depth"; - def->tooltip = "Depth of a wipe color per color change. For N colors, there will be maximum (N-1) tool switches performed, therefore the total depth of the wipe tower will be (N-1) times this value."; + def->tooltip = "Depth of a wipe color per color change. For N colors, there will be " + "maximum (N-1) tool switches performed, therefore the total depth " + "of the wipe tower will be (N-1) times this value."; def->sidetext = "mm"; def->cli = "wipe-tower-per-color-wipe=f"; def->default_value = new ConfigOptionFloat(15.); @@ -1659,14 +1712,19 @@ PrintConfigDef::PrintConfigDef() def = this->add("xy_size_compensation", coFloat); def->label = "XY Size Compensation"; def->category = "Advanced"; - def->tooltip = "The object will be grown/shrunk in the XY plane by the configured value (negative = inwards, positive = outwards). This might be useful for fine-tuning hole sizes."; + def->tooltip = "The object will be grown/shrunk in the XY plane by the configured value " + "(negative = inwards, positive = outwards). This might be useful " + "for fine-tuning hole sizes."; def->sidetext = "mm"; def->cli = "xy-size-compensation=f"; def->default_value = new ConfigOptionFloat(0); def = this->add("z_offset", coFloat); def->label = "Z offset"; - def->tooltip = "This value will be added (or subtracted) from all the Z coordinates in the output G-code. It is used to compensate for bad Z endstop position: for example, if your endstop zero actually leaves the nozzle 0.3mm far from the print bed, set this to -0.3 (or fix your endstop)."; + def->tooltip = "This value will be added (or subtracted) from all the Z coordinates " + "in the output G-code. It is used to compensate for bad Z endstop position: " + "for example, if your endstop zero actually leaves the nozzle 0.3mm far " + "from the print bed, set this to -0.3 (or fix your endstop)."; def->sidetext = "mm"; def->cli = "z-offset=f"; def->default_value = new ConfigOptionFloat(0); @@ -1719,18 +1777,16 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va value = "60%"; } - // cemetery of old config settings - if (opt_key == "duplicate_x" || opt_key == "duplicate_y" || opt_key == "multiply_x" - || opt_key == "multiply_y" || opt_key == "support_material_tool" - || opt_key == "acceleration" || opt_key == "adjust_overhang_flow" - || opt_key == "standby_temperature" || opt_key == "scale" || opt_key == "rotate" - || opt_key == "duplicate" || opt_key == "duplicate_grid" || opt_key == "rotate" - || opt_key == "scale" || opt_key == "duplicate_grid" - || opt_key == "start_perimeters_at_concave_points" - || opt_key == "start_perimeters_at_non_overhang" || opt_key == "randomize_start" - || opt_key == "seal_position" || opt_key == "bed_size" || opt_key == "octoprint_host" - || opt_key == "print_center" || opt_key == "g0" || opt_key == "threads") - { + // Ignore the following obsolete configuration keys: + static std::set<std::string> ignore = { + "duplicate_x", "duplicate_y", "gcode_arcs", "multiply_x", "multiply_y", + "support_material_tool", "acceleration", "adjust_overhang_flow", + "standby_temperature", "scale", "rotate", "duplicate", "duplicate_grid", + "start_perimeters_at_concave_points", "start_perimeters_at_non_overhang", "randomize_start", + "seal_position", "vibration_limit", "bed_size", "octoprint_host", + "print_center", "g0", "threads", "pressure_advance" + }; + if (ignore.find(opt_key) != ignore.end()) { opt_key = ""; return; } @@ -1744,6 +1800,18 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va PrintConfigDef print_config_def; +DynamicPrintConfig* DynamicPrintConfig::new_from_defaults() +{ + return new_from_defaults_keys(FullPrintConfig::defaults().keys()); +} + +DynamicPrintConfig* DynamicPrintConfig::new_from_defaults_keys(const std::vector<std::string> &keys) +{ + auto *out = new DynamicPrintConfig(); + out->apply_only(FullPrintConfig::defaults(), keys); + return out; +} + void DynamicPrintConfig::normalize() { if (this->has("extruder")) { @@ -1785,7 +1853,7 @@ std::string DynamicPrintConfig::validate() { // Full print config is initialized from the defaults. FullPrintConfig fpc; - fpc.apply(*this); + fpc.apply(*this, true); // Verify this print options through the FullPrintConfig. return fpc.validate(); } @@ -1825,7 +1893,7 @@ std::string FullPrintConfig::validate() // --nozzle-diameter for (double nd : this->nozzle_diameter.values) - if (nd < 1) + if (nd < 0.005) return "Invalid value for --nozzle-diameter"; // --perimeters diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 18beeff6f..ab58aa356 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -142,6 +142,9 @@ public: DynamicPrintConfig() {} DynamicPrintConfig(const DynamicPrintConfig &other) : DynamicConfig(other) {} + static DynamicPrintConfig* new_from_defaults(); + static DynamicPrintConfig* new_from_defaults_keys(const std::vector<std::string> &keys); + // 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; } @@ -449,6 +452,7 @@ class GCodeConfig : public StaticPrintConfig STATIC_PRINT_CONFIG_CACHE(GCodeConfig) public: ConfigOptionString before_layer_gcode; + ConfigOptionString between_objects_gcode; ConfigOptionFloats deretract_speed; ConfigOptionString end_gcode; ConfigOptionStrings end_filament_gcode; @@ -497,6 +501,7 @@ protected: void initialize(StaticCacheBase &cache, const char *base_ptr) { OPT_PTR(before_layer_gcode); + OPT_PTR(between_objects_gcode); OPT_PTR(deretract_speed); OPT_PTR(end_gcode); OPT_PTR(end_filament_gcode); diff --git a/xs/src/libslic3r/Slicing.hpp b/xs/src/libslic3r/Slicing.hpp index 1534e19f5..b4a074bb5 100644 --- a/xs/src/libslic3r/Slicing.hpp +++ b/xs/src/libslic3r/Slicing.hpp @@ -103,7 +103,7 @@ inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters sp1.layer_height == sp2.layer_height && sp1.min_layer_height == sp2.min_layer_height && sp1.max_layer_height == sp2.max_layer_height && - sp1.max_suport_layer_height == sp2.max_suport_layer_height && +// sp1.max_suport_layer_height == sp2.max_suport_layer_height && sp1.first_print_layer_height == sp2.first_print_layer_height && sp1.first_object_layer_height == sp2.first_object_layer_height && sp1.first_object_layer_bridging == sp2.first_object_layer_bridging && diff --git a/xs/src/libslic3r/SupportMaterial.cpp b/xs/src/libslic3r/SupportMaterial.cpp index 7a05cf998..07943fb15 100644 --- a/xs/src/libslic3r/SupportMaterial.cpp +++ b/xs/src/libslic3r/SupportMaterial.cpp @@ -2569,7 +2569,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, float(- flow.scaled_spacing())); + to_infill = offset_ex(to_infill, float(- 0.4 * flow.scaled_spacing())); extrusion_entities_append_paths( support_layer.support_fills.entities, to_polylines(STDMOVE(to_infill_polygons)), @@ -2602,7 +2602,8 @@ void PrintObjectSupportMaterial::generate_toolpaths( // Base flange. filler->angle = raft_angle_1st_layer; filler->spacing = m_first_layer_flow.spacing(); - density = 0.5f; + // 70% of density on the 1st layer. + density = 0.7f; } else if (support_layer_id >= m_slicing_params.base_raft_layers) { filler->angle = raft_angle_interface; // We don't use $base_flow->spacing because we need a constant spacing @@ -2776,7 +2777,7 @@ void PrintObjectSupportMaterial::generate_toolpaths( // TODO: use brim ordering algorithm Polygons to_infill_polygons = to_polygons(to_infill); // TODO: use offset2_ex() - to_infill = offset_ex(to_infill, - float(flow.scaled_spacing())); + to_infill = offset_ex(to_infill, - 0.4 * float(flow.scaled_spacing())); extrusion_entities_append_paths( base_layer.extrusions, to_polylines(STDMOVE(to_infill_polygons)), diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index 38d89938a..0dbbf5f8d 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -1208,6 +1208,8 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, ExPolygons* slic // perform a safety offset to merge very close facets (TODO: find test case for this) double safety_offset = scale_(0.0499); +//FIXME see https://github.com/prusa3d/Slic3r/issues/520 +// double safety_offset = scale_(0.0001); ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); #ifdef SLIC3R_TRIANGLEMESH_DEBUG diff --git a/xs/src/libslic3r/Utils.hpp b/xs/src/libslic3r/Utils.hpp index 79e1c2e2e..3afbc912f 100644 --- a/xs/src/libslic3r/Utils.hpp +++ b/xs/src/libslic3r/Utils.hpp @@ -1,6 +1,8 @@ #ifndef slic3r_Utils_hpp_ #define slic3r_Utils_hpp_ +#include <locale> + namespace Slic3r { extern void set_logging_level(unsigned int level); @@ -8,15 +10,31 @@ extern void trace(unsigned int level, const char *message); // Set a path with GUI resource files. void set_var_dir(const std::string &path); -// Return a path to the GUI resource files. +// Return a full path to the GUI resource files. const std::string& var_dir(); -// Return a resource path for a file_name. +// Return a full resource path for a file_name. std::string var(const std::string &file_name); +// Set a path with various static definition data (for example the initial config bundles). +void set_resources_dir(const std::string &path); +// Return a full path to the resources directory. +const std::string& resources_dir(); + +// Set a path with preset files. +void set_data_dir(const std::string &path); +// Return a full path to the GUI resource files. +const std::string& data_dir(); + extern std::string encode_path(const char *src); extern std::string decode_path(const char *src); extern std::string normalize_utf8_nfc(const char *src); +// Timestamp formatted for header_slic3r_generated(). +extern std::string timestamp_str(); +// Standard "generated by Slic3r version xxx timestamp xxx" header string, +// to be placed at the top of Slic3r generated files. +inline std::string header_slic3r_generated() { return std::string("generated by " SLIC3R_FORK_NAME " " SLIC3R_VERSION " " ) + timestamp_str(); } + // Compute the next highest power of 2 of 32-bit v // http://graphics.stanford.edu/~seander/bithacks.html template<typename T> diff --git a/xs/src/libslic3r/libslic3r.h b/xs/src/libslic3r/libslic3r.h index e70113710..f71311e7f 100644 --- a/xs/src/libslic3r/libslic3r.h +++ b/xs/src/libslic3r/libslic3r.h @@ -14,7 +14,7 @@ #include <boost/thread.hpp> #define SLIC3R_FORK_NAME "Slic3r Prusa Edition" -#define SLIC3R_VERSION "1.33.8.devel" +#define SLIC3R_VERSION "1.38.4" #define SLIC3R_BUILD "UNKNOWN" typedef long coord_t; diff --git a/xs/src/libslic3r/utils.cpp b/xs/src/libslic3r/utils.cpp index 6e60f978d..ee579161f 100644 --- a/xs/src/libslic3r/utils.cpp +++ b/xs/src/libslic3r/utils.cpp @@ -1,4 +1,5 @@ #include <locale> +#include <ctime> #include <boost/log/core.hpp> #include <boost/log/trivial.hpp> @@ -6,8 +7,9 @@ #include <boost/locale.hpp> +#include <boost/algorithm/string/predicate.hpp> +#include <boost/date_time/local_time/local_time.hpp> #include <boost/filesystem.hpp> - #include <boost/nowide/integration/filesystem.hpp> #include <boost/nowide/convert.hpp> @@ -87,6 +89,30 @@ std::string var(const std::string &file_name) return file.string(); } +static std::string g_resources_dir; + +void set_resources_dir(const std::string &dir) +{ + g_resources_dir = dir; +} + +const std::string& resources_dir() +{ + return g_resources_dir; +} + +static std::string g_data_dir; + +void set_data_dir(const std::string &dir) +{ + g_data_dir = dir; +} + +const std::string& data_dir() +{ + return g_data_dir; +} + } // namespace Slic3r #ifdef SLIC3R_HAS_BROKEN_CROAK @@ -207,4 +233,16 @@ std::string normalize_utf8_nfc(const char *src) return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8); } +std::string timestamp_str() +{ + const auto now = boost::posix_time::second_clock::local_time(); + const auto date = now.date(); + char buf[2048]; + sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d", + // Local date in an ANSII format. + int(now.date().year()), int(now.date().month()), int(now.date().day()), + int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds())); + return buf; +} + }; // namespace Slic3r |