From 3216448bbc67d76da197591f349e81ab9f65113e Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Fri, 29 Oct 2021 13:36:26 +0200 Subject: [PATCH] PlaceholderParser: implemented round(), digits() and zdigits() macros. round() rounds to an integer. This is a popular request, for example #3472 digits(value, num_digits, num_decimals) rounds to num_digits and num_decimals, left filled with spaces. digits(value, num_digits) the same as digits(value, num_digits, 0) Neither decimal separator nor any decimals after decimal separator are emitted. zdigits(...) is the same as digits(...) only left filled with zeros. If the result does not fit num_digits, the result is never trimmed. --- src/libslic3r/PlaceholderParser.cpp | 68 ++++++++++++++++++++- tests/libslic3r/test_placeholder_parser.cpp | 14 +++++ 2 files changed, 79 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index c4702aef2..1aa5a883e 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -238,6 +238,7 @@ namespace client 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()); } + int as_i_rounded() const { return (this->type == TYPE_INT) ? this->i() : int(std::round(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; } @@ -319,7 +320,7 @@ namespace client expr unary_integer(const Iterator start_pos) const { switch (this->type) { - case TYPE_INT : + case TYPE_INT: return expr(this->i(), start_pos, this->it_range.end()); case TYPE_DOUBLE: return expr(static_cast(this->d()), start_pos, this->it_range.end()); @@ -331,10 +332,25 @@ namespace client return expr(); } + expr round(const Iterator start_pos) const + { + switch (this->type) { + case TYPE_INT: + return expr(this->i(), start_pos, this->it_range.end()); + case TYPE_DOUBLE: + return expr(static_cast(std::round(this->d())), start_pos, this->it_range.end()); + default: + this->throw_exception("Cannot round a non-numeric value."); + } + assert(false); + // Suppress compiler warnings. + return expr(); + } + expr unary_not(const Iterator start_pos) const { switch (this->type) { - case TYPE_BOOL : + case TYPE_BOOL: return expr(! this->b(), start_pos, this->it_range.end()); default: this->throw_exception("Cannot apply a not operator."); @@ -549,6 +565,30 @@ namespace client } } + // Store the result into param1. + // param3 is optional + template + static void digits(expr ¶m1, expr ¶m2, expr ¶m3) + { + throw_if_not_numeric(param1); + if (param2.type != TYPE_INT) + param2.throw_exception("digits: second parameter must be integer"); + bool has_decimals = param3.type != TYPE_EMPTY; + if (has_decimals && param3.type != TYPE_INT) + param3.throw_exception("digits: third parameter must be integer"); + + char buf[256]; + int ndigits = std::clamp(param2.as_i(), 0, 64); + if (has_decimals) { + // Format as double. + int decimals = std::clamp(param3.as_i(), 0, 64); + sprintf(buf, leading_zeros ? "%0*.*lf" : "%*.*lf", ndigits, decimals, param1.as_d()); + } else + // Format as int. + sprintf(buf, leading_zeros ? "%0*d" : "%*d", ndigits, param1.as_i_rounded()); + param1.set_s(buf); + } + static void regex_op(expr &lhs, boost::iterator_range &rhs, char op) { const std::string *subject = nullptr; @@ -930,6 +970,7 @@ namespace client { "additive_expression", "Expecting an expression." }, { "multiplicative_expression", "Expecting an expression." }, { "unary_expression", "Expecting an expression." }, + { "optional_parameter", "Expecting a closing brace or an optional parameter." }, { "scalar_variable_reference", "Expecting a scalar variable reference."}, { "variable_reference", "Expecting a variable reference."}, { "regular_expression", "Expecting a regular expression."} @@ -1194,6 +1235,10 @@ namespace client { out = value.unary_not(out.it_range.begin()); } static void to_int(expr &value, expr &out) { out = value.unary_integer(out.it_range.begin()); } + static void round(expr &value, expr &out) + { out = value.round(out.it_range.begin()); } + // For indicating "no optional parameter". + static void noexpr(expr &out) { out.reset(); } }; unary_expression = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( scalar_variable_reference(_r1) [ _val = _1 ] @@ -1207,7 +1252,12 @@ namespace client [ px::bind(&expr::max, _val, _2) ] | (kw["random"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > ')') [ px::bind(&MyContext::random, _r1, _val, _2) ] - | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] + | (kw["digits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) + [ px::bind(&expr::digits, _val, _2, _3) ] + | (kw["zdigits"] > '(' > conditional_expression(_r1) [_val = _1] > ',' > conditional_expression(_r1) > optional_parameter(_r1)) + [ px::bind(&expr::digits, _val, _2, _3) ] + | (kw["int"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::to_int, _1, _val) ] + | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _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) ] @@ -1216,6 +1266,12 @@ namespace client ); unary_expression.name("unary_expression"); + optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( + lit(')') [ px::bind(&FactorActions::noexpr, _val) ] + | (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ] + ); + optional_parameter.name("optional_parameter"); + scalar_variable_reference = variable_reference(_r1)[_a=_1] >> ( @@ -1234,6 +1290,8 @@ namespace client keywords.add ("and") + ("digits") + ("zdigits") ("if") ("int") //("inf") @@ -1244,6 +1302,7 @@ namespace client ("min") ("max") ("random") + ("round") ("not") ("or") ("true"); @@ -1266,6 +1325,7 @@ namespace client debug(additive_expression); debug(multiplicative_expression); debug(unary_expression); + debug(optional_parameter); debug(scalar_variable_reference); debug(variable_reference); debug(regular_expression); @@ -1303,6 +1363,8 @@ namespace client RuleExpression multiplicative_expression; // Number literals, functions, braced expressions, variable references, variable indexing references. RuleExpression unary_expression; + // Accepting an optional parameter. + RuleExpression optional_parameter; // Rule to capture a regular expression enclosed in //. qi::rule(), spirit_encoding::space_type> regular_expression; // Evaluate boolean expression into bool. diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 59784e940..abf7308f2 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -51,6 +51,20 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("math: max(13.4, -1238.1)") { REQUIRE(std::stod(parser.process("{max(13.4, -1238.1)}")) == Approx(13.4)); } SECTION("math: int(13.4)") { REQUIRE(parser.process("{int(13.4)}") == "13"); } SECTION("math: int(-13.4)") { REQUIRE(parser.process("{int(-13.4)}") == "-13"); } + SECTION("math: round(13.4)") { REQUIRE(parser.process("{round(13.4)}") == "13"); } + SECTION("math: round(-13.4)") { REQUIRE(parser.process("{round(-13.4)}") == "-13"); } + SECTION("math: round(13.6)") { REQUIRE(parser.process("{round(13.6)}") == "14"); } + SECTION("math: round(-13.6)") { REQUIRE(parser.process("{round(-13.6)}") == "-14"); } + SECTION("math: digits(5, 15)") { REQUIRE(parser.process("{digits(5, 15)}") == " 5"); } + SECTION("math: digits(5., 15)") { REQUIRE(parser.process("{digits(5., 15)}") == " 5"); } + SECTION("math: zdigits(5, 15)") { REQUIRE(parser.process("{zdigits(5, 15)}") == "000000000000005"); } + SECTION("math: zdigits(5., 15)") { REQUIRE(parser.process("{zdigits(5., 15)}") == "000000000000005"); } + SECTION("math: digits(5, 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); } + SECTION("math: digits(5., 15, 8)") { REQUIRE(parser.process("{digits(5, 15, 8)}") == " 5.00000000"); } + SECTION("math: zdigits(5, 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); } + SECTION("math: zdigits(5., 15, 8)") { REQUIRE(parser.process("{zdigits(5, 15, 8)}") == "000005.00000000"); } + SECTION("math: digits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{digits(13.84375892476, 15, 8)}") == " 13.84375892"); } + SECTION("math: zdigits(13.84375892476, 15, 8)") { REQUIRE(parser.process("{zdigits(13.84375892476, 15, 8)}") == "000013.84375892"); } // Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions. // first_layer_extrusion_width ratio_over first_layer_heigth.