mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-03 00:30:40 +08:00
SPE-2111: Rewrite the spiral vase with interpolation between layers.
- Adds transition out to prevent a sharp edge at the top of the spiral vase. - Interpolation in the X and Y axes is used whether vase mode is enabled. - Maximum smoothing is automatically calculated from the nozzle diameter. This commit comes from https://github.com/prusa3d/PrusaSlicer/pull/12142 with some refactoring. Co-authored-by: Andrew Boktor <aboktor@microsoft.com> Co-authored-by: SoftFever <softfeverever@gmail.com> Co-authored-by: Vovodroid <vovodroid@users.noreply.github.com> Co-authored-by: Tom Glastonbury <t@tg73.net> Co-authored-by: Lukáš Hejl <hejl.lukas@gmail.com>
This commit is contained in:
parent
b1f5ef354b
commit
122d876bca
@ -1509,11 +1509,12 @@ void GCodeGenerator::process_layers(
|
|||||||
});
|
});
|
||||||
// The pipeline is variable: The vase mode filter is optional.
|
// The pipeline is variable: The vase mode filter is optional.
|
||||||
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(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)
|
if (in.nop_layer_result)
|
||||||
return in;
|
return in;
|
||||||
spiral_vase->enable(in.spiral_vase_enable);
|
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<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||||
[pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
|
[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.
|
// The pipeline is variable: The vase mode filter is optional.
|
||||||
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(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)
|
if (in.nop_layer_result)
|
||||||
return in;
|
return in;
|
||||||
spiral_vase->enable(in.spiral_vase_enable);
|
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<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
|
||||||
[pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
|
[pressure_equalizer = this->m_pressure_equalizer.get()](LayerResult in) -> LayerResult {
|
||||||
|
@ -9,10 +9,21 @@
|
|||||||
#include "SpiralVase.hpp"
|
#include "SpiralVase.hpp"
|
||||||
#include "GCode.hpp"
|
#include "GCode.hpp"
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
std::string SpiralVase::process_layer(const std::string &gcode)
|
static AABBTreeLines::LinesDistancer<Linef> get_layer_distancer(const std::vector<Vec2f> &layer_points)
|
||||||
|
{
|
||||||
|
Linesf lines;
|
||||||
|
for (size_t idx = 1; idx < layer_points.size(); ++idx)
|
||||||
|
lines.emplace_back(layer_points[idx - 1].cast<double>(), layer_points[idx].cast<double>());
|
||||||
|
|
||||||
|
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:
|
/* This post-processor relies on several assumptions:
|
||||||
- all layers are processed through it, including those that are not supposed
|
- all layers are processed through it, including those that are not supposed
|
||||||
@ -24,14 +35,14 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||||||
|
|
||||||
// If we're not going to modify G-code, just feed it to the reader
|
// If we're not going to modify G-code, just feed it to the reader
|
||||||
// in order to update positions.
|
// in order to update positions.
|
||||||
if (! m_enabled) {
|
if (!m_enabled) {
|
||||||
m_reader.parse_buffer(gcode);
|
m_reader.parse_buffer(gcode);
|
||||||
return gcode;
|
return gcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get total XY length for this layer by summing all extrusion moves.
|
// Get total XY length for this layer by summing all extrusion moves.
|
||||||
float total_layer_length = 0;
|
float total_layer_length = 0.f;
|
||||||
float layer_height = 0;
|
float layer_height = 0.f;
|
||||||
float z = 0.f;
|
float z = 0.f;
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -54,18 +65,24 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove layer height from initial Z.
|
// Remove layer height from initial Z.
|
||||||
z -= layer_height;
|
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.
|
// For absolute extruder distances it will be switched off.
|
||||||
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
// Tapering the absolute extruder distances requires to process every extrusion value after the first transition
|
||||||
// layer.
|
// layer.
|
||||||
bool transition = m_transition_layer && m_config.use_relative_e_distances.value;
|
const bool transition_in = m_transition_layer && m_config.use_relative_e_distances.value;
|
||||||
float layer_height_factor = layer_height / total_layer_length;
|
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;
|
float len = 0.f;
|
||||||
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height_factor, transition, &len]
|
|
||||||
|
std::string new_gcode, transition_gcode;
|
||||||
|
std::vector<Vec2f> 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) {
|
(GCodeReader &reader, GCodeReader::GCodeLine line) {
|
||||||
if (line.cmd_is("G1")) {
|
if (line.cmd_is("G1")) {
|
||||||
if (line.has_z()) {
|
if (line.has_z()) {
|
||||||
@ -74,32 +91,63 @@ std::string SpiralVase::process_layer(const std::string &gcode)
|
|||||||
line.set(reader, Z, z);
|
line.set(reader, Z, z);
|
||||||
new_gcode += line.raw() + '\n';
|
new_gcode += line.raw() + '\n';
|
||||||
return;
|
return;
|
||||||
} else {
|
} else if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position.
|
||||||
float dist_XY = line.dist_XY(reader);
|
if (const float dist_XY = line.dist_XY(reader); dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract
|
||||||
if (dist_XY > 0) {
|
|
||||||
// horizontal move
|
|
||||||
if (line.extruding(reader)) {
|
|
||||||
len += dist_XY;
|
len += dist_XY;
|
||||||
line.set(reader, Z, z + len * layer_height_factor);
|
const float factor = len / total_layer_length;
|
||||||
if (transition && line.has(E))
|
if (transition_in)
|
||||||
// Transition layer, modulate the amount of extrusion from zero to the final value.
|
// Transition layer, interpolate the amount of extrusion from zero to the final value.
|
||||||
line.set(reader, E, line.value(E) * len / total_layer_length);
|
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';
|
||||||
|
}
|
||||||
|
// 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<false>(p.cast<double>());
|
||||||
|
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<float>() * (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';
|
new_gcode += line.raw() + '\n';
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Skip travel moves: the move to first perimeter point will
|
/* Skip travel moves: the move to first perimeter point will
|
||||||
cause a visible seam when loops are not aligned in XY; by skipping
|
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
|
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
|
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';
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,31 +14,40 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class SpiralVase
|
||||||
class SpiralVase {
|
{
|
||||||
public:
|
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);
|
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) {
|
void enable(bool enable)
|
||||||
m_transition_layer = en && ! m_enabled;
|
{
|
||||||
m_enabled = en;
|
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:
|
private:
|
||||||
const PrintConfig &m_config;
|
const PrintConfig &m_config;
|
||||||
GCodeReader m_reader;
|
GCodeReader m_reader;
|
||||||
|
float m_max_xy_smoothing = 0.f;
|
||||||
|
|
||||||
bool m_enabled = false;
|
bool m_enabled = false;
|
||||||
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
// First spiral vase layer. Layer height has to be ramped up from zero to the target layer height.
|
||||||
bool m_transition_layer = false;
|
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<Vec2f> m_previous_layer;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // slic3r_SpiralVase_hpp_
|
#endif // slic3r_SpiralVase_hpp_
|
||||||
|
Loading…
x
Reference in New Issue
Block a user