diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 90fd1aa15e..4f7e49d72a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1509,11 +1509,12 @@ void GCodeGenerator::process_layers( }); // The pipeline is variable: The vase mode filter is optional. const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [spiral_vase = this->m_spiral_vase.get()](LayerResult in) -> LayerResult { + [spiral_vase = this->m_spiral_vase.get(), &layers_to_print](LayerResult in) -> LayerResult { if (in.nop_layer_result) return in; spiral_vase->enable(in.spiral_vase_enable); - return { spiral_vase->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_vase->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 { @@ -1602,11 +1603,12 @@ void GCodeGenerator::process_layers( }); // The pipeline is variable: The vase mode filter is optional. const auto spiral_vase = tbb::make_filter(slic3r_tbb_filtermode::serial_in_order, - [spiral_vase = this->m_spiral_vase.get()](LayerResult in)->LayerResult { + [spiral_vase = this->m_spiral_vase.get(), &layers_to_print](LayerResult in)->LayerResult { if (in.nop_layer_result) return in; spiral_vase->enable(in.spiral_vase_enable); - return { spiral_vase->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_vase->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 { diff --git a/src/libslic3r/GCode/SpiralVase.cpp b/src/libslic3r/GCode/SpiralVase.cpp index 4fb52443ac..ca0db227d1 100644 --- a/src/libslic3r/GCode/SpiralVase.cpp +++ b/src/libslic3r/GCode/SpiralVase.cpp @@ -9,10 +9,21 @@ #include "SpiralVase.hpp" #include "GCode.hpp" #include +#include +#include namespace Slic3r { -std::string SpiralVase::process_layer(const std::string &gcode) +static AABBTreeLines::LinesDistancer get_layer_distancer(const std::vector &layer_points) +{ + Linesf lines; + for (size_t idx = 1; idx < layer_points.size(); ++idx) + lines.emplace_back(layer_points[idx - 1].cast(), layer_points[idx].cast()); + + return AABBTreeLines::LinesDistancer{std::move(lines)}; +} + +std::string SpiralVase::process_layer(const std::string &gcode, bool last_layer) { /* This post-processor relies on several assumptions: - all layers are processed through it, including those that are not supposed @@ -21,19 +32,19 @@ std::string SpiralVase::process_layer(const std::string &gcode) at the beginning - each layer is composed by suitable geometry (i.e. a single complete loop) - loops were not clipped before calling this method */ - + // If we're not going to modify G-code, just feed it to the reader // in order to update positions. - if (! m_enabled) { + if (!m_enabled) { m_reader.parse_buffer(gcode); return gcode; } - + // Get total XY length for this layer by summing all extrusion moves. - float total_layer_length = 0; - float layer_height = 0; - float z = 0.f; - + float total_layer_length = 0.f; + float layer_height = 0.f; + float z = 0.f; + { //FIXME Performance warning: This copies the GCodeConfig of the reader. GCodeReader r = m_reader; // clone @@ -53,19 +64,25 @@ std::string SpiralVase::process_layer(const std::string &gcode) } }); } - - // Remove layer height from initial Z. + + // Remove layer height from initial Z. z -= layer_height; - - std::string new_gcode; - //FIXME Tapering of the transition layer only works reliably with relative extruder distances. + + // 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; - float len = 0.f; - m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len] + const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value; + const bool transition_out = last_layer && m_config.use_relative_e_distances.value; + + const AABBTreeLines::LinesDistancer previous_layer_distancer = get_layer_distancer(m_previous_layer); + Vec2f last_point = m_previous_layer.empty() ? Vec2f::Zero() : m_previous_layer.back(); + float len = 0.f; + + std::string new_gcode, transition_gcode; + std::vector current_layer; + m_reader.parse_buffer(gcode, [z, total_layer_length, layer_height, transition_in, transition_out, smooth_spiral = m_smooth_spiral, max_xy_smoothing = m_max_xy_smoothing, + &len, &last_point, &new_gcode, &transition_gcode, ¤t_layer, &previous_layer_distancer] (GCodeReader &reader, GCodeReader::GCodeLine line) { if (line.cmd_is("G1")) { if (line.has_z()) { @@ -74,32 +91,63 @@ std::string SpiralVase::process_layer(const std::string &gcode) line.set(reader, Z, z); new_gcode += line.raw() + '\n'; return; - } else { - float dist_XY = line.dist_XY(reader); - if (dist_XY > 0) { - // horizontal move - if (line.extruding(reader)) { - 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); - new_gcode += line.raw() + '\n'; + } else if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position. + if (const float dist_XY = line.dist_XY(reader); dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract + len += dist_XY; + const 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, 5); + 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 transition_line(line); + transition_line.set(reader, E, line.e() * (1.f - factor), 5); + transition_gcode += transition_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?). */ + // 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 + Vec2f p(line.x(), line.y()); // Get current x/y coordinates + current_layer.emplace_back(p); // Store that point for later use on the next layer + + auto [nearest_distance, idx, nearest_pt] = previous_layer_distancer.distance_from_lines_extra(p.cast()); + if (nearest_distance < max_xy_smoothing) { + // Interpolate between the point on this layer and the point on the previous layer + Vec2f target = nearest_pt.cast() * (1.f - factor) + 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 = (last_point - target).norm(); + // Scale the extrusion amount according to change in length + line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5); + 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?). + 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'; }); - - return new_gcode; + + m_previous_layer = std::move(current_layer); + return new_gcode + transition_gcode; } } diff --git a/src/libslic3r/GCode/SpiralVase.hpp b/src/libslic3r/GCode/SpiralVase.hpp index 8829e8d22b..e3a26f03c8 100644 --- a/src/libslic3r/GCode/SpiralVase.hpp +++ b/src/libslic3r/GCode/SpiralVase.hpp @@ -14,31 +14,40 @@ namespace Slic3r { - -class SpiralVase { +class SpiralVase +{ public: - SpiralVase(const PrintConfig &config) : m_config(config) + SpiralVase() = delete; + + explicit SpiralVase(const PrintConfig &config) : m_config(config) { - m_reader.z() = (float)m_config.z_offset; + m_reader.z() = (float) m_config.z_offset; m_reader.apply_config(m_config); + + const double max_nozzle_diameter = *std::max_element(config.nozzle_diameter.values.begin(), config.nozzle_diameter.values.end()); + m_max_xy_smoothing = float(2. * max_nozzle_diameter); }; - void enable(bool en) { - m_transition_layer = en && ! m_enabled; - m_enabled = en; + void enable(bool enable) + { + m_transition_layer = enable && !m_enabled; + m_enabled = enable; } - std::string process_layer(const std::string &gcode); + std::string process_layer(const std::string &gcode, bool last_layer); private: const PrintConfig &m_config; GCodeReader m_reader; + float m_max_xy_smoothing = 0.f; 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 = true; + std::vector m_previous_layer; }; - } #endif // slic3r_SpiralVase_hpp_