diff --git a/resources/profiles/Voron.idx b/resources/profiles/Voron.idx index a169eb751e..7c519c08c2 100644 --- a/resources/profiles/Voron.idx +++ b/resources/profiles/Voron.idx @@ -1,3 +1,5 @@ +min_slic3r_version = 2.6.0-alpha6 +1.0.2 Updated g-code flavor and travel accelerations. min_slic3r_version = 2.4.2 1.0.1 Added 350mm Voron v1 variant. Updated max print heights. Removed redundant v1 volcano nozzle variants. min_slic3r_version = 2.4.0-beta0 diff --git a/resources/profiles/Voron.ini b/resources/profiles/Voron.ini index e34da50100..921fd375e7 100644 --- a/resources/profiles/Voron.ini +++ b/resources/profiles/Voron.ini @@ -7,7 +7,7 @@ name = Voron # Configuration version of this file. Config file will only be installed, if the config_version differs. # This means, the server may force the PrusaSlicer configuration to be downgraded. -config_version = 1.0.1 +config_version = 1.0.2 # Where to get the updates from? config_update_url = https://files.prusa3d.com/wp-content/uploads/repository/PrusaSlicer-settings-master/live/Voron/ @@ -183,7 +183,8 @@ deretract_speed = 25 end_gcode = print_end ;end script from macro extruder_colour = #FFE3CA extruder_offset = 0x0 -gcode_flavor = marlin +gcode_flavor = klipper +autoemit_temperature_commands = 1 layer_gcode = ;AFTER_LAYER_CHANGE\n;[layer_z] machine_max_acceleration_e = 10000 machine_max_acceleration_extruding = 1500 @@ -672,7 +673,8 @@ brim_width = 0 clip_multipart_objects = 1 compatible_printers = complete_objects = 0 -default_acceleration = 3000 +default_acceleration = 2000 +travel_acceleration = 3000 dont_support_bridges = 1 ensure_vertical_shell_thickness = 1 external_perimeters_first = 0 diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 11ffd4a542..1dbf4120ea 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -1,6 +1,7 @@ #include "PlaceholderParser.hpp" #include "Exception.hpp" #include "Flow.hpp" +#include "Utils.hpp" #include #include #include @@ -204,6 +205,7 @@ namespace client explicit expr(double d, const Iterator &it_begin, const Iterator &it_end) : m_type(TYPE_DOUBLE), it_range(it_begin, it_end) { m_data.d = d; } explicit expr(const char *s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } explicit expr(const std::string &s) : m_type(TYPE_STRING) { m_data.s = new std::string(s); } + explicit expr(std::string &&s) : m_type(TYPE_STRING) { m_data.s = new std::string(std::move(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); } explicit expr(expr &&rhs, const Iterator &it_begin, const Iterator &it_end) : m_type(rhs.type()), it_range{ it_begin, it_end } @@ -904,9 +906,12 @@ namespace client const ConfigOption *opt = ctx->resolve_symbol(opt_key_str); if (opt == nullptr) { // Check whether the opt_key ends with '_'. - if (opt_key_str.back() == '_') + if (opt_key_str.back() == '_') { opt_key_str.resize(opt_key_str.size() - 1); - opt = ctx->resolve_symbol(opt_key_str); + opt = ctx->resolve_symbol(opt_key_str); + } + if (opt == nullptr) + ctx->throw_exception("Variable does not exist", opt_key); } if (! opt->is_vector()) ctx->throw_exception("Trying to index a scalar variable", opt_key); @@ -1701,7 +1706,7 @@ namespace client // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. - struct utf8_char_skipper_parser : qi::primitive_parser + struct utf8_char_parser : qi::primitive_parser { // Define the attribute type exposed by this parser component template @@ -1710,9 +1715,10 @@ namespace client typedef wchar_t type; }; - // This function is called during the actual parsing process + // This function is called during the actual parsing process to skip whitespaces. + // Also it throws if it encounters valid or invalid UTF-8 sequence. template - bool parse(Iterator& first, Iterator const& last, Context& context, Skipper const& skipper, Attribute& attr) const + bool parse(Iterator &first, Iterator const &last, Context &context, Skipper const &skipper, Attribute& attr) const { // The skipper shall always be empty, any white space will be accepted. // skip_over(first, last, skipper); @@ -1762,6 +1768,38 @@ namespace client } }; + // This parser is to be used inside a raw[] directive to accept a single valid UTF-8 character. + // If an invalid UTF-8 sequence is encountered, a qi::expectation_failure is thrown. + struct ascii_char_skipper_parser : public utf8_char_parser + { + // This function is called during the actual parsing process + template + bool parse(Iterator &first, Iterator const &last, Context &context, Skipper const &skipper, Attribute &attr) const + { + Iterator it = first; + // Let the UTF-8 parser throw if it encounters an invalid UTF-8 sequence. + if (! utf8_char_parser::parse(it, last, context, skipper, attr)) + return false; + char c = *first; + if (it - first > 1 || c < 0) + MyContext::throw_exception("Non-ASCII7 characters are only allowed inside text blocks and string literals, not inside code blocks.", IteratorRange(first, it)); + if (c == '\r' || c == '\n' || c == '\t' || c == ' ') { + // Skip the whitespaces + ++ first; + return true; + } else + // Stop skipping, let this 7bit ASCII character be processed. + return false; + } + + // This function is called during error handling to create a human readable string for the error context. + template + spirit::info what(Context&) const + { + return spirit::info("ASCII7_char"); + } + }; + struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out) { out.it_range = IteratorRange(start_pos, start_pos); } @@ -1790,8 +1828,49 @@ namespace client if (ctx->skipping()) { out.reset(); out.it_range = it_range; - } else - out = expr(std::string(it_range.begin() + 1, it_range.end() - 1), it_range.begin(), it_range.end()); + } else { + // Unescape the string, UTF-8 safe. + std::string s; + auto begin = std::next(it_range.begin()); + auto end = std::prev(it_range.end()); + assert(begin <= end); + { + // 1) Get the size of the string after unescaping. + size_t len = 0; + for (auto it = begin; it != end;) { + if (*it == '\\') { + if (++ it == end || + (*it != 'r' && *it != 'n' && *it != '"' && *it != '\\')) + ctx->throw_exception("Invalid escape sequence", {std::prev(it), std::next(it) }); + ++ len; + ++ it; + } else { + size_t n = get_utf8_sequence_length(&*it, end - it); + len += n; + it += n; + } + } + // and reserve the string. + s.reserve(len); + } + // 2) Copy & unescape the string. + for (auto it = begin; it != end;) { + if (*it == '\\') { + char c = *(++ it); + if (c == 'r') + c = '\r'; + else if (c == 'n') + c = '\n'; + s += c; + ++ it; + } else { + size_t n = get_utf8_sequence_length(&*it, end - it); + s.append(&*it, n); + it += n; + } + } + out = expr(std::move(s), it_range.begin(), it_range.end()); + } } static void expr_(expr &value, Iterator &end_pos, expr &out) { auto begin_pos = out.it_range.begin(); out = expr(std::move(value), begin_pos, end_pos); } @@ -1807,11 +1886,13 @@ namespace client static void noexpr(expr &out) { out.reset(); } }; + using skipper = ascii_char_skipper_parser; + /////////////////////////////////////////////////////////////////////////// // Our macro_processor grammar /////////////////////////////////////////////////////////////////////////// // Inspired by the C grammar rules https://www.lysator.liu.se/c/ANSI-C-grammar-y.html - struct macro_processor : qi::grammar, spirit_encoding::space_type> + struct macro_processor : qi::grammar, skipper> { macro_processor() : macro_processor::base_type(start) { @@ -1825,7 +1906,7 @@ namespace client qi::no_skip_type no_skip; qi::real_parser strict_double; spirit_encoding::char_type char_; - utf8_char_skipper_parser utf8char; + utf8_char_parser utf8char; spirit::bool_type bool_; spirit::int_type int_; spirit::double_type double_; @@ -2165,22 +2246,22 @@ namespace client } // Generic expression over expr. - typedef qi::rule RuleExpression; + typedef qi::rule RuleExpression; // The start of the grammar. - qi::rule, spirit_encoding::space_type> start; + qi::rule, skipper> start; // A free-form text. - qi::rule text; + qi::rule text; // A free-form text, possibly empty, possibly containing macro expansions. - qi::rule text_block; + qi::rule text_block; // Statements enclosed in curely braces {} - qi::rule block, statement, macros, if_text_block, if_macros, else_macros; + qi::rule block, statement, macros, if_text_block, if_macros, else_macros; // Legacy variable expansion of the original Slic3r, in the form of [scalar_variable] or [vector_variable_index]. - qi::rule legacy_variable_expansion; + qi::rule legacy_variable_expansion; // Parsed identifier name. - qi::rule identifier; + qi::rule identifier; // Ternary operator (?:) over logical_or_expression. - qi::rule, spirit_encoding::space_type> conditional_expression; + qi::rule, skipper> conditional_expression; // Logical or over logical_and_expressions. RuleExpression logical_or_expression; // Logical and over relational_expressions. @@ -2198,27 +2279,27 @@ namespace client // Accepting an optional parameter. RuleExpression optional_parameter; // Rule to capture a regular expression enclosed in //. - qi::rule regular_expression; + qi::rule regular_expression; // Evaluate boolean expression into bool. - qi::rule bool_expr_eval; + qi::rule bool_expr_eval; // Reference of a scalar variable, or reference to a field of a vector variable. - qi::rule, spirit_encoding::space_type> variable_reference; + qi::rule, skipper> variable_reference; // Rule to translate an identifier to a ConfigOption, or to fail. - qi::rule variable; + qi::rule variable; // Evaluating whether a nullable variable is nil. - qi::rule is_nil_test; + qi::rule is_nil_test; // Evaluating "one of" list of patterns. - qi::rule, spirit_encoding::space_type> one_of; - qi::rule one_of_list; + qi::rule, skipper> one_of; + qi::rule one_of_list; // Evaluating the "interpolate_table" expression. - qi::rule, spirit_encoding::space_type> interpolate_table; - qi::rule interpolate_table_list; + qi::rule, skipper> interpolate_table; + qi::rule interpolate_table_list; - qi::rule, spirit_encoding::space_type> if_else_output; - qi::rule, spirit_encoding::space_type> assignment_statement; + qi::rule, skipper> if_else_output; + qi::rule, skipper> 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> initializer_list; + qi::rule, skipper> new_variable_statement; + qi::rule(const MyContext*), skipper> initializer_list; qi::symbols keywords; }; @@ -2229,7 +2310,7 @@ static const client::macro_processor g_macro_processor_instance; static std::string process_macro(const std::string &templ, client::MyContext &context) { std::string output; - phrase_parse(templ.begin(), templ.end(), g_macro_processor_instance(&context), spirit_encoding::space_type{}, output); + phrase_parse(templ.begin(), templ.end(), g_macro_processor_instance(&context), client::skipper{}, output); if (! context.error_message.empty()) { if (context.error_message.back() != '\n' && context.error_message.back() != '\r') context.error_message += '\n'; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index aec104e108..0b06106477 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1509,7 +1509,9 @@ void PrintObject::discover_vertical_shells() Polygons internal_volume; { Polygons shrinked_bottom_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer - 1]->lslices) : Polygons{}; - Polygons shrinked_upper_slice = idx_layer > 0 ? to_polygons(m_layers[idx_layer + 1]->lslices) : Polygons{}; + Polygons shrinked_upper_slice = (idx_layer + 1) < m_layers.size() ? + to_polygons(m_layers[idx_layer + 1]->lslices) : + Polygons{}; internal_volume = intersection(shrinked_bottom_slice, shrinked_upper_slice); } @@ -1739,7 +1741,7 @@ void PrintObject::bridge_over_infill() // cluster layers by depth needed for thick bridges. Each cluster is to be processed by single thread sequentially, so that bridges cannot appear one on another std::vector> clustered_layers_for_threads; - float target_flow_height_factor = 0.75; + float target_flow_height_factor = 0.9; { std::vector layers_with_candidates; std::map layer_area_covered_by_candidates; @@ -1768,7 +1770,7 @@ void PrintObject::bridge_over_infill() if (clustered_layers_for_threads.empty() || this->get_layer(clustered_layers_for_threads.back().back())->print_z < this->get_layer(pair.first)->print_z - - this->get_layer(pair.first)->regions()[0]->flow(frSolidInfill, true).height() * target_flow_height_factor - + this->get_layer(pair.first)->regions()[0]->bridging_flow(frSolidInfill, true).height() * target_flow_height_factor - EPSILON || intersection(layer_area_covered_by_candidates[clustered_layers_for_threads.back().back()], layer_area_covered_by_candidates[pair.first]) @@ -1798,9 +1800,9 @@ void PrintObject::bridge_over_infill() ExPolygons not_sparse_infill{}; double bottom_z = po->get_layer(lidx)->print_z - target_flow_height * target_flow_height_factor - EPSILON; for (int i = int(lidx) - 1; i >= 0; --i) { - // Stop iterating if layer is lower than bottom_z. + // Stop iterating if layer is lower than bottom_z and at least one iteration was made const Layer *layer = po->get_layer(i); - if (layer->print_z < bottom_z) + if (layer->print_z < bottom_z && i < int(lidx) - 1) break; for (const LayerRegion *region : layer->regions()) { @@ -2103,9 +2105,10 @@ void PrintObject::bridge_over_infill() } // Gather deep infill areas, where thick bridges fit - coordf_t spacing = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).scaled_spacing(); - coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->flow(frSolidInfill, true).height() * target_flow_height_factor; - Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); + coordf_t spacing = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).scaled_spacing(); + coordf_t target_flow_height = surfaces_by_layer[lidx].front().region->bridging_flow(frSolidInfill, true).height() * + target_flow_height_factor; + Polygons deep_infill_area = gather_areas_w_depth(po, lidx, target_flow_height); { // Now also remove area that has been already filled on lower layers by bridging expansion - For this @@ -2144,20 +2147,27 @@ void PrintObject::bridge_over_infill() expansion_area = closing(expansion_area, SCALED_EPSILON); expansion_area = intersection(expansion_area, deep_infill_area); Polylines anchors = intersection_pl(infill_lines[lidx - 1], shrink(expansion_area, spacing)); + Polygons internal_unsupported_area = shrink(deep_infill_area, spacing * 4.5); #ifdef DEBUG_BRIDGE_OVER_INFILL debug_draw(std::to_string(lidx) + "_" + std::to_string(cluster_idx) + "_" + std::to_string(job_idx) + "_" + "_total_area", to_lines(total_fill_area), to_lines(expansion_area), to_lines(deep_infill_area), to_lines(anchors)); #endif - std::vector expanded_surfaces; expanded_surfaces.reserve(surfaces_by_layer[lidx].size()); for (const CandidateSurface &candidate : surfaces_by_layer[lidx]) { const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); - Polygons limiting_area = union_(area_to_be_bridge, expansion_area); + + area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), + [internal_unsupported_area](const Polygon &p) { + return intersection({p}, internal_unsupported_area).empty(); + }), + area_to_be_bridge.end()); + + Polygons limiting_area = union_(area_to_be_bridge, expansion_area); if (area_to_be_bridge.empty()) continue; diff --git a/tests/fff_print/test_perimeters.cpp b/tests/fff_print/test_perimeters.cpp index 99f0ddb993..4fa344d86c 100644 --- a/tests/fff_print/test_perimeters.cpp +++ b/tests/fff_print/test_perimeters.cpp @@ -529,7 +529,7 @@ SCENARIO("Perimeters3", "[Perimeters]") auto config = Slic3r::DynamicPrintConfig::full_print_config_with({ { "skirts", 0 }, { "perimeters", 3 }, - { "layer_height", 0.4 }, + { "layer_height", 0.15 }, { "bridge_speed", 99 }, { "enable_dynamic_overhang_speeds", false }, // to prevent bridging over sparse infill diff --git a/tests/libslic3r/test_placeholder_parser.cpp b/tests/libslic3r/test_placeholder_parser.cpp index a6da48bf34..9a1405f021 100644 --- a/tests/libslic3r/test_placeholder_parser.cpp +++ b/tests/libslic3r/test_placeholder_parser.cpp @@ -44,6 +44,27 @@ SCENARIO("Placeholder parser scripting", "[PlaceholderParser]") { 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"); } + SECTION("parsing string with escaped characters") { REQUIRE(parser.process("{\"hu\\nha\\\\\\\"ha\\\"\"}") == "hu\nha\\\"ha\""); } + + WHEN("An UTF-8 character is used inside the code block") { + THEN("A std::runtime_error exception is thrown.") { + // full-width plus sign instead of plain + + REQUIRE_THROWS_AS(parser.process("{1\xEF\xBC\x8B 3}"), std::runtime_error); + } + } + WHEN("An UTF-8 character is used inside a string") { + THEN("UTF-8 sequence is processed correctly when quoted") { + // japanese "cool" or "stylish" + REQUIRE(parser.process("{1+\"\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84\"+\" \"+3}") == "1\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84 3"); + } + } + WHEN("An UTF-8 character is used inside a string") { + THEN("UTF-8 sequence is processed correctly outside of code blocks") { + // japanese "cool" or "stylish" + REQUIRE(parser.process("{1+3}\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84") == "4\xE3\x81\x8B\xE3\x81\xA3\xE3\x81\x93\xE3\x81\x84\xE3\x81\x84"); + } + } + // Test the math expressions. SECTION("math: 2*3") { REQUIRE(parser.process("{2*3}") == "6"); } SECTION("math: 2*3/6") { REQUIRE(parser.process("{2*3/6}") == "1"); }