Full re-write of spiral vase (#3091)

* 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

* - Increasing E to 5 decimal digits (I observed uneven flow with less than that)
- Excluding all travel moves (I saw a bug where somehow we ended up with travel moves within the print so excluding all travel moves)

* - max_xy_smoothing is now configurable, default is 200% of nozzle_diameter
- fixed no-op travel moves in the middle of spiral that now show up as defects when Smooth Spiral is enabled!

* - Avoiding namespace pollution
- Fixing dist_XY == 0 bug

---------

Co-authored-by: Andrew Boktor <aboktor@microsoft.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
andrewboktor 2023-12-16 21:48:21 -08:00 committed by GitHub
parent 995ed049cb
commit 9acd550a9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 181 additions and 28 deletions

View file

@ -1,10 +1,69 @@
#include "SpiralVase.hpp"
#include "GCode.hpp"
#include <sstream>
#include <cmath>
#include <limits>
namespace Slic3r {
std::string SpiralVase::process_layer(const std::string &gcode)
namespace SpiralVaseHelpers {
/** == Smooth Spiral Helpers == */
/** Distance between a and b */
float distance(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) { return sqrt(pow(a.x - b.x, 2) + pow(a.y - b.y, 2)); }
SpiralVase::SpiralPoint subtract(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b)
{
return SpiralVase::SpiralPoint(a.x - b.x, a.y - b.y);
}
SpiralVase::SpiralPoint add(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) { return SpiralVase::SpiralPoint(a.x + b.x, a.y + b.y); }
SpiralVase::SpiralPoint scale(SpiralVase::SpiralPoint a, float factor) { return SpiralVase::SpiralPoint(a.x * factor, a.y * factor); }
/** dot product */
float dot(SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b) { return a.x * b.x + a.y * b.y; }
/** Find the point on line ab closes to point c */
SpiralVase::SpiralPoint nearest_point_on_line(SpiralVase::SpiralPoint c, SpiralVase::SpiralPoint a, SpiralVase::SpiralPoint b, float& dist)
{
SpiralVase::SpiralPoint ab = subtract(b, a);
SpiralVase::SpiralPoint ca = subtract(c, a);
float t = dot(ca, ab) / dot(ab, ab);
t = t > 1 ? 1 : t;
t = t < 0 ? 0 : t;
SpiralVase::SpiralPoint closest = SpiralVase::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 */
SpiralVase::SpiralPoint nearest_point_on_lines(SpiralVase::SpiralPoint p,
std::vector<SpiralVase::SpiralPoint>* points,
bool& found,
float& dist)
{
if (points->size() < 2) {
found = false;
return SpiralVase::SpiralPoint(0, 0);
}
float min = std::numeric_limits<float>::max();
SpiralVase::SpiralPoint closest(0, 0);
for (unsigned long i = 0; i < points->size() - 1; i++) {
float currentDist = 0;
SpiralVase::SpiralPoint current = nearest_point_on_line(p, points->at(i), points->at(i + 1), currentDist);
if (currentDist < min) {
min = currentDist;
closest = current;
found = true;
}
}
dist = min;
return closest;
}
} // namespace SpiralVase
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
@ -45,19 +104,26 @@ std::string SpiralVase::process_layer(const std::string &gcode)
}
});
}
// Remove layer height from initial Z.
z -= layer_height;
std::vector<SpiralVase::SpiralPoint>* current_layer = new std::vector<SpiralVase::SpiralPoint>();
std::vector<SpiralVase::SpiralPoint>* previous_layer = m_previous_layer;
bool smooth_spiral = m_smooth_spiral;
std::string new_gcode;
std::string transition_gcode;
float max_xy_dist_for_smoothing = m_max_xy_smoothing;
//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]
SpiralVase::SpiralPoint last_point = previous_layer != NULL && previous_layer->size() >0? previous_layer->at(previous_layer->size()-1): SpiralVase::SpiralPoint(0,0);
m_reader.parse_buffer(gcode, [&new_gcode, &z, total_layer_length, layer_height, transition_in, &len, &current_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()) {
@ -68,30 +134,69 @@ std::string SpiralVase::process_layer(const std::string &gcode)
return;
} else {
float dist_XY = line.dist_XY(reader);
if (dist_XY > 0) {
// horizontal move
if (line.extruding(reader)) {
if (line.has_x() || line.has_y()) { // Sometimes lines have X/Y but the move is to the last position
if (dist_XY > 0 && line.extruding(reader)) { // Exclude wipe and retract
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, 5 /*decimal_digits*/);
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), 5 /*decimal_digits*/);
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
SpiralVase::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;
SpiralVase::SpiralPoint nearestp = SpiralVaseHelpers::nearest_point_on_lines(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
SpiralVase::SpiralPoint target = SpiralVaseHelpers::add(SpiralVaseHelpers::scale(nearestp, 1 - factor), SpiralVaseHelpers::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 = SpiralVaseHelpers::distance(last_point, target);
// Scale the extrusion amount according to change in length
line.set(reader, E, line.e() * modified_dist_XY / dist_XY, 5 /*decimal_digits*/);
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;
}
}

View file

@ -6,12 +6,23 @@
namespace Slic3r {
class SpiralVase {
class SpiralVase
{
public:
class SpiralPoint
{
public:
SpiralPoint(float paramx, float paramy) : x(paramx), y(paramy) {}
public:
float x, y;
};
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,17 +30,22 @@ public:
m_enabled = en;
}
std::string process_layer(const std::string &gcode);
std::string process_layer(const std::string &gcode, bool last_layer);
void set_max_xy_smoothing(float max) {
m_max_xy_smoothing = max;
}
private:
const PrintConfig &m_config;
GCodeReader m_reader;
float m_max_xy_smoothing;
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<SpiralPoint> * m_previous_layer;
};
}
#endif // slic3r_SpiralVase_hpp_