From e46891fa8c0fffeb4c33097b4c477a05ca9bb8c0 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 23 Mar 2023 09:23:20 +0100 Subject: [PATCH 1/9] 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 78ded40c4..55e7235eb 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 37103b316..0d109070b 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")); + } + } } From 7df4cfb45334e0773a1f9591884febdda6d9aa17 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 23 Mar 2023 12:27:07 +0100 Subject: [PATCH 2/9] Fixed a crash when selection wipe tower and using add instance function: this was between 2.6.0-alpha3 and 2.6.0-alpha4 in 4d5b85e. --- src/slic3r/GUI/Plater.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index a64de49ee..5c8677d0a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -4779,7 +4779,7 @@ bool Plater::priv::can_increase_instances() const if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss) return false; const auto obj_idxs = get_selection().get_object_idxs(); - return !obj_idxs.empty() && !sidebar->obj_list()->has_selected_cut_object(); + return !obj_idxs.empty() && !get_selection().is_wipe_tower() && !sidebar->obj_list()->has_selected_cut_object(); } bool Plater::priv::can_decrease_instances(int obj_idx /*= -1*/) const From cd70954758e55116ca90540cba32a607b6e9f8a4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 22 Feb 2023 13:13:22 +0100 Subject: [PATCH 3/9] Wipe tower: added an option to change spacing of the lines --- src/libslic3r/GCode/WipeTower.cpp | 3 +-- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 9 +++++++++ src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 4f4871366..99cbbe87c 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -512,6 +512,7 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector> & if (m_plan.empty()) return; - m_extra_spacing = 1.f; - plan_tower(); for (int i=0;i<5;++i) { save_on_last_wipe(); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 09514200f..d9ed8af96 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -461,7 +461,7 @@ static std::vector s_Preset_print_options { "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", - "wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits", + "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width" }; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4030e381a..af897b93c 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -206,6 +206,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "wipe_tower_width" || opt_key == "wipe_tower_brim_width" || opt_key == "wipe_tower_bridging" + || opt_key == "wipe_tower_extra_spacing" || opt_key == "wipe_tower_no_sparse_layers" || opt_key == "wiping_volumes_matrix" || opt_key == "parking_pos_retraction" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index dbdb36c7d..fcb78bc53 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3150,6 +3150,15 @@ void PrintConfigDef::init_fff_params() def->min = 0.; def->set_default_value(new ConfigOptionFloat(2.)); + def = this->add("wipe_tower_extra_spacing", coPercent); + def->label = L("Wipe tower purge lines spacing"); + def->tooltip = L("Spacing of purge lines on the wipe tower."); + def->sidetext = L("%"); + def->mode = comExpert; + def->min = 100.; + def->max = 200.; + def->set_default_value(new ConfigOptionPercent(100.)); + def = this->add("wipe_into_infill", coBool); def->category = L("Wipe options"); def->label = L("Wipe into this object's infill"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index e5a9f4c41..4027eef36 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -823,6 +823,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, wipe_tower_per_color_wipe)) ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, wipe_tower_brim_width)) + ((ConfigOptionPercent, wipe_tower_extra_spacing)) ((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionFloats, wiping_volumes_matrix)) ((ConfigOptionFloats, wiping_volumes_extruders)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index c8c3923f7..5312d52ec 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -320,7 +320,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_wipe_tower = config->opt_bool("wipe_tower"); for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", - "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) + "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters")); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 5c8677d0a..6ecfa2339 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2016,7 +2016,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", - "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", + "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_extra_spacing", "extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 41a015e9f..3f479030b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1602,6 +1602,7 @@ void TabPrint::build() optgroup->append_single_option_line("wipe_tower_rotation_angle"); optgroup->append_single_option_line("wipe_tower_brim_width"); optgroup->append_single_option_line("wipe_tower_bridging"); + optgroup->append_single_option_line("wipe_tower_extra_spacing"); optgroup->append_single_option_line("wipe_tower_no_sparse_layers"); optgroup->append_single_option_line("single_extruder_multi_material_priming"); From 571b133791b0af6789df2df49ea0cf86783d210b Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 1 Mar 2023 10:48:05 +0100 Subject: [PATCH 4/9] Wipe tower: stabilization cone --- src/libslic3r/GCode/WipeTower.cpp | 113 +++++++++++++++++++++++++----- src/libslic3r/GCode/WipeTower.hpp | 4 ++ src/libslic3r/Print.cpp | 30 +++++--- src/libslic3r/Print.hpp | 1 + src/slic3r/GUI/3DScene.cpp | 13 ++++ 5 files changed, 135 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 99cbbe87c..516ae3d8a 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -10,6 +10,7 @@ #include "GCodeProcessor.hpp" #include "BoundingBox.hpp" #include "LocalesUtils.hpp" +#include "Geometry.hpp" #include @@ -1170,35 +1171,94 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() ";------------------\n\n\n\n\n\n\n"); } - // outer perimeter (always): - writer.rectangle(wt_box, feedrate); + // This block creates the stabilization cone. + // First define a lambda to draw the rectangle with stabilization. + auto supported_rectangle = [this, &writer](const box_coordinates& wt_box, double feedrate) -> Polygon { + const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth); + + double r = std::tan(Geometry::deg2rad(15.)) * (m_wipe_tower_height - m_layer_info->z); + Vec2f center = (wt_box.lu + wt_box.rd) / 2.; + double w = wt_box.lu.y() - wt_box.ld.y(); + enum Type { + Arc, + Corner, + ArcStart, + ArcEnd + }; + + std::vector> pts = {{wt_box.ru, Corner}}; + if (double alpha_start = std::asin((0.5*w)/r); ! std::isnan(alpha_start) && r > 0.5*w+0.01) { + for (double alpha = alpha_start; alpha < M_PI-alpha_start+0.001; alpha+=(M_PI-2*alpha_start) / 20.) + pts.emplace_back(Vec2f(center.x() + r*std::cos(alpha)/support_scale, center.y() + r*std::sin(alpha)), alpha == alpha_start ? ArcStart : Arc); + pts.back().second = ArcEnd; + } + pts.emplace_back(wt_box.lu, Corner); + pts.emplace_back(wt_box.ld, Corner); + for (int i=int(pts.size())-3; i>0; --i) + pts.emplace_back(Vec2f(pts[i].first.x(), 2*center.y()-pts[i].first.y()), i == int(pts.size())-3 ? ArcStart : i == 1 ? ArcEnd : Arc); + pts.emplace_back(wt_box.rd, Corner); + + // Find the closest corner and travel to it. + int start_i = 0; + double min_dist = std::numeric_limits::max(); + for (int i=0; i()); + for (int i=cp+1; true; ++i ) { + if (i==int(poly.points.size())) + i = 0; + writer.extrude(unscale(poly.points[i]).cast()); + if (i == cp) + break; + } } // Save actual brim width to be later passed to the Print object, which will use it // for skirt calculation and pass it to GLCanvas for precise preview box - m_wipe_tower_brim_width_real = wt_box.ld.x() - box.ld.x() + spacing/2.f; - wt_box = box; + m_wipe_tower_brim_width_real = loops_num * spacing; } - // Now prepare future wipe. box contains rectangle that was extruded last (ccw). - Vec2f target = (writer.pos() == wt_box.ld ? wt_box.rd : - (writer.pos() == wt_box.rd ? wt_box.ru : - (writer.pos() == wt_box.ru ? wt_box.lu : - wt_box.ld))); - writer.add_wipe_point(writer.pos()) - .add_wipe_point(target); - + // Now prepare future wipe. + int i = poly.closest_point_index(Point::new_scale(writer.x(), writer.y())); + writer.add_wipe_point(writer.pos()); + writer.add_wipe_point(unscale(poly.points[i==0 ? int(poly.points.size())-1 : i-1]).cast()); // Ask our writer about how much material was consumed. // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. @@ -1209,6 +1269,24 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() return construct_tcr(writer, false, old_tool); } +// Static method to get the radius and x-scaling of the stabilizing cone base. +std::pair WipeTower::get_wipe_tower_cone_base(double width, double height, double depth) +{ + const double angle_deg = 15.; + double R = std::tan(Geometry::deg2rad(angle_deg)) * height; + double fake_width = 0.66 * width; + double diag = std::hypot(fake_width / 2., depth / 2.); + double support_scale = 1.; + if (R > diag) { + double w = fake_width; + double sin = 0.5 * depth / diag; + double tan = depth / w; + double t = (R - diag) * sin; + support_scale = (w / 2. + t / tan + t * tan) / (w / 2.); + } + return std::make_pair(R, support_scale); +} + // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume) @@ -1251,6 +1329,7 @@ void WipeTower::plan_tower() m_wipe_tower_depth = 0.f; for (auto& layer : m_plan) layer.depth = 0.f; + m_wipe_tower_height = m_plan.empty() ? 0.f : m_plan.back().z; for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) { @@ -1357,7 +1436,7 @@ void WipeTower::generate(std::vector> & m_old_temperature = -1; // reset last temperature written in the gcode std::vector layer_result; - for (auto layer : m_plan) + for (const WipeTower::WipeTowerInfo& layer : m_plan) { set_layer(layer.z, layer.height, 0, false/*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); m_internal_rotation += 180.f; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 7f4a6bf9a..e23c6645f 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -23,6 +23,8 @@ class WipeTower public: static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; } + static std::pair get_wipe_tower_cone_base(double width, double height, double depth); + struct Extrusion { Extrusion(const Vec2f &pos, float width, unsigned int tool) : pos(pos), width(width), tool(tool) {} @@ -142,6 +144,7 @@ public: float get_depth() const { return m_wipe_tower_depth; } float get_brim_width() const { return m_wipe_tower_brim_width_real; } + float get_wipe_tower_height() const { return m_wipe_tower_height; } @@ -253,6 +256,7 @@ private: Vec2f m_wipe_tower_pos; // Left front corner of the wipe tower in mm. float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower + float m_wipe_tower_height = 0.f; float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index af897b93c..49dbfea1f 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1134,23 +1134,34 @@ Polygons Print::first_layer_islands() const std::vector Print::first_layer_wipe_tower_corners() const { - std::vector corners; + std::vector pts_scaled; + if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); - for (Vec2d pt : { - pt0, - Vec2d(pt0.x()+width, pt0.y() ), - Vec2d(pt0.x()+width, pt0.y()+depth), - Vec2d(pt0.x(), pt0.y()+depth) - }) { + + // First the corners. + std::vector pts = { pt0, + Vec2d(pt0.x()+width, pt0.y()), + Vec2d(pt0.x()+width, pt0.y()+depth), + Vec2d(pt0.x(),pt0.y()+depth) + }; + + // Now the stabilization cone. + Vec2d center = (pts[0] + pts[2])/2.; + const auto [cone_R, cone_x_scale] = WipeTower::get_wipe_tower_cone_base(m_config.wipe_tower_width, m_wipe_tower_data.height, m_wipe_tower_data.depth); + double r = cone_R + m_wipe_tower_data.brim_width; + for (double alpha = 0.; alpha<2*M_PI; alpha += M_PI/20.) + pts.emplace_back(center + r*Vec2d(std::cos(alpha)/cone_x_scale, std::sin(alpha))); + + for (Vec2d& pt : pts) { pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); - corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); + pts_scaled.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); } } - return corners; + return pts_scaled; } void Print::finalize_first_layer_convex_hull() @@ -1448,6 +1459,7 @@ void Print::_make_wipe_tower() wipe_tower.generate(m_wipe_tower_data.tool_changes); m_wipe_tower_data.depth = wipe_tower.get_depth(); m_wipe_tower_data.brim_width = wipe_tower.get_brim_width(); + m_wipe_tower_data.height = wipe_tower.get_wipe_tower_height(); // Unload the current filament over the purge tower. coordf_t layer_height = m_objects.front()->config().layer_height.value; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9fbbe378a..60ef1403f 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -434,6 +434,7 @@ struct WipeTowerData // Depth of the wipe tower to pass to GLCanvas3D for exact bounding box: float depth; float brim_width; + float height; void clear() { priming.reset(nullptr); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c25bff45d..4659bf0f0 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -544,6 +544,19 @@ int GLVolumeCollection::load_wipe_tower_preview( brim_mesh.translate(-brim_width, -brim_width, 0.f); mesh.merge(brim_mesh); + // Now the stabilization cone and its base. + const auto [R, scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth); + TriangleMesh cone_mesh(its_make_cone(R, height)); + cone_mesh.scale(Vec3f(1.f/scale_x, 1.f, 1.f)); + + TriangleMesh disk_mesh(its_make_cylinder(R, brim_height)); + disk_mesh.scale(Vec3f(1. / scale_x, 1., 1.)); // Now it matches the base, which may be elliptic. + disk_mesh.scale(Vec3f(1.f + scale_x*brim_width/R, 1.f + brim_width/R, 1.f)); // Scale so the brim is not deformed. + cone_mesh.merge(disk_mesh); + cone_mesh.translate(width / 2., depth / 2., 0.); + mesh.merge(cone_mesh); + + volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); #if ENABLE_OPENGL_ES From 26ba74fad06fe37325c209f32e69a1ef4f61fe7a Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Tue, 7 Mar 2023 17:27:07 +0100 Subject: [PATCH 5/9] Wipe tower: stabilization cone bottom infill --- src/libslic3r/GCode/WipeTower.cpp | 70 ++++++++++++++++++++++++++----- src/libslic3r/Print.cpp | 2 +- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 516ae3d8a..fdc17c1ff 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -4,13 +4,17 @@ #include #include #include +#include #include #include +#include "ClipperUtils.hpp" #include "GCodeProcessor.hpp" #include "BoundingBox.hpp" #include "LocalesUtils.hpp" #include "Geometry.hpp" +#include "Surface.hpp" +#include "Fill/FillRectilinear.hpp" #include @@ -1171,9 +1175,11 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() ";------------------\n\n\n\n\n\n\n"); } + const float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4); + // This block creates the stabilization cone. // First define a lambda to draw the rectangle with stabilization. - auto supported_rectangle = [this, &writer](const box_coordinates& wt_box, double feedrate) -> Polygon { + auto supported_rectangle = [this, &writer, spacing](const box_coordinates& wt_box, double feedrate, bool infill_cone) -> Polygon { const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth); double r = std::tan(Geometry::deg2rad(15.)) * (m_wipe_tower_height - m_layer_info->z); @@ -1186,6 +1192,7 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() ArcEnd }; + // First generate vector of annotated point which form the boundary. std::vector> pts = {{wt_box.ru, Corner}}; if (double alpha_start = std::asin((0.5*w)/r); ! std::isnan(alpha_start) && r > 0.5*w+0.01) { for (double alpha = alpha_start; alpha < M_PI-alpha_start+0.001; alpha+=(M_PI-2*alpha_start) / 20.) @@ -1198,6 +1205,40 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() pts.emplace_back(Vec2f(pts[i].first.x(), 2*center.y()-pts[i].first.y()), i == int(pts.size())-3 ? ArcStart : i == 1 ? ArcEnd : Arc); pts.emplace_back(wt_box.rd, Corner); + // Create a Polygon from the points. + Polygon poly; + for (const auto& [pt, tag] : pts) + poly.points.push_back(Point::new_scale(pt)); + + // Prepare polygons to be filled by infill. + Polylines polylines; + if (infill_cone && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing) { + ExPolygons infill_areas; + ExPolygon wt_contour(poly); + Polygon wt_rectangle(Points{Point::new_scale(wt_box.ld), Point::new_scale(wt_box.rd), Point::new_scale(wt_box.ru), Point::new_scale(wt_box.lu)}); + wt_rectangle = offset(wt_rectangle, scale_(-spacing/2.)).front(); + wt_contour = offset_ex(wt_contour, scale_(-spacing/2.)).front(); + infill_areas = diff_ex(wt_contour, wt_rectangle); + if (infill_areas.size() == 2) { + ExPolygon& bottom_expoly = infill_areas.front().contour.points.front().y() < infill_areas.back().contour.points.front().y() ? infill_areas[0] : infill_areas[1]; + std::unique_ptr filler(Fill::new_from_type(ipMonotonicLines)); + filler->angle = Geometry::deg2rad(45.f); + filler->spacing = spacing; + FillParams params; + params.density = 1.f; + Surface surface(stBottom, bottom_expoly); + filler->bounding_box = get_extents(bottom_expoly); + polylines = filler->fill_surface(&surface, params); + if (! polylines.empty()) { + if (polylines.front().points.front().x() > polylines.back().points.back().x()) { + std::reverse(polylines.begin(), polylines.end()); + for (Polyline& p : polylines) + p.reverse(); + } + } + } + } + // Find the closest corner and travel to it. int start_i = 0; double min_dist = std::numeric_limits::max(); @@ -1212,29 +1253,38 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() } writer.travel(pts[start_i].first); - // Now actually extrude the boundary: + // Now actually extrude the boundary (and possibly infill): int i = start_i+1 == int(pts.size()) ? 0 : start_i + 1; while (i != start_i) { writer.extrude(pts[i].first, feedrate); + if (pts[i].second == ArcEnd) { + // Extrude the infill. + if (! polylines.empty()) { + // Extrude the infill and travel back to where we were. + bool mirror = ((pts[i].first.y() - center.y()) * (unscale(polylines.front().points.front()).y() - center.y())) < 0.; + for (const Polyline& line : polylines) { + writer.travel(center - (mirror ? 1.f : -1.f) * (unscale(line.points.front()).cast() - center)); + for (size_t i=0; i() - center)); + } + writer.travel(pts[i].first); + } + } if (++i == int(pts.size())) i = 0; } writer.extrude(pts[start_i].first, feedrate); - - // Return the polygon. - Polygon out; - for (const auto& [pt, tag] : pts) - out.points.push_back(Point::new_scale(pt)); - return out; + return poly; }; // outer contour (always) - Polygon poly = supported_rectangle(wt_box, feedrate); + bool infill_cone = first_layer && m_wipe_tower_width > 2*spacing && m_wipe_tower_depth > 2*spacing; + Polygon poly = supported_rectangle(wt_box, feedrate, infill_cone); + // brim (first layer only) if (first_layer) { box_coordinates box = wt_box; - float spacing = m_perimeter_width - m_layer_height*float(1.-M_PI_4); size_t loops_num = (m_wipe_tower_brim_width + spacing/2.f) / spacing; for (size_t i = 0; i < loops_num; ++ i) { diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 49dbfea1f..9d9f46cff 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -591,7 +591,7 @@ std::string Print::validate(std::string* warning) const if (*(lh.end()-2) > *(lh_tallest.end()-2)) tallest_object_idx = i; } - } + } if (has_custom_layering) { for (size_t idx_object = 0; idx_object < m_objects.size(); ++ idx_object) { From b20325671a4459fd2992474ce312dc75b383c4cd Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Thu, 16 Mar 2023 10:07:48 +0100 Subject: [PATCH 6/9] Wipe tower: added the stabilization cone into configuration --- src/libslic3r/GCode/WipeTower.cpp | 10 +++++----- src/libslic3r/GCode/WipeTower.hpp | 3 ++- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 3 ++- src/libslic3r/PrintConfig.cpp | 10 ++++++++++ src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/3DScene.cpp | 24 +++++++++++++----------- src/slic3r/GUI/3DScene.hpp | 4 ++-- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/GCodeViewer.cpp | 2 +- src/slic3r/GUI/GLCanvas3D.cpp | 5 +++-- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 13 files changed, 43 insertions(+), 26 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index fdc17c1ff..5717aa0bb 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -517,6 +517,7 @@ WipeTower::WipeTower(const PrintConfig& config, const std::vector Polygon { - const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth); + const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth, m_wipe_tower_cone_angle); - double r = std::tan(Geometry::deg2rad(15.)) * (m_wipe_tower_height - m_layer_info->z); + double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle/2.f)) * (m_wipe_tower_height - m_layer_info->z); Vec2f center = (wt_box.lu + wt_box.rd) / 2.; double w = wt_box.lu.y() - wt_box.ld.y(); enum Type { @@ -1320,10 +1321,9 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() } // Static method to get the radius and x-scaling of the stabilizing cone base. -std::pair WipeTower::get_wipe_tower_cone_base(double width, double height, double depth) +std::pair WipeTower::get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg) { - const double angle_deg = 15.; - double R = std::tan(Geometry::deg2rad(angle_deg)) * height; + double R = std::tan(Geometry::deg2rad(angle_deg/2.)) * height; double fake_width = 0.66 * width; double diag = std::hypot(fake_width / 2., depth / 2.); double support_scale = 1.; diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index e23c6645f..15b07f3d8 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -23,7 +23,7 @@ class WipeTower public: static const std::string never_skip_tag() { return "_GCODE_WIPE_TOWER_NEVER_SKIP_TAG"; } - static std::pair get_wipe_tower_cone_base(double width, double height, double depth); + static std::pair get_wipe_tower_cone_base(double width, double height, double depth, double angle_deg); struct Extrusion { @@ -257,6 +257,7 @@ private: float m_wipe_tower_width; // Width of the wipe tower. float m_wipe_tower_depth = 0.f; // Depth of the wipe tower float m_wipe_tower_height = 0.f; + float m_wipe_tower_cone_angle = 0.f; float m_wipe_tower_brim_width = 0.f; // Width of brim (mm) from config float m_wipe_tower_brim_width_real = 0.f; // Width of brim (mm) after generation float m_wipe_tower_rotation_angle = 0.f; // Wipe tower rotation angle in degrees (with respect to x axis) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d9ed8af96..b36427175 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -460,7 +460,7 @@ static std::vector s_Preset_print_options { "perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width", "top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y", - "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", + "wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits", "perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width" diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 9d9f46cff..f2143857a 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -205,6 +205,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "wipe_tower" || opt_key == "wipe_tower_width" || opt_key == "wipe_tower_brim_width" + || opt_key == "wipe_tower_cone_angle" || opt_key == "wipe_tower_bridging" || opt_key == "wipe_tower_extra_spacing" || opt_key == "wipe_tower_no_sparse_layers" @@ -1150,7 +1151,7 @@ std::vector Print::first_layer_wipe_tower_corners() const // Now the stabilization cone. Vec2d center = (pts[0] + pts[2])/2.; - const auto [cone_R, cone_x_scale] = WipeTower::get_wipe_tower_cone_base(m_config.wipe_tower_width, m_wipe_tower_data.height, m_wipe_tower_data.depth); + const auto [cone_R, cone_x_scale] = WipeTower::get_wipe_tower_cone_base(m_config.wipe_tower_width, m_wipe_tower_data.height, m_wipe_tower_data.depth, m_config.wipe_tower_cone_angle); double r = cone_R + m_wipe_tower_data.brim_width; for (double alpha = 0.; alpha<2*M_PI; alpha += M_PI/20.) pts.emplace_back(center + r*Vec2d(std::cos(alpha)/cone_x_scale, std::sin(alpha))); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index fcb78bc53..68e27f4d1 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3150,6 +3150,16 @@ void PrintConfigDef::init_fff_params() def->min = 0.; def->set_default_value(new ConfigOptionFloat(2.)); + def = this->add("wipe_tower_cone_angle", coFloat); + def->label = L("Stabilization cone apex angle"); + def->tooltip = L("Angle at the apex of the cone that is used to stabilize the wipe tower. " + "Larger angle means wider base."); + def->sidetext = L("°"); + def->mode = comAdvanced; + def->min = 0.; + def->max = 90.; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("wipe_tower_extra_spacing", coPercent); def->label = L("Wipe tower purge lines spacing"); def->tooltip = L("Spacing of purge lines on the wipe tower."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4027eef36..52924bf91 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -823,6 +823,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, wipe_tower_per_color_wipe)) ((ConfigOptionFloat, wipe_tower_rotation_angle)) ((ConfigOptionFloat, wipe_tower_brim_width)) + ((ConfigOptionFloat, wipe_tower_cone_angle)) ((ConfigOptionPercent, wipe_tower_extra_spacing)) ((ConfigOptionFloat, wipe_tower_bridging)) ((ConfigOptionFloats, wiping_volumes_matrix)) diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 4659bf0f0..f5cf9de7a 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -480,11 +480,11 @@ int GLVolumeCollection::load_object_volume( #if ENABLE_OPENGL_ES int GLVolumeCollection::load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, + float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh) #else int GLVolumeCollection::load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, + float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width) #endif // ENABLE_OPENGL_ES { @@ -545,16 +545,18 @@ int GLVolumeCollection::load_wipe_tower_preview( mesh.merge(brim_mesh); // Now the stabilization cone and its base. - const auto [R, scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth); - TriangleMesh cone_mesh(its_make_cone(R, height)); - cone_mesh.scale(Vec3f(1.f/scale_x, 1.f, 1.f)); + const auto [R, scale_x] = WipeTower::get_wipe_tower_cone_base(width, height, depth, cone_angle); + if (R > 0.) { + TriangleMesh cone_mesh(its_make_cone(R, height)); + cone_mesh.scale(Vec3f(1.f/scale_x, 1.f, 1.f)); - TriangleMesh disk_mesh(its_make_cylinder(R, brim_height)); - disk_mesh.scale(Vec3f(1. / scale_x, 1., 1.)); // Now it matches the base, which may be elliptic. - disk_mesh.scale(Vec3f(1.f + scale_x*brim_width/R, 1.f + brim_width/R, 1.f)); // Scale so the brim is not deformed. - cone_mesh.merge(disk_mesh); - cone_mesh.translate(width / 2., depth / 2., 0.); - mesh.merge(cone_mesh); + TriangleMesh disk_mesh(its_make_cylinder(R, brim_height)); + disk_mesh.scale(Vec3f(1. / scale_x, 1., 1.)); // Now it matches the base, which may be elliptic. + disk_mesh.scale(Vec3f(1.f + scale_x*brim_width/R, 1.f + brim_width/R, 1.f)); // Scale so the brim is not deformed. + cone_mesh.merge(disk_mesh); + cone_mesh.translate(width / 2., depth / 2., 0.); + mesh.merge(cone_mesh); + } volumes.emplace_back(new GLVolume(color)); diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 9791a0faf..d685d6489 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -424,10 +424,10 @@ public: #if ENABLE_OPENGL_ES int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); + float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr); #else int load_wipe_tower_preview( - float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool size_unknown, float brim_width); + float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width); #endif // ENABLE_OPENGL_ES GLVolume* new_toolpath_volume(const ColorRGBA& rgba); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5312d52ec..1bc2280d5 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -319,7 +319,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_wipe_tower = config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index 74f7bf50e..62d4314db 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -2253,7 +2253,7 @@ void GCodeViewer::load_shells(const Print& print) const WipeTowerData& wipe_tower_data = print.wipe_tower_data(extruders_count); const float depth = wipe_tower_data.depth; const float brim_width = wipe_tower_data.brim_width; - m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_rotation_angle, + m_shells.volumes.load_wipe_tower_preview(config.wipe_tower_x, config.wipe_tower_y, config.wipe_tower_width, depth, max_z, config.wipe_tower_cone_angle, config.wipe_tower_rotation_angle, !print.is_step_done(psWipeTower), brim_width); } } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index e369e0f40..6924667e6 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -2035,17 +2035,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const float w = dynamic_cast(m_config->option("wipe_tower_width"))->value; const float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value; const float bw = dynamic_cast(m_config->option("wipe_tower_brim_width"))->value; + const float ca = dynamic_cast(m_config->option("wipe_tower_cone_angle"))->value; const Print *print = m_process->fff_print(); const float depth = print->wipe_tower_data(extruders_count).depth; #if ENABLE_OPENGL_ES int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower), bw, &m_wipe_tower_mesh); #else int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview( - x, y, w, depth, (float)height, a, !print->is_step_done(psWipeTower), + x, y, w, depth, (float)height, ca, a, !print->is_step_done(psWipeTower), bw); #endif // ENABLE_OPENGL_ES if (volume_idx_wipe_tower_old != -1) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6ecfa2339..6a17c4ee5 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2016,7 +2016,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material", - "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_extra_spacing", + "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 3f479030b..6e4f64a99 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1602,6 +1602,7 @@ void TabPrint::build() optgroup->append_single_option_line("wipe_tower_rotation_angle"); optgroup->append_single_option_line("wipe_tower_brim_width"); optgroup->append_single_option_line("wipe_tower_bridging"); + optgroup->append_single_option_line("wipe_tower_cone_angle"); optgroup->append_single_option_line("wipe_tower_extra_spacing"); optgroup->append_single_option_line("wipe_tower_no_sparse_layers"); optgroup->append_single_option_line("single_extruder_multi_material_priming"); From 64cf3579075b99430145946bfaea412d389be0e4 Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Wed, 22 Mar 2023 09:45:30 +0100 Subject: [PATCH 7/9] Wipe tower: fix the cone in combination with 'no_sparse_layers' --- src/libslic3r/GCode/WipeTower.cpp | 10 ++++++++-- src/libslic3r/GCode/WipeTower.hpp | 4 ++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 5717aa0bb..58af2da48 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -1183,7 +1183,9 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() auto supported_rectangle = [this, &writer, spacing](const box_coordinates& wt_box, double feedrate, bool infill_cone) -> Polygon { const auto [R, support_scale] = get_wipe_tower_cone_base(m_wipe_tower_width, m_wipe_tower_height, m_wipe_tower_depth, m_wipe_tower_cone_angle); - double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle/2.f)) * (m_wipe_tower_height - m_layer_info->z); + double z = m_no_sparse_layers ? (m_current_height + m_layer_info->height) : m_layer_info->z; // the former should actually work in both cases, but let's stay on the safe side (the 2.6.0 is close) + + double r = std::tan(Geometry::deg2rad(m_wipe_tower_cone_angle/2.f)) * (m_wipe_tower_height - z); Vec2f center = (wt_box.lu + wt_box.rd) / 2.; double w = wt_box.lu.y() - wt_box.ld.y(); enum Type { @@ -1313,9 +1315,11 @@ WipeTower::ToolChangeResult WipeTower::finish_layer() // Ask our writer about how much material was consumed. // Skip this in case the layer is sparse and config option to not print sparse layers is enabled. - if (! m_no_sparse_layers || toolchanges_on_layer || first_layer) + if (! m_no_sparse_layers || toolchanges_on_layer || first_layer) { if (m_current_tool < m_used_filament_length.size()) m_used_filament_length[m_current_tool] += writer.get_and_reset_used_filament_length(); + m_current_height += m_layer_info->height; + } return construct_tcr(writer, false, old_tool); } @@ -1380,6 +1384,7 @@ void WipeTower::plan_tower() for (auto& layer : m_plan) layer.depth = 0.f; m_wipe_tower_height = m_plan.empty() ? 0.f : m_plan.back().z; + m_current_height = 0.f; for (int layer_index = int(m_plan.size()) - 1; layer_index >= 0; --layer_index) { @@ -1471,6 +1476,7 @@ void WipeTower::generate(std::vector> & } m_layer_info = m_plan.begin(); + m_current_height = 0.f; // we don't know which extruder to start with - we'll set it according to the first toolchange for (const auto& layer : m_plan) { diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 15b07f3d8..561123d79 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -364,6 +364,10 @@ private: std::vector m_plan; // Stores information about all layers and toolchanges for the future wipe tower (filled by plan_toolchange(...)) std::vector::iterator m_layer_info = m_plan.end(); + // This sums height of all extruded layers, not counting the layers which + // will be later removed when the "no_sparse_layers" is used. + float m_current_height = 0.f; + // Stores information about used filament length per extruder: std::vector m_used_filament_length; From 6170783f7c2b60e40d7c35a8c29cbef550634ffa Mon Sep 17 00:00:00 2001 From: Lukas Matena Date: Fri, 17 Mar 2023 13:49:03 +0100 Subject: [PATCH 8/9] Wipe tower: Increased maximum of wipe_tower_extra_spacing to 300 % --- src/libslic3r/PrintConfig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 68e27f4d1..3255c015b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3166,7 +3166,7 @@ void PrintConfigDef::init_fff_params() def->sidetext = L("%"); def->mode = comExpert; def->min = 100.; - def->max = 200.; + def->max = 300.; def->set_default_value(new ConfigOptionPercent(100.)); def = this->add("wipe_into_infill", coBool); From 30458846405061e4b6334470a30d6b09a7631aba Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Thu, 23 Mar 2023 15:00:36 +0100 Subject: [PATCH 9/9] PlaceholderParser: Reduced code duplicity. --- src/libslic3r/PlaceholderParser.cpp | 901 +++++++++++++--------------- 1 file changed, 419 insertions(+), 482 deletions(-) diff --git a/src/libslic3r/PlaceholderParser.cpp b/src/libslic3r/PlaceholderParser.cpp index 55e7235eb..a26841082 100644 --- a/src/libslic3r/PlaceholderParser.cpp +++ b/src/libslic3r/PlaceholderParser.cpp @@ -170,7 +170,7 @@ namespace client template struct OptWithPos { OptWithPos() {} - OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range) : opt(opt), it_range(it_range) {} + OptWithPos(ConfigOptionConstPtr opt, boost::iterator_range it_range, bool writable = false) : opt(opt), it_range(it_range), writable(writable) {} ConfigOptionConstPtr opt { nullptr }; bool writable { false }; // -1 means it is a scalar variable, or it is a vector variable and index was not assigned yet or the whole vector is considered. @@ -720,9 +720,14 @@ namespace client } struct MyContext : public ConfigOptionResolver { + // Config provided as a parameter to PlaceholderParser invocation, overriding PlaceholderParser stored config. const DynamicConfig *external_config = nullptr; + // Config stored inside PlaceholderParser. const DynamicConfig *config = nullptr; + // Config provided as a parameter to PlaceholderParser invocation, evaluated after the two configs above. const DynamicConfig *config_override = nullptr; + // Config provided as a parameter to PlaceholderParser invocation, containing variables that will be read out + // and processed by the PlaceholderParser callee. mutable DynamicConfig *config_outputs = nullptr; // Local variables, read / write mutable DynamicConfig config_local; @@ -737,6 +742,7 @@ namespace client // Table to translate symbol tag to a human readable error message. static std::map tag_to_error_message; + // Should the parser consider the parsed string to be a macro or a 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 @@ -762,13 +768,13 @@ namespace client 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); + void store_new_variable(const std::string &opt_key, std::unique_ptr &&opt, bool global_variable) { + assert(opt); if (global_variable) { assert(this->context_data != nullptr && this->context_data->global_config); - this->context_data->global_config->set_key_value(opt_key, opt); + this->context_data->global_config->set_key_value(opt_key, opt.release()); } else - this->config_local.set_key_value(opt_key ,opt); + this->config_local.set_key_value(opt_key, opt.release()); } template @@ -844,9 +850,10 @@ namespace client boost::iterator_range &opt_key, OptWithPos &output) { - const ConfigOption *opt = ctx->resolve_symbol(std::string(opt_key.begin(), opt_key.end())); + const std::string key{ opt_key.begin(), opt_key.end() }; + const ConfigOption *opt = ctx->resolve_symbol(key); if (opt == nullptr) { - opt = ctx->resolve_output_symbol(std::string(opt_key.begin(), opt_key.end())); + opt = ctx->resolve_output_symbol(key); if (opt == nullptr) ctx->throw_exception("Not a variable name", opt_key); output.writable = true; @@ -872,83 +879,233 @@ namespace client output.it_range.end() = it_end; } + // Evaluating a scalar variable into expr, + // all possible ConfigOption types are supported. + template + static void scalar_variable_to_expr( + const MyContext *ctx, + OptWithPos &opt, + expr &output) + { + assert(opt.opt->is_scalar()); + + switch (opt.opt->type()) { + case coFloat: output.set_d(opt.opt->getFloat()); break; + case coInt: output.set_i(opt.opt->getInt()); break; + case coString: output.set_s(static_cast(opt.opt)->value); break; + case coPercent: output.set_d(opt.opt->getFloat()); break; + case coEnum: + case coPoint: output.set_s(opt.opt->serialize()); break; + case coBool: output.set_b(opt.opt->getBool()); break; + case coFloatOrPercent: + { + std::string opt_key(opt.it_range.begin(), opt.it_range.end()); + if (boost::ends_with(opt_key, "extrusion_width")) { + // Extrusion width supports defaults and a complex graph of dependencies. + output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast(ctx->current_extruder_id))); + } else if (! static_cast(opt.opt)->percent) { + // Not a percent, just return the value. + output.set_d(opt.opt->getFloat()); + } else { + // Resolve dependencies using the "ratio_over" link to a parent value. + const ConfigOptionDef *opt_def = print_config_def.get(opt_key); + assert(opt_def != nullptr); + double v = opt.opt->getFloat() * 0.01; // percent to ratio + for (;;) { + const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); + if (opt_parent == nullptr) + ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); + if (boost::ends_with(opt_def->ratio_over, "extrusion_width")) { + // Extrusion width supports defaults and a complex graph of dependencies. + assert(opt_parent->type() == coFloatOrPercent); + v *= Flow::extrusion_width(opt_def->ratio_over, static_cast(opt_parent), *ctx, static_cast(ctx->current_extruder_id)); + break; + } + if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { + v *= opt_parent->getFloat(); + if (opt_parent->type() == coFloat || ! static_cast(opt_parent)->percent) + break; + v *= 0.01; // percent to ratio + } + // Continue one level up in the "ratio_over" hierarchy. + opt_def = print_config_def.get(opt_def->ratio_over); + assert(opt_def != nullptr); + } + output.set_d(v); + } + break; + } + default: + ctx->throw_exception("Unsupported scalar variable type", opt.it_range); + } + } + + // Evaluating one element of a vector variable. + // all possible ConfigOption types are supported. + template + static void vector_element_to_expr( + const MyContext *ctx, + OptWithPos &opt, + expr &output) + { + assert(opt.opt->is_vector()); + if (! opt.has_index()) + ctx->throw_exception("Referencing a vector variable when scalar 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 = (opt.index < 0) ? 0 : (opt.index >= int(vec->size())) ? 0 : size_t(opt.index); + switch (opt.opt->type()) { + case coFloats: output.set_d(static_cast(opt.opt)->values[idx]); break; + case coInts: output.set_i(static_cast(opt.opt)->values[idx]); break; + case coStrings: output.set_s(static_cast(opt.opt)->values[idx]); break; + case coPercents: output.set_d(static_cast(opt.opt)->values[idx]); break; + case coPoints: output.set_s(to_string(static_cast(opt.opt)->values[idx])); break; + case coBools: output.set_b(static_cast(opt.opt)->values[idx] != 0); break; + //case coEnums: output.set_s(opt.opt->vserialize()[idx]); break; + default: + ctx->throw_exception("Unsupported vector variable type", opt.it_range); + } + } + + 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); + } + + template + static void check_numeric(const expr ¶m) { + if (! param.numeric_type()) + param.throw_exception("Right side is not a numeric expression"); + }; + + template + static size_t evaluate_count(const expr &expr_count) { + 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); + }; + + template + static void scalar_variable_assign_scalar( + const MyContext *ctx, + OptWithPos &lhs, + const expr &rhs) + { + assert(lhs.opt->is_scalar()); + check_writable(ctx, lhs); + ConfigOption *wropt = const_cast(lhs.opt); + switch (wropt->type()) { + case coFloat: + check_numeric(rhs); + static_cast(wropt)->value = rhs.as_d(); + break; + case coInt: + check_numeric(rhs); + static_cast(wropt)->value = rhs.as_i(); + break; + case coString: + static_cast(wropt)->value = rhs.to_string(); + break; + case coPercent: + check_numeric(rhs); + static_cast(wropt)->value = rhs.as_d(); + break; + case coBool: + if (rhs.type() != expr::TYPE_BOOL) + ctx->throw_exception("Right side is not a boolean expression", rhs.it_range); + static_cast(wropt)->value = rhs.b(); + break; + default: + ctx->throw_exception("Unsupported output scalar variable type", lhs.it_range); + } + } + + template + static void vector_variable_element_assign_scalar( + const MyContext *ctx, + OptWithPos &lhs, + const expr &rhs) + { + assert(lhs.opt->is_vector()); + check_writable(ctx, lhs); + if (! lhs.has_index()) + ctx->throw_exception("Referencing an output vector variable when scalar is expected", lhs.it_range); + ConfigOptionVectorBase *vec = const_cast(static_cast(lhs.opt)); + if (vec->empty()) + ctx->throw_exception("Indexing an empty vector variable", lhs.it_range); + if (lhs.index >= int(vec->size())) + ctx->throw_exception("Index out of range", lhs.it_range); + switch (lhs.opt->type()) { + case coFloats: + check_numeric(rhs); + static_cast(vec)->values[lhs.index] = rhs.as_d(); + break; + case coInts: + check_numeric(rhs); + static_cast(vec)->values[lhs.index] = rhs.as_i(); + break; + case coStrings: + static_cast(vec)->values[lhs.index] = rhs.to_string(); + break; + case coPercents: + check_numeric(rhs); + static_cast(vec)->values[lhs.index] = rhs.as_d(); + break; + case coBools: + if (rhs.type() != expr::TYPE_BOOL) + ctx->throw_exception("Right side is not a boolean expression", rhs.it_range); + static_cast(vec)->values[lhs.index] = rhs.b(); + break; + default: + ctx->throw_exception("Unsupported output vector variable type", lhs.it_range); + } + } + + template + static void vector_variable_assign_expr_with_count( + const MyContext *ctx, + OptWithPos &lhs, + const expr &rhs_count, + const expr &rhs_value) + { + size_t count = evaluate_count(rhs_count); + auto *opt = const_cast(lhs.opt); + switch (lhs.opt->type()) { + case coFloats: + check_numeric(rhs_value); + static_cast(opt)->values.assign(count, rhs_value.as_d()); + break; + case coInts: + check_numeric(rhs_value); + static_cast(opt)->values.assign(count, rhs_value.as_i()); + break; + case coStrings: + static_cast(opt)->values.assign(count, rhs_value.to_string()); + break; + case coBools: + if (rhs_value.type() != expr::TYPE_BOOL) + rhs_value.throw_exception("Right side is not a boolean expression"); + static_cast(opt)->values.assign(count, rhs_value.b()); + break; + default: assert(false); + } + } + template static void variable_value( const MyContext *ctx, OptWithPos &opt, expr &output) { - if (opt.opt->is_vector()) { - if (! opt.has_index()) - ctx->throw_exception("Referencing a vector variable when scalar 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 = (opt.index < 0) ? 0 : (opt.index >= int(vec->size())) ? 0 : size_t(opt.index); - switch (opt.opt->type()) { - case coFloats: output.set_d(static_cast(opt.opt)->values[idx]); break; - case coInts: output.set_i(static_cast(opt.opt)->values[idx]); break; - case coStrings: output.set_s(static_cast(opt.opt)->values[idx]); break; - case coPercents: output.set_d(static_cast(opt.opt)->values[idx]); break; - case coPoints: output.set_s(to_string(static_cast(opt.opt)->values[idx])); break; - case coBools: output.set_b(static_cast(opt.opt)->values[idx] != 0); break; - //case coEnums: output.set_s(opt.opt->vserialize()[idx]); break; - default: - ctx->throw_exception("Unknown vector variable type", opt.it_range); - } - } else { - assert(opt.opt->is_scalar()); - switch (opt.opt->type()) { - case coFloat: output.set_d(opt.opt->getFloat()); break; - case coInt: output.set_i(opt.opt->getInt()); break; - case coString: output.set_s(static_cast(opt.opt)->value); break; - case coPercent: output.set_d(opt.opt->getFloat()); break; - case coEnum: - case coPoint: output.set_s(opt.opt->serialize()); break; - case coBool: output.set_b(opt.opt->getBool()); break; - case coFloatOrPercent: - { - std::string opt_key(opt.it_range.begin(), opt.it_range.end()); - if (boost::ends_with(opt_key, "extrusion_width")) { - // Extrusion width supports defaults and a complex graph of dependencies. - output.set_d(Flow::extrusion_width(opt_key, *ctx, static_cast(ctx->current_extruder_id))); - } else if (! static_cast(opt.opt)->percent) { - // Not a percent, just return the value. - output.set_d(opt.opt->getFloat()); - } else { - // Resolve dependencies using the "ratio_over" link to a parent value. - const ConfigOptionDef *opt_def = print_config_def.get(opt_key); - assert(opt_def != nullptr); - double v = opt.opt->getFloat() * 0.01; // percent to ratio - for (;;) { - const ConfigOption *opt_parent = opt_def->ratio_over.empty() ? nullptr : ctx->resolve_symbol(opt_def->ratio_over); - if (opt_parent == nullptr) - ctx->throw_exception("FloatOrPercent variable failed to resolve the \"ratio_over\" dependencies", opt.it_range); - if (boost::ends_with(opt_def->ratio_over, "extrusion_width")) { - // Extrusion width supports defaults and a complex graph of dependencies. - assert(opt_parent->type() == coFloatOrPercent); - v *= Flow::extrusion_width(opt_def->ratio_over, static_cast(opt_parent), *ctx, static_cast(ctx->current_extruder_id)); - break; - } - if (opt_parent->type() == coFloat || opt_parent->type() == coFloatOrPercent) { - v *= opt_parent->getFloat(); - if (opt_parent->type() == coFloat || ! static_cast(opt_parent)->percent) - break; - v *= 0.01; // percent to ratio - } - // Continue one level up in the "ratio_over" hierarchy. - opt_def = print_config_def.get(opt_def->ratio_over); - assert(opt_def != nullptr); - } - output.set_d(v); - } - break; - } - default: - ctx->throw_exception("Unknown scalar variable type", opt.it_range); - } - } - + if (opt.opt->is_vector()) + vector_element_to_expr(ctx, opt, output); + else + scalar_variable_to_expr(ctx, opt, output); output.it_range = opt.it_range; } @@ -974,6 +1131,7 @@ namespace client output.it_range = opt.it_range; } + // Reference to an existing symbol, or a name of a new symbol. template struct NewOldVariable { std::string name; @@ -1010,240 +1168,148 @@ namespace client 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( + static void scalar_variable_assign_scalar_expression( const MyContext *ctx, OptWithPos &opt, - expr ¶m) + const 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"); - }; - if (opt.opt->is_vector()) { - if (! opt.has_index()) - ctx->throw_exception("Referencing an output vector variable when scalar is expected", opt.it_range); - ConfigOptionVectorBase *vec = const_cast(static_cast(opt.opt)); - if (vec->empty()) - ctx->throw_exception("Indexing an empty vector variable", opt.it_range); - if (opt.index >= int(vec->size())) - ctx->throw_exception("Index out of range", opt.it_range); - switch (opt.opt->type()) { - case coFloats: - check_numeric(param); - static_cast(vec)->values[opt.index] = param.as_d(); - break; - case coInts: - 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: - check_numeric(param); - static_cast(vec)->values[opt.index] = param.as_d(); - break; - case coBools: - if (param.type() != expr::TYPE_BOOL) - ctx->throw_exception("Right side is not a boolean expression", param.it_range); - static_cast(vec)->values[opt.index] = param.b(); - break; - default: - ctx->throw_exception("Unsupported output vector variable type", opt.it_range); - } - } else { - assert(opt.opt->is_scalar()); - ConfigOption *wropt = const_cast(opt.opt); - switch (wropt->type()) { - case coFloat: - check_numeric(param); - static_cast(wropt)->value = param.as_d(); - break; - case coInt: - check_numeric(param); - static_cast(wropt)->value = param.as_i(); - break; - case coString: - static_cast(wropt)->value = param.to_string(); - break; - case coPercent: - check_numeric(param); - static_cast(wropt)->value = param.as_d(); - break; - case coBool: - if (param.type() != expr::TYPE_BOOL) - ctx->throw_exception("Right side is not a boolean expression", param.it_range); - static_cast(wropt)->value = param.b(); - break; - default: - ctx->throw_exception("Unsupported output scalar variable type", opt.it_range); - } - } + if (opt.opt->is_vector()) + vector_variable_element_assign_scalar(ctx, opt, param); + else + scalar_variable_assign_scalar(ctx, opt, param); } template - static void new_vector_variable_array( + static void scalar_variable_new_from_scalar_expression( const MyContext *ctx, bool global_variable, - NewOldVariable &output_variable, - const expr &expr_count, - const expr &expr_value) + NewOldVariable &lhs, + const expr &rhs) { - 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); - } + if (lhs.opt) { + if (lhs.opt->is_vector()) + rhs.throw_exception("Cannot assign a scalar value to a vector variable."); + OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; + scalar_variable_assign_scalar(ctx, lhs_opt, rhs); } 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; + std::unique_ptr opt_new; + switch (rhs.type()) { + case expr::TYPE_BOOL: opt_new = std::make_unique(rhs.b()); break; + case expr::TYPE_INT: opt_new = std::make_unique(rhs.i()); break; + case expr::TYPE_DOUBLE: opt_new = std::make_unique(rhs.d()); break; + case expr::TYPE_STRING: opt_new = std::make_unique(rhs.s()); break; default: assert(false); } - const_cast(ctx)->store_new_variable(output_variable.name, output_variable.opt, global_variable); + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); } } template - static void assign_vector_variable_array( + static void vector_variable_new_from_array( + const MyContext *ctx, + bool global_variable, + NewOldVariable &lhs, + const expr &rhs_count, + const expr &rhs_value) + { + if (lhs.opt) { + if (lhs.opt->is_scalar()) + rhs_value.throw_exception("Cannot assign a vector value to a scalar variable."); + OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; + vector_variable_assign_expr_with_count(ctx, lhs_opt, rhs_count, rhs_value); + } else { + size_t count = evaluate_count(rhs_count); + std::unique_ptr opt_new; + switch (rhs_value.type()) { + case expr::TYPE_BOOL: opt_new = std::make_unique(count, rhs_value.b()); break; + case expr::TYPE_INT: opt_new = std::make_unique(count, rhs_value.i()); break; + case expr::TYPE_DOUBLE: opt_new = std::make_unique(count, rhs_value.d()); break; + case expr::TYPE_STRING: opt_new = std::make_unique(count, rhs_value.s()); break; + default: assert(false); + } + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); + } + } + + template + static void vector_variable_assign_array( const MyContext *ctx, OptWithPos &lhs, - const expr &expr_count, - const expr &expr_value) + const expr &rhs_count, + const expr &rhs_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); + rhs_value.throw_exception("Cannot assign a vector value to a scalar variable."); + vector_variable_assign_expr_with_count(ctx, lhs, rhs_count, rhs_value); + } + + template + static void fill_vector_from_initializer_list(ConfigOption *opt, const std::vector> &il, RightValueEvaluate rv_eval) { + auto& out = static_cast(opt)->values; + out.clear(); + out.reserve(il.size()); + for (const expr& i : il) + out.emplace_back(rv_eval(i)); + } + + template + static void vector_variable_assign_initializer_list( + const MyContext *ctx, + OptWithPos &lhs, + const std::vector> &il) + { + check_writable(ctx, lhs); + auto check_numeric_vector = [](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(expr_value); - static_cast(opt)->values.assign(count, expr_value.as_d()); + check_numeric_vector(il); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.as_d(); }); break; case coInts: - check_numeric(expr_value); - static_cast(opt)->values.assign(count, expr_value.as_i()); + check_numeric_vector(il); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.as_i(); }); break; case coStrings: - static_cast(opt)->values.assign(count, expr_value.to_string()); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.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()); + for (auto &i : il) + if (i.type() != expr::TYPE_BOOL) + i.throw_exception("Right side is not a boolean expression"); + fill_vector_from_initializer_list(opt, il, [](auto &v){ return v.b(); }); break; default: assert(false); } } template - static void new_vector_variable_initializer_list( + static void vector_variable_new_from_initializer_list( const MyContext *ctx, bool global_variable, - NewOldVariable &output_variable, + NewOldVariable &lhs, const std::vector> &il) { - if (! output_variable.opt) { + if (lhs.opt) { + // Assign to an existing vector variable. + if (lhs.opt->is_scalar()) + ctx->throw_exception("Cannot assign a vector value to a scalar variable.", lhs.it_range); + OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; + vector_variable_assign_initializer_list(ctx, lhs_opt, il); + } else { + // Allocate a new vector variable. // First guesstimate type of the output vector. size_t num_bool = 0; size_t num_int = 0; @@ -1257,186 +1323,53 @@ namespace client case expr::TYPE_STRING: ++ num_string; break; default: assert(false); } + std::unique_ptr opt_new; if (num_string > 0) // Convert everything to strings. - output_variable.opt = new ConfigOptionStrings(); + opt_new = std::make_unique(); 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 + opt_new = std::make_unique(); + } 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); + if (num_double == 0) + opt_new = std::make_unique(); + else + opt_new = std::make_unique(); + } + OptWithPos lhs_opt{ opt_new.get(), lhs.it_range, true }; + vector_variable_assign_initializer_list(ctx, lhs_opt, il); + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); } } template - static void assign_vector_variable_initializer_list( - const MyContext *ctx, - OptWithPos &lhs, - const std::vector> &il) + static void copy_vector_variable_to_vector_variable( + const MyContext *ctx, + OptWithPos &lhs, + const OptWithPos &rhs) { 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; + assert(rhs.opt->is_vector()); + if (! lhs.opt->is_vector()) + ctx->throw_exception("Cannot assign vector to a scalar", lhs.it_range); + if (lhs.opt->type() != rhs.opt->type()) { + // Vector types are not compatible. + switch (lhs.opt->type()) { + case coFloats: + ctx->throw_exception("Left hand side is a float vector, while the right hand side is not.", lhs.it_range); + case coInts: + ctx->throw_exception("Left hand side is an int vector, while the right hand side is not.", lhs.it_range); + case coStrings: + ctx->throw_exception("Left hand side is a string vector, while the right hand side is not.", lhs.it_range); + case coBools: + ctx->throw_exception("Left hand side is a bool vector, while the right hand side is not.", lhs.it_range); + default: + ctx->throw_exception("Left hand side / right hand side vectors are not compatible.", lhs.it_range); + } } - 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; + const_cast(lhs.opt)->set(rhs.opt); } template @@ -1445,49 +1378,53 @@ namespace client } template - static bool assign_vector_variable_copy( + static bool vector_variable_new_from_copy( const MyContext *ctx, - OptWithPos &lhs, - const OptWithPos &src_variable) + bool global_variable, + NewOldVariable &lhs, + const OptWithPos &rhs) { - if (! is_vector_variable_reference(src_variable)) + if (is_vector_variable_reference(rhs)) { + if (lhs.opt) { + OptWithPos lhs_opt{ lhs.opt, lhs.it_range, true }; + copy_vector_variable_to_vector_variable(ctx, lhs_opt, rhs); + } else { + // Clone the vector variable. + std::unique_ptr opt_new; + if (one_of(rhs.opt->type(), { coFloats, coInts, coStrings, coBools })) + opt_new = std::unique_ptr(rhs.opt->clone()); + else if (rhs.opt->type() == coPercents) + opt_new = std::make_unique(static_cast(rhs.opt)->values); + else + ctx->throw_exception("Duplicating this type of vector variable is not supported", rhs.it_range); + const_cast(ctx)->store_new_variable(lhs.name, std::move(opt_new), global_variable); + } + // Continue parsing. + return true; + } else { // 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) + static bool vector_variable_assign_copy( + const MyContext *ctx, + OptWithPos &lhs, + const OptWithPos &rhs) + { + if (is_vector_variable_reference(rhs)) { + copy_vector_variable_to_vector_variable(ctx, lhs, rhs); + // Continue parsing. + return true; + } else { + // Skip parsing this branch, bactrack. + return false; + } + } + + template + static void initializer_list_append(std::vector> &list, expr &expr) { list.emplace_back(std::move(expr)); } @@ -1910,36 +1847,36 @@ namespace client 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)] + [px::bind(&MyContext::vector_variable_assign_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)] + [px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_assign_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)] + [px::bind(&MyContext::scalar_variable_assign_scalar_expression, _r1, _a, _1)] | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") - [px::bind(&MyContext::assign_vector_variable_array, _r1, _a, _1, _2)] + [px::bind(&MyContext::vector_variable_assign_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)] + [px::bind(&MyContext::vector_variable_new_from_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)] + [px::ref(qi::_pass) = px::bind(&MyContext::vector_variable_new_from_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)] + [px::bind(&MyContext::scalar_variable_new_from_scalar_expression, _r1, _a, _b, _1)] | (kw["array"] > "(" > additive_expression(_r1) > "," > conditional_expression(_r1) > ")") - [px::bind(&MyContext::new_vector_variable_array, _r1, _a, _b, _1, _2)] + [px::bind(&MyContext::vector_variable_new_from_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)]); + conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)] >> + *(lit(',') > conditional_expression(_r1)[px::bind(&MyContext::initializer_list_append, _val, _1)]); struct FactorActions { static void set_start_pos(Iterator &start_pos, expr &out)