From 3d3654707b97f679037cb66c2143338cf8baf917 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Tue, 1 Nov 2016 13:41:24 +0100 Subject: [PATCH] Added "Notes" page to the filament configuration. Added "filament_max_volumetric_speed", a cap on the maximum volumetric extrusion role, filament specific. This is very useful when mixing rigid filament with a soft filament. Extended the import / export of multi-string values into configuration values, including the test cases. Multi-line strings will be enclosed into quotes, quotes escaped using a C-style escape sequences. Single word strings could still be stored without quotes. --- lib/Slic3r/GUI/Tab.pm | 26 ++++- xs/src/libslic3r/Config.cpp | 165 +++++++++++++++++++++++++++++-- xs/src/libslic3r/Config.hpp | 53 +++------- xs/src/libslic3r/GCode.cpp | 7 ++ xs/src/libslic3r/PrintConfig.cpp | 25 +++++ xs/src/libslic3r/PrintConfig.hpp | 4 + xs/t/15_config.t | 46 ++++++++- 7 files changed, 280 insertions(+), 46 deletions(-) diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index d30e31abc..5bf40f5d4 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -138,7 +138,10 @@ sub new { $self->_on_presets_changed; }); + # C++ instance DynamicPrintConfig $self->{config} = Slic3r::Config->new; + # Initialize the DynamicPrintConfig by default keys/values. + # Possible %params keys: no_controller $self->build(%params); $self->update_tree; $self->_update; @@ -910,7 +913,7 @@ sub build { my $self = shift; $self->init_config_options(qw( - filament_colour filament_diameter extrusion_multiplier + filament_colour filament_diameter filament_notes filament_max_volumetric_speed extrusion_multiplier temperature first_layer_temperature bed_temperature first_layer_bed_temperature fan_always_on cooling min_fan_speed max_fan_speed bridge_fan_speed disable_fan_first_layers @@ -991,6 +994,27 @@ sub build { $optgroup->append_single_option_line('min_print_speed'); } } + + { + my $page = $self->add_options_page('Advanced', 'wrench.png'); + { + my $optgroup = $page->new_optgroup('Print speed override'); + $optgroup->append_single_option_line('filament_max_volumetric_speed', 0); + } + } + + { + my $page = $self->add_options_page('Notes', 'note.png'); + { + my $optgroup = $page->new_optgroup('Notes', + label_width => 0, + ); + my $option = $optgroup->get_option('filament_notes', 0); + $option->full_width(1); + $option->height(250); + $optgroup->append_single_option_line($option); + } + } } sub _update { diff --git a/xs/src/libslic3r/Config.cpp b/xs/src/libslic3r/Config.cpp index 39c0da2bb..cdeee4dd8 100644 --- a/xs/src/libslic3r/Config.cpp +++ b/xs/src/libslic3r/Config.cpp @@ -8,6 +8,159 @@ namespace Slic3r { +std::string escape_string_cstyle(const std::string &str) +{ + // Allocate a buffer twice the input string length, + // so the output will fit even if all input characters get escaped. + std::vector out(str.size() * 2, 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == '\n' || c == '\r') { + (*outptr ++) = '\\'; + (*outptr ++) = 'n'; + } else + (*outptr ++) = c; + } + return std::string(out.data(), outptr - out.data()); +} + +std::string escape_strings_cstyle(const std::vector &strs) +{ + // 1) Estimate the output buffer size to avoid buffer reallocation. + size_t outbuflen = 0; + for (size_t i = 0; i < strs.size(); ++ i) + // Reserve space for every character escaped + quotes + semicolon. + outbuflen += strs[i].size() * 2 + 3; + // 2) Fill in the buffer. + std::vector out(outbuflen, 0); + char *outptr = out.data(); + for (size_t j = 0; j < strs.size(); ++ j) { + if (j > 0) + // Separate the strings. + (*outptr ++) = ';'; + const std::string &str = strs[j]; + // Is the string simple or complex? Complex string contains spaces, tabs, new lines and other + // escapable characters. Empty string shall be quoted as well, if it is the only string in strs. + bool should_quote = strs.size() == 1 && str.empty(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == ' ' || c == '\t' || c == '\\' || c == '"' || c == '\r' || c == '\n') { + should_quote = true; + break; + } + } + if (should_quote) { + (*outptr ++) = '"'; + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == '\\' || c == '"') { + (*outptr ++) = '\\'; + (*outptr ++) = c; + } else if (c == '\n' || c == '\r') { + (*outptr ++) = '\\'; + (*outptr ++) = 'n'; + } else + (*outptr ++) = c; + } + (*outptr ++) = '"'; + } else { + memcpy(outptr, str.data(), str.size()); + outptr += str.size(); + } + } + return std::string(out.data(), outptr - out.data()); +} + +bool unescape_string_cstyle(const std::string &str, std::string &str_out) +{ + std::vector out(str.size(), 0); + char *outptr = out.data(); + for (size_t i = 0; i < str.size(); ++ i) { + char c = str[i]; + if (c == '\\') { + if (++ i == str.size()) + return false; + c = str[i]; + if (c == 'n') + (*outptr ++) = '\n'; + } else + (*outptr ++) = c; + } + str_out.assign(out.data(), outptr - out.data()); + return true; +} + +bool unescape_strings_cstyle(const std::string &str, std::vector &out) +{ + out.clear(); + if (str.empty()) + return true; + + size_t i = 0; + for (;;) { + // Skip white spaces. + char c = str[i]; + while (c == ' ' || c == '\t') { + if (++ i == str.size()) + return true; + c = str[i]; + } + // Start of a word. + std::vector buf; + buf.reserve(16); + // Is it enclosed in quotes? + c = str[i]; + if (c == '"') { + // Complex case, string is enclosed in quotes. + for (++ i; i < str.size(); ++ i) { + c = str[i]; + if (c == '"') { + // End of string. + break; + } + if (c == '\\') { + if (++ i == str.size()) + return false; + c = str[i]; + if (c == 'n') + c = '\n'; + } + buf.push_back(c); + } + if (i == str.size()) + return false; + ++ i; + } else { + for (; i < str.size(); ++ i) { + c = str[i]; + if (c == ';') + break; + buf.push_back(c); + } + } + // Store the string into the output vector. + out.push_back(std::string(buf.data(), buf.size())); + if (i == str.size()) + break; + // Skip white spaces. + c = str[i]; + while (c == ' ' || c == '\t') { + if (++ i == str.size()) + // End of string. This is correct. + return true; + c = str[i]; + } + if (c != ';') + return false; + if (++ i == str.size()) { + // Emit one additional empty string. + out.push_back(std::string()); + return true; + } + } +} + bool operator== (const ConfigOption &a, const ConfigOption &b) { @@ -116,16 +269,16 @@ ConfigBase::set_deserialize(const t_config_option_key &opt_key, std::string str) // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to the layer height. double -ConfigBase::get_abs_value(const t_config_option_key &opt_key) { - ConfigOption* opt = this->option(opt_key, false); - if (ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { +ConfigBase::get_abs_value(const t_config_option_key &opt_key) const { + const ConfigOption* opt = this->option(opt_key); + if (const ConfigOptionFloatOrPercent* optv = dynamic_cast(opt)) { // get option definition const ConfigOptionDef* def = this->def->get(opt_key); assert(def != NULL); // compute absolute value over the absolute value of the base option return optv->get_abs_value(this->get_abs_value(def->ratio_over)); - } else if (ConfigOptionFloat* optv = dynamic_cast(opt)) { + } else if (const ConfigOptionFloat* optv = dynamic_cast(opt)) { return optv->value; } else { throw "Not a valid option type for get_abs_value()"; @@ -135,9 +288,9 @@ ConfigBase::get_abs_value(const t_config_option_key &opt_key) { // Return an absolute value of a possibly relative config variable. // For example, return absolute infill extrusion width, either from an absolute value, or relative to a provided value. double -ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) { +ConfigBase::get_abs_value(const t_config_option_key &opt_key, double ratio_over) const { // get stored option value - ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); + const ConfigOptionFloatOrPercent* opt = dynamic_cast(this->option(opt_key)); assert(opt != NULL); // compute absolute value diff --git a/xs/src/libslic3r/Config.hpp b/xs/src/libslic3r/Config.hpp index ac6b03a49..3fbd82060 100644 --- a/xs/src/libslic3r/Config.hpp +++ b/xs/src/libslic3r/Config.hpp @@ -18,6 +18,11 @@ namespace Slic3r { typedef std::string t_config_option_key; typedef std::vector t_config_option_keys; +extern std::string escape_string_cstyle(const std::string &str); +extern std::string escape_strings_cstyle(const std::vector &strs); +extern bool unescape_string_cstyle(const std::string &str, std::string &out); +extern bool unescape_strings_cstyle(const std::string &str, std::vector &out); + // A generic value of a configuration option. class ConfigOption { public: @@ -112,6 +117,7 @@ class ConfigOptionFloats : public ConfigOptionVector std::vector vserialize() const { std::vector vv; + vv.reserve(this->values.size()); for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; @@ -171,6 +177,7 @@ class ConfigOptionInts : public ConfigOptionVector std::vector vserialize() const { std::vector vv; + vv.reserve(this->values.size()); for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { std::ostringstream ss; ss << *it; @@ -199,29 +206,12 @@ class ConfigOptionString : public ConfigOptionSingle ConfigOptionString() : ConfigOptionSingle("") {}; ConfigOptionString(std::string _value) : ConfigOptionSingle(_value) {}; - std::string serialize() const { - std::string str = this->value; - - // s/\R/\\n/g - size_t pos = 0; - while ((pos = str.find("\n", pos)) != std::string::npos || (pos = str.find("\r", pos)) != std::string::npos) { - str.replace(pos, 1, "\\n"); - pos += 2; // length of "\\n" - } - - return str; - }; - + std::string serialize() const { + return escape_string_cstyle(this->value); + } + bool deserialize(std::string str) { - // s/\\n/\n/g - size_t pos = 0; - while ((pos = str.find("\\n", pos)) != std::string::npos) { - str.replace(pos, 2, "\n"); - pos += 1; // length of "\n" - } - - this->value = str; - return true; + return unescape_string_cstyle(str, this->value); }; }; @@ -231,12 +221,7 @@ class ConfigOptionStrings : public ConfigOptionVector public: std::string serialize() const { - std::ostringstream ss; - for (std::vector::const_iterator it = this->values.begin(); it != this->values.end(); ++it) { - if (it - this->values.begin() != 0) ss << ";"; - ss << *it; - } - return ss.str(); + return escape_strings_cstyle(this->values); }; std::vector vserialize() const { @@ -244,13 +229,7 @@ class ConfigOptionStrings : public ConfigOptionVector }; bool deserialize(std::string str) { - this->values.clear(); - std::istringstream is(str); - std::string item_str; - while (std::getline(is, item_str, ';')) { - this->values.push_back(item_str); - } - return true; + return unescape_strings_cstyle(str, this->values); }; }; @@ -637,8 +616,8 @@ class ConfigBase std::string serialize(const t_config_option_key &opt_key) const; bool set_deserialize(const t_config_option_key &opt_key, std::string str); - double get_abs_value(const t_config_option_key &opt_key); - double get_abs_value(const t_config_option_key &opt_key, double ratio_over); + double get_abs_value(const t_config_option_key &opt_key) const; + double get_abs_value(const t_config_option_key &opt_key, double ratio_over) const; void setenv_(); }; diff --git a/xs/src/libslic3r/GCode.cpp b/xs/src/libslic3r/GCode.cpp index ffc10b9a4..ee76e75d9 100644 --- a/xs/src/libslic3r/GCode.cpp +++ b/xs/src/libslic3r/GCode.cpp @@ -865,6 +865,13 @@ GCode::_extrude(ExtrusionPath path, std::string description, double speed) this->config.max_volumetric_speed.value / path.mm3_per_mm ); } + if (EXTRUDER_CONFIG(filament_max_volumetric_speed) > 0) { + // cap speed with max_volumetric_speed anyway (even if user is not using autospeed) + speed = std::min( + speed, + EXTRUDER_CONFIG(filament_max_volumetric_speed) / path.mm3_per_mm + ); + } double F = speed * 60; // convert mm/sec to mm/min // extrude arc or line diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index 21a441a49..78b79d77d 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -298,6 +298,31 @@ PrintConfigDef::PrintConfigDef() def->default_value = opt; } + def = this->add("filament_notes", coStrings); + def->label = "Filament notes"; + def->tooltip = "You can put your notes regarding the filament here."; + def->cli = "filament-notes=s@"; + def->multiline = true; + def->full_width = true; + def->height = 130; + { + ConfigOptionStrings* opt = new ConfigOptionStrings(); + opt->values.push_back(""); + def->default_value = opt; + } + + 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->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 = 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."; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 55d34368d..4efdd4173 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -292,6 +292,7 @@ class GCodeConfig : public virtual StaticPrintConfig ConfigOptionString extrusion_axis; ConfigOptionFloats extrusion_multiplier; ConfigOptionFloats filament_diameter; + ConfigOptionFloats filament_max_volumetric_speed; ConfigOptionBool gcode_comments; ConfigOptionEnum gcode_flavor; ConfigOptionString layer_gcode; @@ -326,6 +327,7 @@ class GCodeConfig : public virtual StaticPrintConfig OPT_PTR(extrusion_axis); OPT_PTR(extrusion_multiplier); OPT_PTR(filament_diameter); + OPT_PTR(filament_max_volumetric_speed); OPT_PTR(gcode_comments); OPT_PTR(gcode_flavor); OPT_PTR(layer_gcode); @@ -385,6 +387,7 @@ class PrintConfig : public GCodeConfig ConfigOptionBool fan_always_on; ConfigOptionInt fan_below_layer_time; ConfigOptionStrings filament_colour; + ConfigOptionStrings filament_notes; ConfigOptionFloat first_layer_acceleration; ConfigOptionInt first_layer_bed_temperature; ConfigOptionFloatOrPercent first_layer_extrusion_width; @@ -441,6 +444,7 @@ class PrintConfig : public GCodeConfig OPT_PTR(fan_always_on); OPT_PTR(fan_below_layer_time); OPT_PTR(filament_colour); + OPT_PTR(filament_notes); OPT_PTR(first_layer_acceleration); OPT_PTR(first_layer_bed_temperature); OPT_PTR(first_layer_extrusion_width); diff --git a/xs/t/15_config.t b/xs/t/15_config.t index 838ce18b0..a4c5d5925 100644 --- a/xs/t/15_config.t +++ b/xs/t/15_config.t @@ -4,7 +4,8 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 110; +use Test::More tests => 146; +use Data::Dumper; foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintConfig) { $config->set('layer_height', 0.3); @@ -24,7 +25,48 @@ foreach my $config (Slic3r::Config->new, Slic3r::Config::Static::new_FullPrintCo is $config->serialize('notes'), 'foo\nbar', 'serialize string with newline'; $config->set_deserialize('notes', 'bar\nbaz'); is $config->get('notes'), "bar\nbaz", 'deserialize string with newline'; - + + foreach my $test_data ( + { + name => 'empty', + values => [], + serialized => '' + }, + { + name => 'single empty', + values => [''], + serialized => '""' + }, + { + name => 'single noempty, simple', + values => ['RGB'], + serialized => 'RGB' + }, + { + name => 'multiple noempty, simple', + values => ['ABC', 'DEF', '09182745@!#$*(&'], + serialized => 'ABC;DEF;09182745@!#$*(&' + }, + { + name => 'multiple, simple, some empty', + values => ['ABC', 'DEF', '', '09182745@!#$*(&', ''], + serialized => 'ABC;DEF;;09182745@!#$*(&;' + }, + { + name => 'complex', + values => ['some "quoted" notes', "yet\n some notes", "whatever \n notes", ''], + serialized => '"some \"quoted\" notes";"yet\n some notes";"whatever \n notes";' + } + ) + { + $config->set('filament_notes', $test_data->{values}); + is $config->serialize('filament_notes'), $test_data->{serialized}, 'serialize multi-string value ' . $test_data->{name}; + $config->set_deserialize('filament_notes', ''); + is_deeply $config->get('filament_notes'), [], 'deserialize multi-string value - empty ' . $test_data->{name}; + $config->set_deserialize('filament_notes', $test_data->{serialized}); + is_deeply $config->get('filament_notes'), $test_data->{values}, 'deserialize complex multi-string value ' . $test_data->{name}; + } + $config->set('first_layer_height', 0.3); ok abs($config->get('first_layer_height') - 0.3) < 1e-4, 'set/get absolute floatOrPercent'; is $config->serialize('first_layer_height'), '0.3', 'serialize absolute floatOrPercent';