diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 109c949cd..059cbdac4 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -875,6 +875,38 @@ namespace client output.it_range = boost::iterator_range(opt.it_range.begin(), it_end); } + // Return a boolean value, true if the scalar variable referenced by "opt" is nullable and it has a nil value. + template + static void is_nil_test_scalar( + const MyContext *ctx, + OptWithPos &opt, + expr &output) + { + if (opt.opt->is_vector()) + ctx->throw_exception("Referencing a vector variable when scalar is expected", opt.it_range); + output.set_b(opt.opt->is_nil()); + output.it_range = opt.it_range; + } + + // Return a boolean value, true if an element of a vector variable referenced by "opt[index]" is nullable and it has a nil value. + template + static void is_nil_test_vector( + const MyContext *ctx, + OptWithPos &opt, + int &index, + Iterator it_end, + expr &output) + { + if (opt.opt->is_scalar()) + ctx->throw_exception("Referencing a scalar variable when vector is expected", opt.it_range); + const ConfigOptionVectorBase *vec = static_cast(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); + output.set_b(static_cast(opt.opt)->is_nil(idx)); + output.it_range = boost::iterator_range(opt.it_range.begin(), it_end); + } + // Verify that the expression returns an integer, which may be used // to address a vector. template @@ -973,6 +1005,7 @@ namespace client { "unary_expression", "Expecting an expression." }, { "optional_parameter", "Expecting a closing brace or an optional parameter." }, { "scalar_variable_reference", "Expecting a scalar variable reference."}, + { "is_nil_test", "Expecting a scalar variable reference."}, { "variable_reference", "Expecting a variable reference."}, { "regular_expression", "Expecting a regular expression."} }; @@ -1259,6 +1292,7 @@ namespace client [ px::bind(&expr::template 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) ] + | (kw["is_nil"] > '(' > is_nil_test(_r1) > ')') [_val = _1] | (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) ] @@ -1286,6 +1320,15 @@ namespace client [ px::bind(&MyContext::resolve_variable, _r1, _1, _val) ]; variable_reference.name("variable reference"); + is_nil_test = + variable_reference(_r1)[_a=_1] >> + ( + ('[' > additive_expression(_r1)[px::bind(&MyContext::evaluate_index, _1, _b)] > ']' > + iter_pos[px::bind(&MyContext::is_nil_test_vector, _r1, _a, _b, _1, _val)]) + | eps[px::bind(&MyContext::is_nil_test_scalar, _r1, _a, _val)] + ); + is_nil_test.name("is_nil test"); + regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']]; regular_expression.name("regular_expression"); @@ -1295,6 +1338,7 @@ namespace client ("zdigits") ("if") ("int") + ("is_nil") //("inf") ("else") ("elsif") @@ -1329,6 +1373,7 @@ namespace client debug(optional_parameter); debug(scalar_variable_reference); debug(variable_reference); + debug(is_nil_test); debug(regular_expression); } } @@ -1374,6 +1419,8 @@ namespace client qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> scalar_variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. qi::rule(const MyContext*), spirit_encoding::space_type> variable_reference; + // Evaluating whether a nullable variable is nil. + qi::rule(const MyContext*), qi::locals, int>, spirit_encoding::space_type> is_nil_test; qi::rule, spirit_encoding::space_type> if_else_output; // qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index abf7308f2..a4b3b7514 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -24,6 +24,8 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { // a percent to what. config.option("first_layer_speed")->value = 50.; config.option("first_layer_speed")->percent = true; + ConfigOptionFloatsNullable *opt_filament_retract_length = config.option("filament_retract_length", true); + opt_filament_retract_length->values = { 5., ConfigOptionFloatsNullable::nil_value(), 3. }; parser.apply_config(config); parser.set("foo", 0); @@ -33,6 +35,9 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { SECTION("nested config options (legacy syntax)") { REQUIRE(parser.process("[temperature_[foo]]") == "357"); } SECTION("array reference") { REQUIRE(parser.process("{temperature[foo]}") == "357"); } SECTION("whitespaces and newlines are maintained") { REQUIRE(parser.process("test [ temperature_ [foo] ] \n hu") == "test 357 \n hu"); } + SECTION("nullable is not null") { REQUIRE(parser.process("{is_nil(filament_retract_length[0])}") == "false"); } + SECTION("nullable is null") { REQUIRE(parser.process("{is_nil(filament_retract_length[1])}") == "true"); } + SECTION("nullable is not null 2") { REQUIRE(parser.process("{is_nil(filament_retract_length[2])}") == "false"); } // Test the math expressions. SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); }