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.
This commit is contained in:
parent
4e66ed81d2
commit
3d3654707b
@ -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 {
|
||||
|
@ -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<char> 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<std::string> &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<char> 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<char> 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<std::string> &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<char> 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<ConfigOptionFloatOrPercent*>(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<const ConfigOptionFloatOrPercent*>(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<ConfigOptionFloat*>(opt)) {
|
||||
} else if (const ConfigOptionFloat* optv = dynamic_cast<const ConfigOptionFloat*>(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<ConfigOptionFloatOrPercent*>(this->option(opt_key));
|
||||
const ConfigOptionFloatOrPercent* opt = dynamic_cast<const ConfigOptionFloatOrPercent*>(this->option(opt_key));
|
||||
assert(opt != NULL);
|
||||
|
||||
// compute absolute value
|
||||
|
@ -18,6 +18,11 @@ namespace Slic3r {
|
||||
typedef std::string t_config_option_key;
|
||||
typedef std::vector<std::string> t_config_option_keys;
|
||||
|
||||
extern std::string escape_string_cstyle(const std::string &str);
|
||||
extern std::string escape_strings_cstyle(const std::vector<std::string> &strs);
|
||||
extern bool unescape_string_cstyle(const std::string &str, std::string &out);
|
||||
extern bool unescape_strings_cstyle(const std::string &str, std::vector<std::string> &out);
|
||||
|
||||
// A generic value of a configuration option.
|
||||
class ConfigOption {
|
||||
public:
|
||||
@ -112,6 +117,7 @@ class ConfigOptionFloats : public ConfigOptionVector<double>
|
||||
|
||||
std::vector<std::string> vserialize() const {
|
||||
std::vector<std::string> vv;
|
||||
vv.reserve(this->values.size());
|
||||
for (std::vector<double>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
|
||||
std::ostringstream ss;
|
||||
ss << *it;
|
||||
@ -171,6 +177,7 @@ class ConfigOptionInts : public ConfigOptionVector<int>
|
||||
|
||||
std::vector<std::string> vserialize() const {
|
||||
std::vector<std::string> vv;
|
||||
vv.reserve(this->values.size());
|
||||
for (std::vector<int>::const_iterator it = this->values.begin(); it != this->values.end(); ++it) {
|
||||
std::ostringstream ss;
|
||||
ss << *it;
|
||||
@ -199,29 +206,12 @@ class ConfigOptionString : public ConfigOptionSingle<std::string>
|
||||
ConfigOptionString() : ConfigOptionSingle<std::string>("") {};
|
||||
ConfigOptionString(std::string _value) : ConfigOptionSingle<std::string>(_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<std::string>
|
||||
public:
|
||||
|
||||
std::string serialize() const {
|
||||
std::ostringstream ss;
|
||||
for (std::vector<std::string>::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<std::string> vserialize() const {
|
||||
@ -244,13 +229,7 @@ class ConfigOptionStrings : public ConfigOptionVector<std::string>
|
||||
};
|
||||
|
||||
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_();
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.";
|
||||
|
@ -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<GCodeFlavor> 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);
|
||||
|
@ -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';
|
||||
|
Loading…
Reference in New Issue
Block a user