From fea308e516d276b6a91d9e5b3e6a3b05c7a86eb8 Mon Sep 17 00:00:00 2001 From: Andrew Boktor Date: Sun, 3 Dec 2023 00:31:37 -0800 Subject: [PATCH] Practically full re-write of spiral vase - Adds transition out to prevent sharp edge at the top of spiral vase. - Adds XY interpolation - Adds option to turn XY interpolation on/off --- src/libslic3r/GCode.cpp | 10 +- src/libslic3r/GCode/SpiralVase.cpp | 137 +++++++++++++++++++++++--- src/libslic3r/GCode/SpiralVase.hpp | 19 +++- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 7 ++ src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/GUI/ConfigManipulation.cpp | 1 + src/slic3r/GUI/Tab.cpp | 1 + 8 files changed, 158 insertions(+), 20 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9907dd70d68..0a47df968cc 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2729,12 +2729,13 @@ void GCode::process_layers( }); const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_mode = *this->m_spiral_vase.get()](LayerResult in) -> LayerResult { + [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult { if (in.nop_layer_result) return in; spiral_mode.enable(in.spiral_vase_enable); - return { spiral_mode.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + bool last_layer = in.layer_id==layers_to_print.size()-1; + return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush}; }); const auto pressure_equalizer = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult { @@ -2810,9 +2811,10 @@ void GCode::process_layers( } }); const auto spiral_mode = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [&spiral_mode = *this->m_spiral_vase.get()](LayerResult in)->LayerResult { + [&spiral_mode = *this->m_spiral_vase.get(), &layers_to_print](LayerResult in)->LayerResult { spiral_mode.enable(in.spiral_vase_enable); - return { spiral_mode.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; + bool last_layer = in.layer_id==layers_to_print.size()-1; + return { spiral_mode.process_layer(std::move(in.gcode), last_layer), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush }; }); const auto cooling = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, [&cooling_buffer = *this->m_cooling_buffer.get()](LayerResult in)->std::string { diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index c3caee2dc42..85af021ea1f 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -1,10 +1,70 @@ #include "SpiralVase.hpp" #include "GCode.hpp" #include +#include +#include namespace Slic3r { -std::string SpiralVase::process_layer(const std::string &gcode) +/** == Smooth Spiral Helpers == */ +/** Distance between a and b */ +float distance(SpiralPoint a, SpiralPoint b) { + return sqrt(pow(a.x-b.x,2)+pow(a.y-b.y, 2)); +} + +SpiralPoint subtract(SpiralPoint a, SpiralPoint b) { + return SpiralPoint(a.x-b.x, a.y-b.y); +} + +SpiralPoint add(SpiralPoint a, SpiralPoint b) { + return SpiralPoint(a.x+b.x, a.y+b.y); +} + +SpiralPoint scale(SpiralPoint a, float factor){ + return SpiralPoint(a.x*factor, a.y*factor); +} + +/** dot product */ +float dot(SpiralPoint a, SpiralPoint b) { + return a.x*b.x+a.y*b.y; +} + +/** Find the point on line ab closes to point c */ +SpiralPoint nearest_point_on_line(SpiralPoint c, SpiralPoint a, SpiralPoint b, float& dist) { + SpiralPoint ab = subtract(b, a); + SpiralPoint ca = subtract(c, a); + float t = dot(ca, ab)/dot(ab,ab); + t=t>1?1:t; + t=t<0?0:t; + SpiralPoint closest= SpiralPoint(add(a, scale(ab, t))); + dist = distance(c, closest); + return closest; +} + +/** Given a set of lines defined by points such as line[n] is the line from points[n] to points[n+1], + * find the closest point to p that falls on any of the lines */ +SpiralPoint nearest_point_on_polygon(SpiralPoint p, std::vector* points, bool& found, float& dist) { + if(points->size()<2) { + found=false; + return SpiralPoint(0,0); + } + float min = std::numeric_limits::max(); + SpiralPoint closest(0,0); + for(unsigned long i=0; isize()-1; i++) { + float currentDist=0; + SpiralPoint current = nearest_point_on_line(p, points->at(i), points->at(i+1), currentDist); + if(currentDist* current_layer = new std::vector(); + std::vector* previous_layer = m_previous_layer; + + bool smooth_spiral = m_smooth_spiral; std::string new_gcode; + std::string transition_gcode; + // TODO: This should be proportional to line_width. Something like 2*line_width should be pretty good. + float max_xy_dist_for_smoothing = 0.8; // Made up threshold to prevent snapping to points too far away, Cura uses (2*line_width)^2 //FIXME Tapering of the transition layer only works reliably with relative extruder distances. // For absolute extruder distances it will be switched off. // Tapering the absolute extruder distances requires to process every extrusion value after the first transition // layer. - bool transition = m_transition_layer && m_config.use_relative_e_distances.value; - float layer_height_factor = layer_height / total_layer_length; + bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value; + bool transition_out = last_layer && m_config.use_relative_e_distances.value; float len = 0.f; - m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] + SpiralPoint last_point = previous_layer != NULL && previous_layer->size() >0? previous_layer->at(previous_layer->size()-1): SpiralPoint(0,0); + m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, ¤t_layer, &previous_layer, &transition_gcode, transition_out, smooth_spiral, &max_xy_dist_for_smoothing, &last_point] (GCodeReader &reader, GCodeReader::GCodeLine line) { if (line.cmd_is("G1")) { if (line.has_z()) { @@ -70,28 +138,69 @@ std::string SpiralVase::process_layer(const std::string &gcode) float dist_XY = line.dist_XY(reader); if (dist_XY > 0) { // horizontal move - if (line.extruding(reader)) { + if (line.extruding(reader)) { // We need this to exclude retract and wipe moves! len += dist_XY; - line.set(reader, Z, z + len * layer_height_factor); - if (transition && line.has(E)) - // Transition layer, modulate the amount of extrusion from zero to the final value. - line.set(reader, E, line.value(E) * len / total_layer_length); + float factor = len / total_layer_length; + if (transition_in) + // Transition layer, interpolate the amount of extrusion from zero to the final value. + line.set(reader, E, line.e() * factor); + else if (transition_out) { + // We want the last layer to ramp down extrusion, but without changing z height! + // So clone the line before we mess with its Z and duplicate it into a new layer that ramps down E + // We add this new layer at the very end + GCodeReader::GCodeLine transitionLine(line); + transitionLine.set(reader, E, line.e() * (1 - factor)); + transition_gcode += transitionLine.raw() + '\n'; + } + // This line is the core of Spiral Vase mode, ramp up the Z smoothly + line.set(reader, Z, z + factor * layer_height); + if (smooth_spiral) { + // Now we also need to try to interpolate X and Y + SpiralPoint p(line.x(), line.y()); // Get current x/y coordinates + current_layer->push_back(p); // Store that point for later use on the next layer + + if (previous_layer != NULL) { + bool found = false; + float dist = 0; + SpiralPoint nearestp = nearest_point_on_polygon(p, previous_layer, found, dist); + if (found && dist < max_xy_dist_for_smoothing) { + // Interpolate between the point on this layer and the point on the previous layer + SpiralPoint target = add(scale(nearestp, 1 - factor), scale(p, factor)); + line.set(reader, X, target.x); + line.set(reader, Y, target.y); + // We need to figure out the distance of this new line! + float modified_dist_XY = distance(last_point, target); + line.set(reader, E, + line.e() * modified_dist_XY / dist_XY); // Scale the extrusion amount according to change in length + last_point = target; + } else { + last_point = p; + } + } + } new_gcode += line.raw() + '\n'; } return; - /* Skip travel moves: the move to first perimeter point will cause a visible seam when loops are not aligned in XY; by skipping it we blend the first loop move in the XY plane (although the smoothness of such blend depend on how long the first segment is; maybe we should - enforce some minimum length?). */ + enforce some minimum length?). + When smooth_spiral is enabled, we're gonna end up exactly where the next layer should + start anyway, so we don't need the travel move */ } } } new_gcode += line.raw() + '\n'; + if(transition_out) { + transition_gcode += line.raw() + '\n'; + } }); + + delete m_previous_layer; + m_previous_layer = current_layer; - return new_gcode; + return new_gcode + transition_gcode; } } diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index fb461c20151..6ce6455f6e0 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -6,12 +6,24 @@ namespace Slic3r { +class SpiralPoint +{ + public: + SpiralPoint(float paramx, float paramy) : x(paramx), y(paramy) {} + public: + float x, y; + +}; + class SpiralVase { + public: SpiralVase(const PrintConfig &config) : m_config(config) { m_reader.z() = (float)m_config.z_offset; m_reader.apply_config(m_config); + m_previous_layer = NULL; + m_smooth_spiral = config.spiral_mode_smooth; }; void enable(bool en) { @@ -19,7 +31,7 @@ class SpiralVase { m_enabled = en; } - std::string process_layer(const std::string &gcode); + std::string process_layer(const std::string &gcode, bool last_layer); private: const PrintConfig &m_config; @@ -28,8 +40,13 @@ class SpiralVase { bool m_enabled = false; // First spiral vase layer. Layer height has to be ramped up from zero to the target layer height. bool m_transition_layer = false; + // Whether to interpolate XY coordinates with the previous layer. Results in no seam at layer changes + bool m_smooth_spiral = false; + std::vector * m_previous_layer; }; + + } #endif // slic3r_SpiralVase_hpp_ diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d0356954cef..7188f20c7a2 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -766,7 +766,7 @@ bool Preset::has_cali_lines(PresetBundle* preset_bundle) } static std::vector s_Preset_print_options { - "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "slicing_mode", + "layer_height", "initial_layer_print_height", "wall_loops", "slice_closing_radius", "spiral_mode", "spiral_mode_smooth", "slicing_mode", "top_shell_layers", "top_shell_thickness", "bottom_shell_layers", "bottom_shell_thickness", "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "reduce_crossing_wall", "detect_thin_wall", "detect_overhang_wall", "overhang_reverse", "overhang_reverse_threshold","overhang_reverse_internal_only", "seam_position", "staggered_inner_seams", "wall_sequence", "is_infill_first", "sparse_infill_density", "sparse_infill_pattern", "top_surface_pattern", "bottom_surface_pattern", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c7281cdd3be..7ff8084e64c 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3427,6 +3427,13 @@ def = this->add("filament_loading_speed", coFloats); def->mode = comSimple; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("spiral_mode_smooth", coBool); + def->label = L("Smooth Spiral"); + def->tooltip = L("Smooth Spiral smoothes out X and Y moves as well" + "resulting in no visible seam at all, even in the XY directions on walls that are not vertical"); + def->mode = comSimple; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("timelapse_type", coEnum); def->label = L("Timelapse"); def->tooltip = L("If smooth or traditional mode is selected, a timelapse video will be generated for each print. " diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 8d63e543ae3..47f364e89de 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1120,6 +1120,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloat, skirt_speed)) ((ConfigOptionFloats, slow_down_layer_time)) ((ConfigOptionBool, spiral_mode)) + ((ConfigOptionBool, spiral_mode_smooth)) ((ConfigOptionInt, standby_temperature_delta)) ((ConfigOptionInts, nozzle_temperature)) ((ConfigOptionBools, wipe)) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 9964b37df97..24f2158f305 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -505,6 +505,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("infill_anchor", has_infill_anchors); bool has_spiral_vase = config->opt_bool("spiral_mode"); + toggle_line("spiral_mode_smooth", has_spiral_vase); bool has_top_solid_infill = config->opt_int("top_shell_layers") > 0; bool has_bottom_solid_infill = config->opt_int("bottom_shell_layers") > 0; bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 30568b78fcb..2913519b07a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2159,6 +2159,7 @@ void TabPrint::build() optgroup->append_single_option_line("slicing_mode"); optgroup->append_single_option_line("print_sequence", "sequent-print"); optgroup->append_single_option_line("spiral_mode", "spiral-vase"); + optgroup->append_single_option_line("spiral_mode_smooth", "spiral-vase-smooth"); optgroup->append_single_option_line("timelapse_type", "Timelapse"); optgroup->append_single_option_line("fuzzy_skin");