Files
OrcaSlicer-KX/src/libslic3r/GCode/SpiralVase.cpp
Kaarel Pärtel 3105bab18e Add minimum flow rates for spiral vase transitsions (#8333)
Add minimum flow ratios for spiral vase transitsions

Currently when starting the spiral vase the extrusion rate is ramped
from 0 to 100% on the first layer and from 100% to 0% on the last layer.
In some cases it can lead to underextrusion at the beginning and end of
the spiral.

This change adds minimum flow ratio options for the beginning and the end
of the vase. This means that instead of ramping from 0% to 100% it
instead ramps from for example 20% to 100%.

This issue has been reported in SuperSlicer
https://github.com/supermerill/SuperSlicer/issues/4195
2025-02-17 08:55:23 +08:00

218 lines
11 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "SpiralVase.hpp"
#include "GCode.hpp"
#include <sstream>
#include <cmath>
#include <limits>
namespace Slic3r {
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
to be transformed, in order to update the reader with the XY positions
- each call to this method includes a full layer, with a single Z move
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) {
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;
{
//FIXME Performance warning: This copies the GCodeConfig of the reader.
GCodeReader r = m_reader; // clone
bool set_z = false;
r.parse_buffer(gcode, [&total_layer_length, &layer_height, &z, &set_z]
(GCodeReader &reader, const GCodeReader::GCodeLine &line) {
if (line.cmd_is("G1")) {
if (line.extruding(reader)) {
total_layer_length += line.dist_XY(reader);
} else if (line.has(Z)) {
layer_height += line.dist_Z(reader);
if (!set_z) {
z = line.new_Z(reader);
set_z = true;
}
}
}
});
}
// 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_in = m_transition_layer && m_config.use_relative_e_distances.value;
bool transition_out = last_layer && m_config.use_relative_e_distances.value;
float starting_flowrate = float(m_config.spiral_starting_flow_ratio.value);
float finishing_flowrate = float(m_config.spiral_finishing_flow_ratio.value);
float len = 0.f;
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, starting_flowrate, finishing_flowrate]
(GCodeReader &reader, GCodeReader::GCodeLine line) {
if (line.cmd_is("G1")) {
// Orca: Filter out retractions at layer change
if (line.retracting(reader) || (line.extruding(reader) && line.dist_XY(reader) < EPSILON)) return;
if (line.has_z() && !line.retracting(reader)) {
// If this is the initial Z move of the layer, replace it with a
// (redundant) move to the last Z of previous layer.
line.set(Z, z);
new_gcode += line.raw() + '\n';
return;
} else {
float dist_XY = line.dist_XY(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;
float factor = len / total_layer_length;
if (transition_in){
// Transition layer, interpolate the amount of extrusion starting from spiral_vase_starting_flow_rate to 100%.
float starting_e_factor = starting_flowrate + (factor * (1.f - starting_flowrate));
line.set(E, line.e() * starting_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
// As with transition_in, the amount is ramped down from 100% to spiral_vase_finishing_flow_rate
GCodeReader::GCodeLine transitionLine(line);
float finishing_e_factor = finishing_flowrate + ((1.f -factor) * (1.f - finishing_flowrate));
transitionLine.set(E, line.e() * finishing_e_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(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));
// Remove tiny movement
// We need to figure out the distance of this new line!
float modified_dist_XY = SpiralVaseHelpers::distance(last_point, target);
if (modified_dist_XY < 0.001)
line.clear();
else {
line.set(X, target.x);
line.set(Y, target.y);
// Scale the extrusion amount according to change in length
line.set(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?).
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 + transition_gcode;
}
}