From fbcc1ab27603a741246ef3762df67345cfed2c78 Mon Sep 17 00:00:00 2001 From: Pavel Mikus Date: Wed, 22 Mar 2023 13:23:07 +0100 Subject: [PATCH 1/5] Fix SPE-1595 - crash when no bridgin surfaces present and adaptive fill selected --- src/libslic3r/PrintObject.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 86cb16e2d0..aa1216a0fb 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -528,7 +528,8 @@ std::pair PrintObject::prepare its_transform(mesh, to_octree * this->trafo_centered(), true); // Triangulate internal bridging surfaces. - std::vector> overhangs(surfaces_w_bottom_z.size()); + std::vector> overhangs(std::max(surfaces_w_bottom_z.size(), size_t(1))); + // ^ make sure vector is not empty, even with no briding surfaces we still want to build the adaptive trees later, some continue normally tbb::parallel_for(tbb::blocked_range(0, surfaces_w_bottom_z.size()), [this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range &range) { for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) { From 0a3db4525d91e402f01d651b9111610f6f79b238 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Tue, 21 Mar 2023 14:50:53 +0100 Subject: [PATCH 2/5] Fix suggested by Stefan Csomor for releasing MacOs references. link: https://groups.google.com/g/wx-users/c/f2nVyT59H6M/m/_BocNmwCAgAJ --- src/slic3r/Utils/WxFontUtils.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/slic3r/Utils/WxFontUtils.cpp b/src/slic3r/Utils/WxFontUtils.cpp index 1c191606d3..2c862d5a5e 100644 --- a/src/slic3r/Utils/WxFontUtils.cpp +++ b/src/slic3r/Utils/WxFontUtils.cpp @@ -1,6 +1,7 @@ #include "WxFontUtils.hpp" #include #include +#include "libslic3r/Utils.hpp" #if defined(__APPLE__) #include @@ -14,10 +15,9 @@ using namespace Slic3r; using namespace Slic3r::GUI; -namespace { - #ifdef __APPLE__ -static bool is_valid_ttf(std::string_view file_path) +namespace { +bool is_valid_ttf(std::string_view file_path) { if (file_path.empty()) return false; auto const pos_point = file_path.find_last_of('.'); @@ -34,8 +34,7 @@ static bool is_valid_ttf(std::string_view file_path) if (extension_size >= 5) return false; // a lot of symbols for extension if (extension_size <= 1) return false; // few letters for extension - std::string_view extension = file_path.substr(pos_point + 1, - extension_size); + std::string_view extension = file_path.substr(pos_point + 1, extension_size); // Because of MacOs - Courier, Geneva, Monaco if (extension == std::string_view("dfont")) return false; @@ -44,28 +43,29 @@ static bool is_valid_ttf(std::string_view file_path) } // get filepath from wxFont on Mac OsX -static std::string get_file_path(const wxFont& font) { +std::string get_file_path(const wxFont& font) { const wxNativeFontInfo *info = font.GetNativeFontInfo(); if (info == nullptr) return {}; CTFontDescriptorRef descriptor = info->GetCTFontDescriptor(); - CFURLRef typeref = (CFURLRef) - CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); + CFURLRef typeref = (CFURLRef)CTFontDescriptorCopyAttribute(descriptor, kCTFontURLAttribute); if (typeref == NULL) return {}; + ScopeGuard sg([&typeref]() { CFRelease(typeref); }); CFStringRef url = CFURLGetString(typeref); if (url == NULL) return {}; - wxString file_uri; - wxCFTypeRef(url).GetValue(file_uri); + wxString file_uri(wxCFStringRef::AsString(url)); wxURI uri(file_uri); const wxString &path = uri.GetPath(); - std::string path_str(wxURI::Unescape(path).c_str()); + wxString path_unescaped = wxURI::Unescape(path); + std::string path_str = path_unescaped.ToUTF8().data(); BOOST_LOG_TRIVIAL(trace) << "input uri(" << file_uri.c_str() << ") convert to path(" << path.c_str() << ") string(" << path_str << ")."; return path_str; } -#endif // __APPLE__ } // namespace +#endif // __APPLE__ bool WxFontUtils::can_load(const wxFont &font) { + if (!font.IsOk()) return false; #ifdef _WIN32 return Emboss::can_load(font.GetHFONT()) != nullptr; From 963ca415d40f145ce2285de92b4f34401fad8096 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Tue, 21 Mar 2023 11:14:46 +0100 Subject: [PATCH 3/5] PlaceholderParser: new interpolate_table() "function" interpolate_table(x, (x0, y0), (x1, y1), (x2, y2), ...) interpolates a table at position x. --- src/libslic3r/PlaceholderParser.cpp | 116 +++++++++++++++++--- tests/libslic3r/test_placeholder_parser.cpp | 3 + 2 files changed, 102 insertions(+), 17 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 5f8624ad30..6ecb522339 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -249,6 +249,7 @@ namespace client TYPE_STRING, }; Type type() const { return m_type; } + bool numeric_type() const { return m_type == TYPE_INT || m_type == TYPE_DOUBLE; } bool& b() { return m_data.b; } bool b() const { return m_data.b; } @@ -472,8 +473,7 @@ namespace client static void compare_op(expr &lhs, expr &rhs, char op, bool invert) { bool value = false; - if ((lhs.type() == TYPE_INT || lhs.type() == TYPE_DOUBLE) && - (rhs.type() == TYPE_INT || rhs.type() == TYPE_DOUBLE)) { + if (lhs.numeric_type() && rhs.numeric_type()) { // Both types are numeric. switch (op) { case '=': @@ -681,7 +681,7 @@ namespace client void throw_if_not_numeric(const char *message) const { - if (this->type() != TYPE_INT && this->type() != TYPE_DOUBLE) + if (! this->numeric_type()) this->throw_exception(message); } @@ -964,6 +964,10 @@ namespace client { if (! opt.writable) ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); + auto check_numeric = [](const expr ¶m) { + if (! param.numeric_type()) + param.throw_exception("Right side is not a numeric expression"); + }; if (opt.opt->is_vector()) { if (! opt.has_index()) ctx->throw_exception("Referencing an output vector variable when scalar is expected", opt.it_range); @@ -974,21 +978,18 @@ namespace client ctx->throw_exception("Index out of range", opt.it_range); switch (opt.opt->type()) { case coFloats: - if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) - ctx->throw_exception("Right side is not a numeric expression", param.it_range); + check_numeric(param); static_cast(vec)->values[opt.index] = param.as_d(); break; case coInts: - if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) - ctx->throw_exception("Right side is not a numeric expression", param.it_range); + check_numeric(param); static_cast(vec)->values[opt.index] = param.as_i(); break; case coStrings: static_cast(vec)->values[opt.index] = param.to_string(); break; case coPercents: - if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) - ctx->throw_exception("Right side is not a numeric expression", param.it_range); + check_numeric(param); static_cast(vec)->values[opt.index] = param.as_d(); break; case coBools: @@ -1004,21 +1005,18 @@ namespace client ConfigOption *wropt = const_cast(opt.opt); switch (wropt->type()) { case coFloat: - if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) - ctx->throw_exception("Right side is not a numeric expression", param.it_range); + check_numeric(param); static_cast(wropt)->value = param.as_d(); break; case coInt: - if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) - ctx->throw_exception("Right side is not a numeric expression", param.it_range); + check_numeric(param); static_cast(wropt)->value = param.as_i(); break; case coString: static_cast(wropt)->value = param.to_string(); break; case coPercent: - if (param.type() != expr::TYPE_INT && param.type() != expr::TYPE_DOUBLE) - ctx->throw_exception("Right side is not a numeric expression", param.it_range); + check_numeric(param); static_cast(wropt)->value = param.as_d(); break; case coBool: @@ -1109,6 +1107,72 @@ namespace client } }; + template + struct InterpolateTableContext { + template + struct Item { + double x; + boost::iterator_range it_range_x; + double y; + }; + std::vector> table; + + static void init(const expr &x) { + if (!x.numeric_type()) + x.throw_exception("Interpolation value must be a number."); + } + static void add_pair(const expr &x, const expr &y, InterpolateTableContext &table) { + if (! x.numeric_type()) + x.throw_exception("X value of a table point must be a number."); + if (! y.numeric_type()) + y.throw_exception("Y value of a table point must be a number."); + table.table.push_back({ x.as_d(), x.it_range, y.as_d() }); + } + static void evaluate(const expr &expr_x, const InterpolateTableContext &table, expr &out) { + // Check whether the table X values are sorted. + double x = expr_x.as_d(); + bool evaluated = false; + for (size_t i = 1; i < table.table.size(); ++i) { + double x0 = table.table[i - 1].x; + double x1 = table.table[i].x; + if (x0 > x1) + boost::throw_exception(qi::expectation_failure( + table.table[i - 1].it_range_x.begin(), table.table[i].it_range_x.end(), spirit::info("X coordinates of the table must be increasing"))); + if (! evaluated && x >= x0 && x <= x1) { + double y0 = table.table[i - 1].y; + double y1 = table.table[i].y; + if (x == x0) + out.set_d(y0); + else if (x == x1) + out.set_d(y1); + else if (is_approx(x0, x1)) + out.set_d(0.5 * (y0 + y1)); + else + out.set_d(Slic3r::lerp(y0, y1, (x - x0) / (x1 - x0))); + evaluated = true; + } + } + if (! evaluated) { + // Clamp x into the table range with EPSILON. + if (x > table.table.front().x - EPSILON) + out.set_d(table.table.front().y); + else if (x < table.table.back().x + EPSILON) + out.set_d(table.table.back().y); + else + // The value is really outside the table range. + expr_x.throw_exception("Interpolation value is outside the table range"); + } + } + }; + + template + std::ostream& operator<<(std::ostream &os, const InterpolateTableContext &table_context) + { + for (const auto &item : table_context.table) + os << "(" << item.x << "," << item.y << ")"; + return os; + } + // Table to translate symbol tag to a human readable error message. std::map MyContext::tag_to_error_message = { { "eoi", "Unknown syntax error" }, @@ -1428,6 +1492,7 @@ namespace client | (kw["round"] > '(' > conditional_expression(_r1) > ')') [ px::bind(&FactorActions::round, _1, _val) ] | (kw["is_nil"] > '(' > is_nil_test(_r1) > ')') [ _val = _1 ] | (kw["one_of"] > '(' > one_of(_r1) > ')') [ _val = _1 ] + | (kw["interpolate_table"] > '(' > interpolate_table(_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) ] @@ -1440,16 +1505,26 @@ namespace client one_of.name("one_of"); one_of_list = eps[px::bind(&expr::one_of_test_init, _val)] > - ( ',' > *( + ( ( ',' > *( ( unary_expression(_r1)[px::bind(&expr::template one_of_test, _r2, _1, _val)] | (lit('~') > unary_expression(_r1))[px::bind(&expr::template one_of_test, _r2, _1, _val)] | regular_expression[px::bind(&expr::one_of_test_regex, _r2, _1, _val)] ) >> -lit(',')) - | eps + ) + | eps ); one_of_list.name("one_of_list"); + interpolate_table = (unary_expression(_r1)[_a = _1] > ',' > interpolate_table_list(_r1, _a)) + [px::bind(&InterpolateTableContext::evaluate, _a, _2, _val)]; + interpolate_table.name("interpolate_table"); + interpolate_table_list = + eps[px::bind(&InterpolateTableContext::init, _r2)] > + ( *(( lit('(') > unary_expression(_r1) > ',' > unary_expression(_r1) > ')' ) + [px::bind(&InterpolateTableContext::add_pair, _1, _2, _val)] >> -lit(',')) ); + interpolate_table.name("interpolate_table_list"); + optional_parameter = iter_pos[px::bind(&FactorActions::set_start_pos, _1, _val)] >> ( lit(')') [ px::bind(&FactorActions::noexpr, _val) ] | (lit(',') > conditional_expression(_r1) > ')') [ _val = _1 ] @@ -1486,6 +1561,7 @@ namespace client ("elsif") ("endif") ("false") + ("interpolate_table") ("min") ("max") ("random") @@ -1501,9 +1577,12 @@ namespace client debug(text_block); debug(macro); debug(if_else_output); + debug(interpolate_table); // debug(switch_output); debug(legacy_variable_expansion); debug(identifier); + debug(interpolate_table); + debug(interpolate_table_list); debug(conditional_expression); debug(logical_or_expression); debug(logical_and_expression); @@ -1569,6 +1648,9 @@ namespace client // Evaluating "one of" list of patterns. qi::rule(const MyContext*), qi::locals>, spirit_encoding::space_type> one_of; qi::rule(const MyContext*, const expr ¶m), spirit_encoding::space_type> one_of_list; + // Evaluating the "interpolate_table" expression. + qi::rule(const MyContext*), qi::locals>, spirit_encoding::space_type> interpolate_table; + qi::rule(const MyContext*, const expr ¶m), spirit_encoding::space_type> interpolate_table_list; qi::rule, spirit_encoding::space_type> if_else_output; qi::rule, int>, spirit_encoding::space_type> assignment_statement; diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index e40657d160..320b004aea 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -71,6 +71,9 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { 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"); } + SECTION("math: interpolate_table(13.84375892476, (0, 0), (20, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13.84375892476, (0, 0), (20, 20))}")) == Approx(13.84375892476)); } + SECTION("math: interpolate_table(13, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(13, (0, 0), (20, 20), (30, 20))}")) == Approx(13.)); } + SECTION("math: interpolate_table(25, (0, 0), (20, 20), (30, 20))") { REQUIRE(std::stod(parser.process("{interpolate_table(25, (0, 0), (20, 20), (30, 20))}")) == Approx(20.)); } // Test the "coFloatOrPercent" and "xxx_extrusion_width" substitutions. // first_layer_extrusion_width ratio_over first_layer_heigth. From c28585ab7fa62a56a1f188284b8bf782b734003f Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Wed, 22 Mar 2023 17:46:47 +0100 Subject: [PATCH 4/5] WIP PlaceholderParser: Support for local and global variables. Implements #4048 #7196 Syntax: (global|local) variable_name = (scalar_expression|vector_variable|array_expr|initializer_list) array_expr := array(repeat, value) initializer_list := (value, value, value, ...) The type of the newly created variable is defined by the type of the right hand side intitializer. Newly declared variable must not override an existing variable. Variable may be assigned with global|local expression, but its type must not be changed. Newly the assignment operator also accepts the same right hand expressions as the global|local variable definition. --- src/libslic3r/GCode.cpp | 2 + src/libslic3r/PlaceholderParser.cpp | 538 +++++++++++++++++++- src/libslic3r/PlaceholderParser.hpp | 5 +- tests/libslic3r/test_placeholder_parser.cpp | 77 +++ 4 files changed, 602 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index e841a5c437..65549d2e8b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1086,6 +1086,8 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato m_placeholder_parser = print.placeholder_parser(); m_placeholder_parser.update_timestamp(); m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + // Enable passing global variables between PlaceholderParser invocations. + m_placeholder_parser_context.global_config = std::make_unique(); print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode"); // Get optimal tool ordering to minimize tool switches of a multi-exruder print. diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 6ecb522339..78ded40c4a 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -191,6 +191,10 @@ namespace client struct expr { expr() {} + expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range) + { if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); } + expr(expr &&rhs) : expr(std::move(rhs), rhs.it_range.begin(), rhs.it_range.end()) {} + explicit expr(bool b) : m_type(TYPE_BOOL) { m_data.b = b; } explicit expr(bool b, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_BOOL), it_range(it_begin, it_end) { m_data.b = b; } explicit expr(int i) : m_type(TYPE_INT) { m_data.i = i; } @@ -201,11 +205,8 @@ namespace client explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } explicit expr(const std::string &s, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_STRING), it_range(it_begin, it_end) { m_data.s = new std::string(s); } - expr(const expr &rhs) : m_type(rhs.type()), it_range(rhs.it_range) - { if (rhs.type() == TYPE_STRING) m_data.s = new std::string(*rhs.m_data.s); else m_data.set(rhs.m_data); } - explicit expr(expr &&rhs) : expr(rhs, rhs.it_range.begin(), rhs.it_range.end()) {} explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end } - { + { m_data.set(rhs.m_data); rhs.m_type = TYPE_EMPTY; } @@ -723,7 +724,10 @@ namespace client const DynamicConfig *config = nullptr; const DynamicConfig *config_override = nullptr; mutable DynamicConfig *config_outputs = nullptr; + // Local variables, read / write + mutable DynamicConfig config_local; size_t current_extruder_id = 0; + // Random number generator and optionally global variables. PlaceholderParser::ContextData *context_data = nullptr; // If false, the macro_processor will evaluate a full macro. // If true, the macro processor will evaluate just a boolean condition using the full expressive power of the macro processor. @@ -748,7 +752,24 @@ namespace client } const ConfigOption* resolve_symbol(const std::string &opt_key) const { return this->optptr(opt_key); } - ConfigOption* resolve_output_symbol(const std::string &opt_key) const { return this->config_outputs ? this->config_outputs->optptr(opt_key, false) : nullptr; } + ConfigOption* resolve_output_symbol(const std::string &opt_key) const { + ConfigOption *out = nullptr; + if (this->config_outputs) + out = this->config_outputs->optptr(opt_key, false); + if (out == nullptr && this->context_data != nullptr && this->context_data->global_config) + out = this->context_data->global_config->optptr(opt_key); + if (out == nullptr) + out = this->config_local.optptr(opt_key); + return out; + } + void store_new_variable(const std::string &opt_key, ConfigOption *opt, bool global_variable) { + assert(opt != nullptr); + if (global_variable) { + assert(this->context_data != nullptr && this->context_data->global_config); + this->context_data->global_config->set_key_value(opt_key, opt); + } else + this->config_local.set_key_value(opt_key ,opt); + } template static void legacy_variable_expansion( @@ -953,17 +974,101 @@ namespace client output.it_range = opt.it_range; } - // Decoding a scalar variable symbol "opt", assigning it a value of "param". + template + struct NewOldVariable { + std::string name; + boost::iterator_range it_range; + ConfigOption *opt{ nullptr }; + }; template - static void variable_assign( - const MyContext *ctx, - OptWithPos &opt, - expr ¶m, - // Not used, just clear it. - std::string &out) + static void new_old_variable( + const MyContext *ctx, + bool global_variable, + const boost::iterator_range &it_range, + NewOldVariable &out) { + t_config_option_key key(std::string(it_range.begin(), it_range.end())); + if (const ConfigOption* opt = ctx->resolve_symbol(key); opt) + ctx->throw_exception("Symbol is already defined in read-only system dictionary", it_range); + if (ctx->config_outputs && ctx->config_outputs->optptr(key)) + ctx->throw_exception("Symbol is already defined as system output variable", it_range); + + bool has_global_dictionary = ctx->context_data != nullptr && ctx->context_data->global_config; + if (global_variable) { + if (! has_global_dictionary) + ctx->throw_exception("Global variables are not available in this context", it_range); + if (ctx->config_local.optptr(key)) + ctx->throw_exception("Variable name already defined in local scope", it_range); + out.opt = ctx->context_data->global_config->optptr(key); + } else { + if (has_global_dictionary && ctx->context_data->global_config->optptr(key)) + ctx->throw_exception("Variable name already defined in global scope", it_range); + out.opt = ctx->config_local.optptr(key); + } + + out.name = std::move(key); + out.it_range = it_range; + } + + template + static void new_scalar_variable( + const MyContext *ctx, + bool global_variable, + NewOldVariable &output_variable, + const expr ¶m) + { + auto check_numeric = [](const expr ¶m) { + if (! param.numeric_type()) + param.throw_exception("Right side is not a numeric expression"); + }; + if (output_variable.opt) { + if (output_variable.opt->is_vector()) + param.throw_exception("Cannot assign a scalar value to a vector variable."); + switch (output_variable.opt->type()) { + case coFloat: + check_numeric(param); + static_cast(output_variable.opt)->value = param.as_d(); + break; + case coInt: + check_numeric(param); + static_cast(output_variable.opt)->value = param.as_i(); + break; + case coString: + static_cast(output_variable.opt)->value = param.to_string(); + break; + case coBool: + if (param.type() != expr::TYPE_BOOL) + param.throw_exception("Right side is not a boolean expression"); + static_cast(output_variable.opt)->value = param.b(); + break; + default: assert(false); + } + } else { + switch (param.type()) { + case expr::TYPE_BOOL: output_variable.opt = new ConfigOptionBool(param.b()); break; + case expr::TYPE_INT: output_variable.opt = new ConfigOptionInt(param.i()); break; + case expr::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloat(param.d()); break; + case expr::TYPE_STRING: output_variable.opt = new ConfigOptionString(param.s()); break; + default: assert(false); + } + const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); + } + } + + template + static void check_writable(const MyContext *ctx, OptWithPos &opt) { if (! opt.writable) ctx->throw_exception("Cannot modify a read-only variable", opt.it_range); + } + + // Decoding a scalar variable symbol "opt", assigning it a value of "param". + template + static void assign_scalar_variable( + const MyContext *ctx, + OptWithPos &opt, + expr ¶m) + { + check_writable(ctx, opt); auto check_numeric = [](const expr ¶m) { if (! param.numeric_type()) param.throw_exception("Right side is not a numeric expression"); @@ -1028,7 +1133,363 @@ namespace client ctx->throw_exception("Unsupported output scalar variable type", opt.it_range); } } - out.clear(); + } + + template + static void new_vector_variable_array( + const MyContext *ctx, + bool global_variable, + NewOldVariable &output_variable, + const expr &expr_count, + const expr &expr_value) + { + auto check_numeric = [](const expr ¶m) { + if (! param.numeric_type()) + param.throw_exception("Right side is not a numeric expression"); + }; + auto evaluate_count = [](const expr &expr_count) -> size_t { + if (expr_count.type() != expr::TYPE_INT) + expr_count.throw_exception("Expected number of elements to fill a vector with."); + int count = expr_count.i(); + if (count < 0) + expr_count.throw_exception("Negative number of elements specified."); + return size_t(count); + }; + if (output_variable.opt) { + if (output_variable.opt->is_scalar()) + expr_value.throw_exception("Cannot assign a vector value to a scalar variable."); + size_t count = evaluate_count(expr_count); + switch (output_variable.opt->type()) { + case coFloats: + check_numeric(expr_value); + static_cast(output_variable.opt)->values.assign(count, expr_value.as_d()); + break; + case coInts: + check_numeric(expr_value); + static_cast(output_variable.opt)->values.assign(count, expr_value.as_i()); + break; + case coStrings: + static_cast(output_variable.opt)->values.assign(count, expr_value.to_string()); + break; + case coBools: + if (expr_value.type() != expr::TYPE_BOOL) + expr_value.throw_exception("Right side is not a boolean expression"); + static_cast(output_variable.opt)->values.assign(count, expr_value.b()); + break; + default: assert(false); + } + } else { + size_t count = evaluate_count(expr_count); + switch (expr_value.type()) { + case expr::TYPE_BOOL: output_variable.opt = new ConfigOptionBools(count, expr_value.b()); break; + case expr::TYPE_INT: output_variable.opt = new ConfigOptionInts(count, expr_value.i()); break; + case expr::TYPE_DOUBLE: output_variable.opt = new ConfigOptionFloats(count, expr_value.d()); break; + case expr::TYPE_STRING: output_variable.opt = new ConfigOptionStrings(count, expr_value.s()); break; + default: assert(false); + } + const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); + } + } + + template + static void assign_vector_variable_array( + const MyContext *ctx, + OptWithPos &lhs, + const expr &expr_count, + const expr &expr_value) + { + check_writable(ctx, lhs); + auto check_numeric = [](const expr ¶m) { + if (! param.numeric_type()) + param.throw_exception("Right side is not a numeric expression"); + }; + auto evaluate_count = [](const expr &expr_count) -> size_t { + if (expr_count.type() != expr::TYPE_INT) + expr_count.throw_exception("Expected number of elements to fill a vector with."); + int count = expr_count.i(); + if (count < 0) + expr_count.throw_exception("Negative number of elements specified."); + return size_t(count); + }; + if (lhs.opt->is_scalar()) + expr_value.throw_exception("Cannot assign a vector value to a scalar variable."); + auto *opt = const_cast(lhs.opt); + size_t count = evaluate_count(expr_count); + switch (lhs.opt->type()) { + case coFloats: + check_numeric(expr_value); + static_cast(opt)->values.assign(count, expr_value.as_d()); + break; + case coInts: + check_numeric(expr_value); + static_cast(opt)->values.assign(count, expr_value.as_i()); + break; + case coStrings: + static_cast(opt)->values.assign(count, expr_value.to_string()); + break; + case coBools: + if (expr_value.type() != expr::TYPE_BOOL) + expr_value.throw_exception("Right side is not a boolean expression"); + static_cast(opt)->values.assign(count, expr_value.b()); + break; + default: assert(false); + } + } + + template + static void new_vector_variable_initializer_list( + const MyContext *ctx, + bool global_variable, + NewOldVariable &output_variable, + const std::vector> &il) + { + if (! output_variable.opt) { + // First guesstimate type of the output vector. + size_t num_bool = 0; + size_t num_int = 0; + size_t num_double = 0; + size_t num_string = 0; + for (auto &i : il) + switch (i.type()) { + case expr::TYPE_BOOL: ++ num_bool; break; + case expr::TYPE_INT: ++ num_int; break; + case expr::TYPE_DOUBLE: ++ num_double; break; + case expr::TYPE_STRING: ++ num_string; break; + default: assert(false); + } + if (num_string > 0) + // Convert everything to strings. + output_variable.opt = new ConfigOptionStrings(); + else if (num_bool > 0) { + if (num_double + num_int > 0) + ctx->throw_exception("Right side is not valid: Mixing numeric and boolean types.", boost::iterator_range{ il.front().it_range.begin(), il.back().it_range.end() }); + output_variable.opt = new ConfigOptionBools(); + } else + // Output is numeric. + output_variable.opt = num_double == 0 ? static_cast(new ConfigOptionInts()) : static_cast(new ConfigOptionFloats()); + const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); + } + + auto check_numeric = [](const std::vector> &il) { + for (auto& i : il) + if (!i.numeric_type()) + i.throw_exception("Right side is not a numeric expression"); + }; + + if (output_variable.opt->is_scalar()) + ctx->throw_exception("Cannot assign a vector value to a scalar variable.", output_variable.it_range); + + switch (output_variable.opt->type()) { + case coFloats: + { + check_numeric(il); + auto &out = static_cast(output_variable.opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto &i : il) + out.emplace_back(i.as_d()); + break; + } + case coInts: + { + check_numeric(il); + auto &out = static_cast(output_variable.opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto& i : il) + out.emplace_back(i.as_i()); + break; + } + case coStrings: + { + auto &out = static_cast(output_variable.opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto &i : il) + out.emplace_back(i.to_string()); + break; + } + case coBools: + { + auto &out = static_cast(output_variable.opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto &i : il) + if (i.type() == expr::TYPE_BOOL) + out.emplace_back(i.b()); + else + i.throw_exception("Right side is not a boolean expression"); + break; + } + default: + assert(false); + } + } + + template + static void assign_vector_variable_initializer_list( + const MyContext *ctx, + OptWithPos &lhs, + const std::vector> &il) + { + check_writable(ctx, lhs); + auto check_numeric = [](const std::vector> &il) { + for (auto &i : il) + if (! i.numeric_type()) + i.throw_exception("Right side is not a numeric expression"); + }; + + if (lhs.opt->is_scalar()) + ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range); + + ConfigOption *opt = const_cast(lhs.opt); + switch (lhs.opt->type()) { + case coFloats: + { + check_numeric(il); + auto &out = static_cast(opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto &i : il) + out.emplace_back(i.as_d()); + break; + } + case coInts: + { + check_numeric(il); + auto &out = static_cast(opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto& i : il) + out.emplace_back(i.as_i()); + break; + } + case coStrings: + { + auto &out = static_cast(opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto &i : il) + out.emplace_back(i.to_string()); + break; + } + case coBools: + { + auto &out = static_cast(opt)->values; + out.clear(); + out.reserve(il.size()); + for (auto &i : il) + if (i.type() == expr::TYPE_BOOL) + out.emplace_back(i.b()); + else + i.throw_exception("Right side is not a boolean expression"); + break; + } + default: + assert(false); + } + } + + template + static bool new_vector_variable_copy( + const MyContext *ctx, + bool global_variable, + NewOldVariable &output_variable, + const OptWithPos &src_variable) + { + if (! is_vector_variable_reference(src_variable)) + // Skip parsing this branch, bactrack. + return false; + + if (! output_variable.opt) { + if (one_of(src_variable.opt->type(), { coFloats, coInts, coStrings, coBools })) + output_variable.opt = src_variable.opt->clone(); + else if (src_variable.opt->type() == coPercents) + output_variable.opt = new ConfigOptionFloats(static_cast(src_variable.opt)->values); + else + ctx->throw_exception("Duplicating this vector variable is not supported", src_variable.it_range); + const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); + } + + switch (output_variable.opt->type()) { + case coFloats: + if (output_variable.opt->type() != coFloats) + ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); + static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; + break; + case coInts: + if (output_variable.opt->type() != coInts) + ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); + static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; + break; + case coStrings: + if (output_variable.opt->type() != coStrings) + ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); + static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; + break; + case coBools: + if (output_variable.opt->type() != coBools) + ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", boost::iterator_range{ output_variable.it_range.begin(), src_variable.it_range.end() }); + static_cast(output_variable.opt)->values = static_cast(src_variable.opt)->values; + break; + default: + assert(false); + } + // Continue parsing. + return true; + } + + template + static bool is_vector_variable_reference(const OptWithPos &var) { + return ! var.has_index() && var.opt->is_vector(); + } + + template + static bool assign_vector_variable_copy( + const MyContext *ctx, + OptWithPos &lhs, + const OptWithPos &src_variable) + { + if (! is_vector_variable_reference(src_variable)) + // Skip parsing this branch, bactrack. + return false; + + check_writable(ctx, lhs); + + auto *opt = const_cast(lhs.opt); + switch (lhs.opt->type()) { + case coFloats: + if (lhs.opt->type() != coFloats) + ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", lhs.it_range); + static_cast(opt)->values = static_cast(src_variable.opt)->values; + break; + case coInts: + if (lhs.opt->type() != coInts) + ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", lhs.it_range); + static_cast(opt)->values = static_cast(src_variable.opt)->values; + break; + case coStrings: + if (lhs.opt->type() != coStrings) + ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", lhs.it_range); + static_cast(opt)->values = static_cast(src_variable.opt)->values; + break; + case coBools: + if (lhs.opt->type() != coBools) + ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", lhs.it_range); + static_cast(opt)->values = static_cast(src_variable.opt)->values; + break; + default: + assert(false); + } + + // Continue parsing. + return true; + } + + template + static void new_vector_variable_initializer_list_append(std::vector> &list, expr &expr) + { + list.emplace_back(std::move(expr)); } // Verify that the expression returns an integer, which may be used @@ -1175,6 +1636,7 @@ namespace client // Table to translate symbol tag to a human readable error message. std::map MyContext::tag_to_error_message = { + { "array", "Unknown syntax error" }, { "eoi", "Unknown syntax error" }, { "start", "Unknown syntax error" }, { "text", "Invalid text." }, @@ -1335,7 +1797,7 @@ namespace client // 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('{') >> macro(_r1)[_val+=_1] > *(+lit(';') >> macro(_r1)[_val+=_1]) > *lit(';') > '}') | (lit('[') > legacy_variable_expansion(_r1) [_val+=_1] > ']') ); text_block.name("text_block"); @@ -1351,6 +1813,7 @@ namespace client (kw["if"] > if_else_output(_r1) [_val = _1]) // | (kw["switch"] > switch_output(_r1) [_val = _1]) | (assignment_statement(_r1) [_val = _1]) + | (new_variable_statement(_r1) [_val = _1]) | (additive_expression(_r1) [ px::bind(&expr::to_string2, _1, _val) ]) ; macro.name("macro"); @@ -1445,8 +1908,39 @@ namespace client multiplicative_expression.name("multiplicative_expression"); assignment_statement = - (variable_reference(_r1) >> '=' > additive_expression(_r1)) - [px::bind(&MyContext::variable_assign, _r1, _1, _2, _val)]; + variable_reference(_r1)[_a = _1] >> '=' > + ( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer. + (lit('(') > new_variable_initializer_list(_r1) > ')') + [px::bind(&MyContext::assign_vector_variable_initializer_list, _r1, _a, _1)] + // Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index. + // Only process such variable references, which return a naked vector variable. + | variable_reference(_r1) + [px::ref(qi::_pass) = px::bind(&MyContext::assign_vector_variable_copy, _r1, _a, _1)] + // Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above. + | conditional_expression(_r1) + [px::bind(&MyContext::assign_scalar_variable, _r1, _a, _1)] + | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") + [px::bind(&MyContext::assign_vector_variable_array, _r1, _a, _1, _2)] + ); + + new_variable_statement = + (kw["local"][_a = false] | kw["global"][_a = true]) > identifier[px::bind(&MyContext::new_old_variable, _r1, _a, _1, _b)] > lit('=') > + ( // Consumes also '(' conditional_expression ')', that means enclosing an expression into braces makes it a single value vector initializer. + (lit('(') > new_variable_initializer_list(_r1) > ')') + [px::bind(&MyContext::new_vector_variable_initializer_list, _r1, _a, _b, _1)] + // Process it before conditional_expression, as conditional_expression requires a vector reference to be augmented with an index. + // Only process such variable references, which return a naked vector variable. + | variable_reference(_r1) + [px::ref(qi::_pass) = px::bind(&MyContext::new_vector_variable_copy, _r1, _a, _b, _1)] + // Would NOT consume '(' conditional_expression ')' because such value was consumed with the expression above. + | conditional_expression(_r1) + [px::bind(&MyContext::new_scalar_variable, _r1, _a, _b, _1)] + | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") + [px::bind(&MyContext::new_vector_variable_array, _r1, _a, _b, _1, _2)] + ); + new_variable_initializer_list = + conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append, _val, _1)] >> + *(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::new_vector_variable_initializer_list_append, _val, _1)]); struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) @@ -1544,23 +2038,26 @@ namespace client variable_reference.name("variable reference"); variable = identifier[ px::bind(&MyContext::resolve_variable, _r1, _1, _val) ]; - variable.name("variable reference"); + variable.name("variable name"); regular_expression = raw[lexeme['/' > *((utf8char - char_('\\') - char_('/')) | ('\\' > char_)) > '/']]; regular_expression.name("regular_expression"); keywords.add ("and") + ("array") ("digits") ("zdigits") ("if") ("int") ("is_nil") + ("local") //("inf") ("else") ("elsif") ("endif") ("false") + ("global") ("interpolate_table") ("min") ("max") @@ -1653,9 +2150,12 @@ namespace client qi::rule(const MyContext*, const expr ¶m), spirit_encoding::space_type> interpolate_table_list; qi::rule, spirit_encoding::space_type> if_else_output; - qi::rule, int>, spirit_encoding::space_type> assignment_statement; -// qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; + qi::rule>, spirit_encoding::space_type> assignment_statement; + // Allocating new local or global variables. + qi::rule>, spirit_encoding::space_type> new_variable_statement; + qi::rule>(const MyContext*), spirit_encoding::space_type> new_variable_initializer_list; +// qi::rule, bool, std::string>, spirit_encoding::space_type> switch_output; qi::symbols keywords; }; } diff --git a/src/libslic3r/PlaceholderParser.hpp b/src/libslic3r/PlaceholderParser.hpp index a3f0515586..39c33206c6 100644 --- a/src/libslic3r/PlaceholderParser.hpp +++ b/src/libslic3r/PlaceholderParser.hpp @@ -20,7 +20,10 @@ public: // In the future, the context may hold variables created and modified by the PlaceholderParser // and shared between the PlaceholderParser::process() invocations. struct ContextData { - std::mt19937 rng; + std::mt19937 rng; + // If defined, then this dictionary is used by the scripts to define user variables and persist them + // between PlaceholderParser evaluations. + std::unique_ptr global_config; }; PlaceholderParser(const DynamicConfig *external_config = nullptr); diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index 320b004aea..ee1461baf8 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -39,6 +39,10 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { 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"); } + SECTION("multiple expressions") { REQUIRE(parser.process("{temperature[foo];temperature[foo]}") == "357357"); } + SECTION("multiple expressions with semicolons") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];}") == "357357"); } + SECTION("multiple expressions with semicolons 2") { REQUIRE(parser.process("{temperature[foo];;temperature[foo];}") == "357357"); } + SECTION("multiple expressions with semicolons 3") { REQUIRE(parser.process("{temperature[foo];;;temperature[foo];;}") == "357357"); } // Test the math expressions. SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } @@ -146,3 +150,76 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { REQUIRE(config_outputs.opt_float("writable_floats", 1) == Approx(33.)); } } + +SCENARIO("Placeholder parser variables", "[PlaceholderParser]") { + PlaceholderParser parser; + auto config = DynamicPrintConfig::full_print_config(); + + config.set_deserialize_strict({ + { "filament_notes", "testnotes" }, + { "enable_dynamic_fan_speeds", "1" }, + { "nozzle_diameter", "0.6;0.6;0.6;0.6" }, + { "temperature", "357;359;363;378" } + }); + + PlaceholderParser::ContextData context_with_global_dict; + context_with_global_dict.global_config = std::make_unique(); + + SECTION("create an int local variable") { REQUIRE(parser.process("{local myint = 33+2}{myint}", 0, nullptr, nullptr, nullptr) == "35"); } + SECTION("create a string local variable") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{mystr}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } + SECTION("create a bool local variable") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{mybool}", 0, nullptr, nullptr, nullptr) == "true"); } + SECTION("create an int global variable") { REQUIRE(parser.process("{global myint = 33+2}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "35"); } + SECTION("create a string global variable") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); } + SECTION("create a bool global variable") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); } + + SECTION("create an int local variable and overwrite it") { REQUIRE(parser.process("{local myint = 33+2}{myint = 12}{myint}", 0, nullptr, nullptr, nullptr) == "12"); } + SECTION("create a string local variable and overwrite it") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, nullptr) == "yours"); } + SECTION("create a bool local variable and overwrite it") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{mybool = false}{mybool}", 0, nullptr, nullptr, nullptr) == "false"); } + SECTION("create an int global variable and overwrite it") { REQUIRE(parser.process("{global myint = 33+2}{myint = 12}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "12"); } + SECTION("create a string global variable and overwrite it") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "yours"); } + SECTION("create a bool global variable and overwrite it") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{mybool = false}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "false"); } + + SECTION("create an int local variable and redefine it") { REQUIRE(parser.process("{local myint = 33+2}{local myint = 12}{myint}", 0, nullptr, nullptr, nullptr) == "12"); } + SECTION("create a string local variable and redefine it") { REQUIRE(parser.process("{local mystr = \"mine\" + \"only\" + \"mine\"}{local mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, nullptr) == "yours"); } + SECTION("create a bool local variable and redefine it") { REQUIRE(parser.process("{local mybool = 1 + 1 == 2}{local mybool = false}{mybool}", 0, nullptr, nullptr, nullptr) == "false"); } + SECTION("create an int global variable and redefine it") { REQUIRE(parser.process("{global myint = 33+2}{global myint = 12}{myint}", 0, nullptr, nullptr, &context_with_global_dict) == "12"); } + SECTION("create a string global variable and redefine it") { REQUIRE(parser.process("{global mystr = \"mine\" + \"only\" + \"mine\"}{global mystr = \"yours\"}{mystr}", 0, nullptr, nullptr, &context_with_global_dict) == "yours"); } + SECTION("create a bool global variable and redefine it") { REQUIRE(parser.process("{global mybool = 1 + 1 == 2}{global mybool = false}{mybool}", 0, nullptr, nullptr, &context_with_global_dict) == "false"); } + + SECTION("create an ints local variable with array()") { REQUIRE(parser.process("{local myint = array(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, nullptr) == "24"); } + SECTION("create a strings local variable array()") { REQUIRE(parser.process("{local mystr = array(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } + SECTION("create a bools local variable array()") { REQUIRE(parser.process("{local mybool = array(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, nullptr) == "true"); } + SECTION("create an ints global variable array()") { REQUIRE(parser.process("{global myint = array(2*3, 4*6)}{myint[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); } + SECTION("create a strings global variable array()") { REQUIRE(parser.process("{global mystr = array(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); } + SECTION("create a bools global variable array()") { REQUIRE(parser.process("{global mybool = array(5, 1 + 1 == 2)}{mybool[4]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); } + + SECTION("create an ints local variable with initializer list") { REQUIRE(parser.process("{local myint = (2*3, 4*6, 5*5)}{myint[1]}", 0, nullptr, nullptr, nullptr) == "24"); } + SECTION("create a strings local variable with initializer list") { REQUIRE(parser.process("{local mystr = (2*3, \"mine\" + \"only\" + \"mine\", 8)}{mystr[1]}", 0, nullptr, nullptr, nullptr) == "mineonlymine"); } + SECTION("create a bools local variable with initializer list") { REQUIRE(parser.process("{local mybool = (3*3 == 8, 1 + 1 == 2)}{mybool[1]}", 0, nullptr, nullptr, nullptr) == "true"); } + SECTION("create an ints global variable with initializer list") { REQUIRE(parser.process("{global myint = (2*3, 4*6, 5*5)}{myint[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "24"); } + SECTION("create a strings global variable with initializer list") { REQUIRE(parser.process("{global mystr = (2*3, \"mine\" + \"only\" + \"mine\", 8)}{mystr[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "mineonlymine"); } + SECTION("create a bools global variable with initializer list") { REQUIRE(parser.process("{global mybool = (2*3 == 8, 1 + 1 == 2, 5*5 != 33)}{mybool[1]}", 0, nullptr, nullptr, &context_with_global_dict) == "true"); } + + SECTION("create an ints local variable by a copy") { REQUIRE(parser.process("{local myint = temperature}{myint[0]}", 0, &config, nullptr, nullptr) == "357"); } + SECTION("create a strings local variable by a copy") { REQUIRE(parser.process("{local mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, nullptr) == "testnotes"); } + SECTION("create a bools local variable by a copy") { REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); } + SECTION("create an ints global variable by a copy") { REQUIRE(parser.process("{global myint = temperature}{myint[0]}", 0, &config, nullptr, &context_with_global_dict) == "357"); } + SECTION("create a strings global variable by a copy") { REQUIRE(parser.process("{global mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, &context_with_global_dict) == "testnotes"); } + SECTION("create a bools global variable by a copy") { REQUIRE(parser.process("{global mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, &context_with_global_dict) == "true"); } + + SECTION("create an ints local variable by a copy and overwrite it") { + REQUIRE(parser.process("{local myint = temperature}{myint = array(2*3, 4*6)}{myint[5]}", 0, &config, nullptr, nullptr) == "24"); + REQUIRE(parser.process("{local myint = temperature}{myint = (2*3, 4*6)}{myint[1]}", 0, &config, nullptr, nullptr) == "24"); + REQUIRE(parser.process("{local myint = temperature}{myint = (1)}{myint = temperature}{myint[0]}", 0, &config, nullptr, nullptr) == "357"); + } + SECTION("create a strings local variable by a copy and overwrite it") { + REQUIRE(parser.process("{local mystr = filament_notes}{mystr = array(2*3, \"mine\" + \"only\" + \"mine\")}{mystr[5]}", 0, &config, nullptr, nullptr) == "mineonlymine"); + REQUIRE(parser.process("{local mystr = filament_notes}{mystr = (2*3, \"mine\" + \"only\" + \"mine\")}{mystr[1]}", 0, &config, nullptr, nullptr) == "mineonlymine"); + REQUIRE(parser.process("{local mystr = filament_notes}{mystr = (2*3, \"mine\" + \"only\" + \"mine\")}{mystr = filament_notes}{mystr[0]}", 0, &config, nullptr, nullptr) == "testnotes"); + } + SECTION("create a bools local variable by a copy and overwrite it") { + REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = array(2*3, true)}{mybool[5]}", 0, &config, nullptr, nullptr) == "true"); + REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = (false, true)}{mybool[1]}", 0, &config, nullptr, nullptr) == "true"); + REQUIRE(parser.process("{local mybool = enable_dynamic_fan_speeds}{mybool = (false, false)}{mybool = enable_dynamic_fan_speeds}{mybool[0]}", 0, &config, nullptr, nullptr) == "true"); + } +} From e46891fa8c0fffeb4c33097b4c477a05ca9bb8c0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 23 Mar 2023 09:23:20 +0100 Subject: [PATCH 5/5] PlaceholderParser: Fixed compilation issues, added integration test with FDM slicing process. --- src/libslic3r/PlaceholderParser.cpp | 9 ++++----- tests/fff_print/test_custom_gcode.cpp | 12 ++++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 78ded40c4a..55e7235ebb 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -737,7 +737,7 @@ namespace client // Table to translate symbol tag to a human readable error message. static std::map tag_to_error_message; - static void evaluate_full_macro(const MyContext *ctx, bool &result) { result = ! ctx->just_boolean_expression; } + static bool evaluate_full_macro(const MyContext *ctx) { return ! ctx->just_boolean_expression; } const ConfigOption* optptr(const t_config_option_key &opt_key) const override { @@ -1570,13 +1570,12 @@ namespace client template struct InterpolateTableContext { - template struct Item { double x; boost::iterator_range it_range_x; double y; }; - std::vector> table; + std::vector table; static void init(const expr &x) { if (!x.numeric_type()) @@ -1785,8 +1784,8 @@ namespace client // Also the start symbol switches between the "full macro syntax" and a "boolean expression only", // depending on the context->just_boolean_expression flag. This way a single static expression parser // could serve both purposes. - start = eps[px::bind(&MyContext::evaluate_full_macro, _r1, _a)] > - ( (eps(_a==true) > text_block(_r1) [_val=_1]) + start = + ( (eps(px::bind(&MyContext::evaluate_full_macro, _r1)) > text_block(_r1) [_val=_1]) | conditional_expression(_r1) [ px::bind(&expr::evaluate_boolean_to_string, _1, _val) ] ) > eoi; start.name("start"); diff --git a/tests/fff_print/test_custom_gcode.cpp b/tests/fff_print/test_custom_gcode.cpp index 37103b316d..0d109070bf 100644 --- a/tests/fff_print/test_custom_gcode.cpp +++ b/tests/fff_print/test_custom_gcode.cpp @@ -258,4 +258,16 @@ SCENARIO("Custom G-code", "[CustomGCode]") REQUIRE(match_count == 2); } } + GIVEN("before_layer_gcode increments global variable") { + auto config = Slic3r::DynamicPrintConfig::new_with({ + { "start_gcode", "{global counter=0}" }, + { "before_layer_gcode", ";Counter{counter=counter+1;counter}\n" } + }); + std::string gcode = Slic3r::Test::slice({ Slic3r::Test::TestMesh::cube_20x20x20 }, config); + THEN("The counter is emitted multiple times before layer change.") { + REQUIRE(Slic3r::Test::contains(gcode, ";Counter1\n")); + REQUIRE(Slic3r::Test::contains(gcode, ";Counter2\n")); + REQUIRE(Slic3r::Test::contains(gcode, ";Counter3\n")); + } + } }