Reworked pressure equalizer and GCode processing pipeline to make pressure equalizer always returns one whole layer at once.

Now pressure equalizer is returning one layer back (the previous layer).

GCode produced by pressure equalizer now has the same number of decimal places as non-processed GCode.

Pressure equalizer was disabled for external perimeters and gap-fill.
This commit is contained in:
Lukáš Hejl 2022-06-09 21:00:14 +02:00
parent a497769558
commit 9c07218d82
4 changed files with 253 additions and 252 deletions

View File

@ -1529,11 +1529,18 @@ void GCode::process_layers(
{
// The pipeline is variable: The vase mode filter is optional.
size_t layer_to_print_idx = 0;
const auto generator = tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> GCode::LayerResult {
if (layer_to_print_idx == layers_to_print.size()) {
fc.stop();
return {};
const auto generator = tbb::make_filter<void, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &print_object_instances_ordering, &layers_to_print, &layer_to_print_idx](tbb::flow_control& fc) -> LayerResult {
if (layer_to_print_idx >= layers_to_print.size()) {
if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) {
fc.stop();
return {};
} else {
// Pressure equalizer need insert empty input. Because it returns one layer back.
// Insert NOP (no operation) layer;
++layer_to_print_idx;
return LayerResult::make_nop_layer_result();
}
} else {
const std::pair<coordf_t, std::vector<LayerToPrint>>& layer = layers_to_print[layer_to_print_idx++];
const LayerTools& layer_tools = tool_ordering.tools_for_layer(layer.first);
@ -1543,17 +1550,23 @@ void GCode::process_layers(
return this->process_layer(print, layer.second, layer_tools, &layer == &layers_to_print.back(), &print_object_instances_ordering, size_t(-1));
}
});
const auto spiral_vase = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&spiral_vase = *this->m_spiral_vase](GCode::LayerResult in) -> GCode::LayerResult {
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&spiral_vase = *this->m_spiral_vase](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, in.pressure_equalizer_buffer_flush };
return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush};
});
const auto pressure_equalizer = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&pressure_equalizer = *this->m_pressure_equalizer](GCode::LayerResult in) -> GCode::LayerResult {
return { std::string(pressure_equalizer.process_layer(std::move(in.gcode.c_str()), in.pressure_equalizer_buffer_flush)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.pressure_equalizer_buffer_flush };
const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&pressure_equalizer = *this->m_pressure_equalizer](LayerResult in) -> LayerResult {
return pressure_equalizer.process_layer(std::move(in));
});
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer](GCode::LayerResult in) -> std::string {
const auto cooling = tbb::make_filter<LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer](LayerResult in) -> std::string {
if (in.nop_layer_result)
return in.gcode;
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
});
const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
@ -1601,28 +1614,39 @@ void GCode::process_layers(
{
// The pipeline is variable: The vase mode filter is optional.
size_t layer_to_print_idx = 0;
const auto generator = tbb::make_filter<void, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> GCode::LayerResult {
if (layer_to_print_idx == layers_to_print.size()) {
fc.stop();
return {};
const auto generator = tbb::make_filter<void, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[this, &print, &tool_ordering, &layers_to_print, &layer_to_print_idx, single_object_idx](tbb::flow_control& fc) -> LayerResult {
if (layer_to_print_idx >= layers_to_print.size()) {
if ((!m_pressure_equalizer && layer_to_print_idx == layers_to_print.size()) || (m_pressure_equalizer && layer_to_print_idx == (layers_to_print.size() + 1))) {
fc.stop();
return {};
} else {
// Pressure equalizer need insert empty input. Because it returns one layer back.
// Insert NOP (no operation) layer;
++layer_to_print_idx;
return LayerResult::make_nop_layer_result();
}
} else {
LayerToPrint &layer = layers_to_print[layer_to_print_idx ++];
print.throw_if_canceled();
return this->process_layer(print, { std::move(layer) }, tool_ordering.tools_for_layer(layer.print_z()), &layer == &layers_to_print.back(), nullptr, single_object_idx);
}
});
const auto spiral_vase = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&spiral_vase = *this->m_spiral_vase](GCode::LayerResult in)->GCode::LayerResult {
const auto spiral_vase = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&spiral_vase = *this->m_spiral_vase](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, in.pressure_equalizer_buffer_flush };
return { spiral_vase.process_layer(std::move(in.gcode)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush };
});
const auto pressure_equalizer = tbb::make_filter<GCode::LayerResult, GCode::LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&pressure_equalizer = *this->m_pressure_equalizer](GCode::LayerResult in) -> GCode::LayerResult {
return { std::string(pressure_equalizer.process_layer(std::move(in.gcode.c_str()), in.pressure_equalizer_buffer_flush)), in.layer_id, in.spiral_vase_enable, in.cooling_buffer_flush, in.pressure_equalizer_buffer_flush };
const auto pressure_equalizer = tbb::make_filter<LayerResult, LayerResult>(slic3r_tbb_filtermode::serial_in_order,
[&pressure_equalizer = *this->m_pressure_equalizer](LayerResult in) -> LayerResult {
return pressure_equalizer.process_layer(std::move(in));
});
const auto cooling = tbb::make_filter<GCode::LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer](GCode::LayerResult in)->std::string {
const auto cooling = tbb::make_filter<LayerResult, std::string>(slic3r_tbb_filtermode::serial_in_order,
[&cooling_buffer = *this->m_cooling_buffer](LayerResult in)->std::string {
if (in.nop_layer_result)
return in.gcode;
return cooling_buffer.process_layer(std::move(in.gcode), in.layer_id, in.cooling_buffer_flush);
});
const auto find_replace = tbb::make_filter<std::string, std::string>(slic3r_tbb_filtermode::serial_in_order,
@ -2079,7 +2103,7 @@ namespace Skirt {
// In non-sequential mode, process_layer is called per each print_z height with all object and support layers accumulated.
// For multi-material prints, this routine minimizes extruder switches by gathering extruder specific extrusion paths
// and performing the extruder specific extrusions together.
GCode::LayerResult GCode::process_layer(
LayerResult GCode::process_layer(
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
const std::vector<LayerToPrint> &layers,
@ -2110,7 +2134,7 @@ GCode::LayerResult GCode::process_layer(
}
}
const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
GCode::LayerResult result { {}, layer.id(), false, last_layer, last_layer};
LayerResult result { {}, layer.id(), false, last_layer, false};
if (layer_tools.extruders.empty())
// Nothing to extrude.
return result;

View File

@ -114,6 +114,20 @@ public:
static const std::vector<std::string>& get() { return Colors; }
};
struct LayerResult {
std::string gcode;
size_t layer_id;
// Is spiral vase post processing enabled for this layer?
bool spiral_vase_enable { false };
// Should the cooling buffer content be flushed at the end of this layer?
bool cooling_buffer_flush { false };
// Is indicating if this LayerResult should be processed, or it is just inserted artificial LayerResult.
// It is used for the pressure equalizer because it needs to buffer one layer back.
bool nop_layer_result { false };
static LayerResult make_nop_layer_result() { return {"", std::numeric_limits<coord_t>::max(), false, false, true}; }
};
class GCode {
public:
GCode() :
@ -226,16 +240,6 @@ private:
static std::vector<LayerToPrint> collect_layers_to_print(const PrintObject &object);
static std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> collect_layers_to_print(const Print &print);
struct LayerResult {
std::string gcode;
size_t layer_id;
// Is spiral vase post processing enabled for this layer?
bool spiral_vase_enable { false };
// Should the cooling buffer content be flushed at the end of this layer?
bool cooling_buffer_flush { false };
// Should the PressureEqualizer buffer content be flushed at the end of this layer?
bool pressure_equalizer_buffer_flush { false };
};
LayerResult process_layer(
const Print &print,
// Set of object & print layers of the same PrintObject and with the same print_z.
@ -446,6 +450,7 @@ private:
friend class Wipe;
friend class WipeTowerIntegration;
friend class PressureEqualizer;
};
std::vector<const PrintInstance*> sort_object_instances_by_model_order(const Print& print);

View File

@ -5,36 +5,24 @@
#include "../libslic3r.h"
#include "../PrintConfig.hpp"
#include "../LocalesUtils.hpp"
#include "../GCode.hpp"
#include "PressureEqualizer.hpp"
#include "fast_float/fast_float.h"
#include "GCodeWriter.hpp"
namespace Slic3r {
static constexpr const std::array<char, 18> EXTRUSION_ROLE_TAG = {";_EXTRUSION_ROLE:"};
static constexpr const size_t EXTRUSION_ROLE_TAG_LENGTH = EXTRUSION_ROLE_TAG.size() - 1;
static constexpr const std::array<char, 14> EXTRUDE_END_TAG = {";_EXTRUDE_END"};
static constexpr const size_t EXTRUDE_END_TAG_LENGTH = EXTRUDE_END_TAG.size() - 1;
static constexpr const std::array<char, 20> EXTRUDE_SET_SPEED_TAG = {";_EXTRUDE_SET_SPEED"};
static constexpr const size_t EXTRUDE_SET_SPEED_TAG_LENGTH = EXTRUDE_SET_SPEED_TAG.size() - 1;
static constexpr const std::array<char, 21> EXTERNAL_PERIMETER_TAG = {";_EXTERNAL_PERIMETER"};
static constexpr const size_t EXTERNAL_PERIMETER_TAG_LENGTH = EXTERNAL_PERIMETER_TAG.size() - 1;
static const std::string EXTRUSION_ROLE_TAG = ";_EXTRUSION_ROLE:";
static const std::string EXTRUDE_END_TAG = ";_EXTRUDE_END";
static const std::string EXTRUDE_SET_SPEED_TAG = ";_EXTRUDE_SET_SPEED";
static const std::string EXTERNAL_PERIMETER_TAG = ";_EXTERNAL_PERIMETER";
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_config(config)
PressureEqualizer::PressureEqualizer(const Slic3r::GCodeConfig &config) : m_use_relative_e_distances(config.use_relative_e_distances.value)
{
reset();
}
void PressureEqualizer::reset()
{
circular_buffer_pos = 0;
circular_buffer_size = 100;
circular_buffer_items = 0;
circular_buffer.assign(circular_buffer_size, GCodeLine());
// Preallocate some data, so that output_buffer.data() will return an empty string.
output_buffer.assign(32, 0);
output_buffer_length = 0;
output_buffer_length = 0;
m_current_extruder = 0;
// Zero the position of the XYZE axes + the current feed
@ -45,7 +33,7 @@ void PressureEqualizer::reset()
// Calculate filamet crossections for the multiple extruders.
m_filament_crossections.clear();
for (double r : m_config.filament_diameter.values) {
for (double r : config.filament_diameter.values) {
double a = 0.25f * M_PI * r * r;
m_filament_crossections.push_back(float(a));
}
@ -54,86 +42,82 @@ void PressureEqualizer::reset()
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 60mm/s XY movement: 0.45*0.2*60*60=5.4*60 = 324 mm^3/min
// Volumetric rate of a 0.45mm x 0.2mm extrusion at 20mm/s XY movement: 0.45*0.2*20*60=1.8*60 = 108 mm^3/min
// Slope of the volumetric rate, changing from 20mm/s to 60mm/s over 2 seconds: (5.4-1.8)*60*60/2=60*60*1.8 = 6480 mm^3/min^2 = 1.8 mm^3/s^2
m_max_volumetric_extrusion_rate_slope_positive = float(m_config.max_volumetric_extrusion_rate_slope_positive.value) * 60.f * 60.f;
m_max_volumetric_extrusion_rate_slope_negative = float(m_config.max_volumetric_extrusion_rate_slope_negative.value) * 60.f * 60.f;
m_max_volumetric_extrusion_rate_slope_positive = float(config.max_volumetric_extrusion_rate_slope_positive.value) * 60.f * 60.f;
m_max_volumetric_extrusion_rate_slope_negative = float(config.max_volumetric_extrusion_rate_slope_negative.value) * 60.f * 60.f;
for (size_t i = 0; i < numExtrusionRoles; ++ i) {
m_max_volumetric_extrusion_rate_slopes[i].negative = m_max_volumetric_extrusion_rate_slope_negative;
m_max_volumetric_extrusion_rate_slopes[i].positive = m_max_volumetric_extrusion_rate_slope_positive;
for (ExtrusionRateSlope &extrusion_rate_slope : m_max_volumetric_extrusion_rate_slopes) {
extrusion_rate_slope.negative = m_max_volumetric_extrusion_rate_slope_negative;
extrusion_rate_slope.positive = m_max_volumetric_extrusion_rate_slope_positive;
}
// Don't regulate the pressure in infill.
m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].negative = 0;
m_max_volumetric_extrusion_rate_slopes[erBridgeInfill].positive = 0;
// Don't regulate the pressure in gap fill.
m_max_volumetric_extrusion_rate_slopes[erGapFill].negative = 0;
m_max_volumetric_extrusion_rate_slopes[erGapFill].positive = 0;
// Don't regulate the pressure in infill and gap fill.
// TODO: Do we want to regulate pressure in erWipeTower, erCustom and erMixed?
for (const ExtrusionRole er : {erBridgeInfill, erGapFill}) {
m_max_volumetric_extrusion_rate_slopes[er].negative = 0;
m_max_volumetric_extrusion_rate_slopes[er].positive = 0;
}
#ifdef PRESSURE_EQUALIZER_STATISTIC
m_stat.reset();
#endif
#ifdef PRESSURE_EQUALIZER_DEBUG
line_idx = 0;
#endif
}
const char* PressureEqualizer::process_layer(const char *szGCode, bool flush)
void PressureEqualizer::process_layer(const std::string &gcode)
{
// Reset length of the output_buffer.
output_buffer_length = 0;
if (szGCode != nullptr) {
const char *p = szGCode;
while (*p != 0) {
if (!gcode.empty()) {
const char *gcode_begin = gcode.c_str();
while (*gcode_begin != 0) {
// Find end of the line.
const char *endl = p;
const char *gcode_end = gcode_begin;
// Slic3r always generates end of lines in a Unix style.
for (; *endl != 0 && *endl != '\n'; ++ endl) ;
if (circular_buffer_items == circular_buffer_size)
// Buffer is full. Push out the oldest line.
output_gcode_line(circular_buffer[circular_buffer_pos]);
else
++ circular_buffer_items;
// Process a G-code line, store it into the provided GCodeLine object.
size_t idx_tail = circular_buffer_pos;
circular_buffer_pos = circular_buffer_idx_next(circular_buffer_pos);
if (! process_line(p, endl, circular_buffer[idx_tail])) {
// The line has to be forgotten. It contains comment marks, which shall be
// filtered out of the target g-code.
circular_buffer_pos = idx_tail;
-- circular_buffer_items;
for (; *gcode_end != 0 && *gcode_end != '\n'; ++gcode_end);
m_gcode_lines.emplace_back();
if (!this->process_line(gcode_begin, gcode_end, m_gcode_lines.back())) {
// The line has to be forgotten. It contains comment marks, which shall be filtered out of the target g-code.
m_gcode_lines.pop_back();
}
p = endl;
if (*p == '\n')
++ p;
gcode_begin = gcode_end;
if (*gcode_begin == '\n')
++gcode_begin;
}
}
}
if (flush) {
// Flush the remaining valid lines of the circular buffer.
for (size_t idx = circular_buffer_idx_head(); circular_buffer_items > 0; -- circular_buffer_items) {
output_gcode_line(circular_buffer[idx]);
if (++ idx == circular_buffer_size)
idx = 0;
}
// Reset the index pointer.
assert(circular_buffer_items == 0);
circular_buffer_pos = 0;
LayerResult PressureEqualizer::process_layer(LayerResult &&input)
{
const bool is_first_layer = m_layer_results.empty();
const size_t next_layer_first_idx = m_gcode_lines.size();
#ifdef PRESSURE_EQUALIZER_STATISTIC
printf("Statistics: \n");
printf("Minimum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_min);
printf("Maximum volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_max);
if (m_stat.extrusion_length > 0)
m_stat.volumetric_extrusion_rate_avg /= m_stat.extrusion_length;
printf("Average volumetric extrusion rate: %f\n", m_stat.volumetric_extrusion_rate_avg);
m_stat.reset();
#endif
}
if (!input.nop_layer_result) {
this->process_layer(input.gcode);
input.gcode.clear(); // GCode is already processed, so it isn't needed to store it.
m_layer_results.emplace(new LayerResult(input));
}
if (is_first_layer) // Buffer previous input result and output NOP.
return LayerResult::make_nop_layer_result();
// Export previous layer.
LayerResult *prev_layer_result = m_layer_results.front();
m_layer_results.pop();
output_buffer_length = 0;
for (size_t line_idx = 0; line_idx < next_layer_first_idx; ++line_idx)
output_gcode_line(m_gcode_lines[line_idx]);
m_gcode_lines.erase(m_gcode_lines.begin(), m_gcode_lines.begin() + int(next_layer_first_idx));
if (output_buffer_length > 0)
return output_buffer.data();
prev_layer_result->gcode = std::string(output_buffer.data());
return "\0";
assert(!input.nop_layer_result || m_layer_results.empty());
LayerResult out = *prev_layer_result;
delete prev_layer_result;
return out;
}
// Is a white space?
@ -186,12 +170,13 @@ static inline float parse_float(const char *&line, const size_t line_length)
bool PressureEqualizer::process_line(const char *line, const char *line_end, GCodeLine &buf)
{
const size_t len = line_end - line;
if (strncmp(line, EXTRUSION_ROLE_TAG.data(), EXTRUSION_ROLE_TAG_LENGTH) == 0) {
line += EXTRUSION_ROLE_TAG_LENGTH;
if (strncmp(line, EXTRUSION_ROLE_TAG.data(), EXTRUSION_ROLE_TAG.length()) == 0) {
line += EXTRUSION_ROLE_TAG.length();
int role = atoi(line);
m_current_extrusion_role = ExtrusionRole(role);
++ line_idx;
#ifdef PRESSURE_EQUALIZER_DEBUG
++line_idx;
#endif
return false;
}
@ -251,7 +236,7 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
throw Slic3r::RuntimeError(std::string("GCode::PressureEqualizer: Invalid axis for G0/G1: ") + axis);
buf.pos_provided[i] = true;
new_pos[i] = parse_float(line, line_end - line);
if (i == 3 && m_config.use_relative_e_distances.value)
if (i == 3 && m_use_relative_e_distances)
new_pos[i] += m_current_pos[i];
changed[i] = new_pos[i] != m_current_pos[i];
eatws(line);
@ -358,7 +343,7 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
{
// Activate an extruder head.
int new_extruder = parse_int(line);
if (new_extruder != m_current_extruder) {
if (new_extruder != int(m_current_extruder)) {
m_current_extruder = new_extruder;
m_retracted = true;
buf.type = GCODELINETYPE_TOOL_CHANGE;
@ -373,8 +358,10 @@ bool PressureEqualizer::process_line(const char *line, const char *line_end, GCo
memcpy(buf.pos_end, m_current_pos, sizeof(float)*5);
adjust_volumetric_rate();
++ line_idx;
return true;
#ifdef PRESSURE_EQUALIZER_DEBUG
++line_idx;
#endif
return true;
}
void PressureEqualizer::output_gcode_line(GCodeLine &line)
@ -486,55 +473,56 @@ void PressureEqualizer::output_gcode_line(GCodeLine &line)
void PressureEqualizer::adjust_volumetric_rate()
{
if (circular_buffer_items < 2)
if (m_gcode_lines.size() < 2)
return;
// Go back from the current circular_buffer_pos and lower the feedtrate to decrease the slope of the extrusion rate changes.
const size_t idx_head = circular_buffer_idx_head();
const size_t idx_tail = circular_buffer_idx_prev(circular_buffer_idx_tail());
size_t idx = idx_tail;
if (idx == idx_head || ! circular_buffer[idx].extruding())
// size_t fist_line_idx = size_t(std::max<int>(0, int(m_gcode_lines.size()) - 100));
size_t fist_line_idx = 0;
const size_t last_line_idx = m_gcode_lines.size() - 1;
size_t line_idx = last_line_idx;
if (line_idx == fist_line_idx || !m_gcode_lines[line_idx].extruding())
// Nothing to do, the last move is not extruding.
return;
float feedrate_per_extrusion_role[numExtrusionRoles];
for (size_t i = 0; i < numExtrusionRoles; ++ i)
feedrate_per_extrusion_role[i] = FLT_MAX;
feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_start;
std::array<float, erCount> feedrate_per_extrusion_role{};
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max());
feedrate_per_extrusion_role[m_gcode_lines[line_idx].extrusion_role] = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
bool modified = true;
while (modified && idx != idx_head) {
size_t idx_prev = circular_buffer_idx_prev(idx);
for (; !circular_buffer[idx_prev].extruding() && idx_prev != idx_head; idx_prev = circular_buffer_idx_prev(idx_prev));
if (!circular_buffer[idx_prev].extruding())
while (line_idx != fist_line_idx) {
size_t idx_prev = line_idx - 1;
for (; !m_gcode_lines[idx_prev].extruding() && idx_prev != fist_line_idx; --idx_prev);
if (!m_gcode_lines[idx_prev].extruding())
break;
// Volumetric extrusion rate at the start of the succeding segment.
float rate_succ = circular_buffer[idx].volumetric_extrusion_rate_start;
float rate_succ = m_gcode_lines[line_idx].volumetric_extrusion_rate_start;
// What is the gradient of the extrusion rate between idx_prev and idx?
idx = idx_prev;
GCodeLine &line = circular_buffer[idx];
for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative;
if (rate_slope == 0)
// The negative rate is unlimited.
continue;
line_idx = idx_prev;
GCodeLine &line = m_gcode_lines[line_idx];
for (size_t iRole = 1; iRole < erCount; ++ iRole) {
const float &rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].negative;
if (rate_slope == 0 || feedrate_per_extrusion_role[iRole] == std::numeric_limits<float>::max())
continue; // The negative rate is unlimited or the rate for ExtrusionRole iRole is unlimited.
float rate_end = feedrate_per_extrusion_role[iRole];
if (iRole == line.extrusion_role && rate_succ < rate_end)
// Limit by the succeeding volumetric flow rate.
rate_end = rate_succ;
if (line.volumetric_extrusion_rate_end > rate_end) {
if (line.extrusion_role == erExternalPerimeter || line.extrusion_role == erGapFill || line.extrusion_role == erBridgeInfill) {
rate_end = line.volumetric_extrusion_rate_end;
} else if (line.volumetric_extrusion_rate_end > rate_end) {
line.volumetric_extrusion_rate_end = rate_end;
line.max_volumetric_extrusion_rate_slope_negative = rate_slope;
line.modified = true;
} else if (iRole == line.extrusion_role) {
rate_end = line.volumetric_extrusion_rate_end;
} else if (rate_end == FLT_MAX) {
// The rate for ExtrusionRole iRole is unlimited.
continue;
} else {
// Use the original, 'floating' extrusion rate as a starting point for the limiter.
}
// modified = false;
float rate_start = rate_end + rate_slope * line.time_corrected();
if (rate_start < line.volumetric_extrusion_rate_start) {
// Limit the volumetric extrusion rate at the start of this segment due to a segment
@ -542,34 +530,35 @@ void PressureEqualizer::adjust_volumetric_rate()
line.volumetric_extrusion_rate_start = rate_start;
line.max_volumetric_extrusion_rate_slope_negative = rate_slope;
line.modified = true;
// modified = true;
}
feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_start : rate_start;
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_start;
}
}
// Go forward and adjust the feedrate to decrease the slope of the extrusion rate changes.
for (size_t i = 0; i < numExtrusionRoles; ++i)
feedrate_per_extrusion_role[i] = FLT_MAX;
feedrate_per_extrusion_role[circular_buffer[idx].extrusion_role] = circular_buffer[idx].volumetric_extrusion_rate_end;
feedrate_per_extrusion_role.fill(std::numeric_limits<float>::max());
feedrate_per_extrusion_role[m_gcode_lines[line_idx].extrusion_role] = m_gcode_lines[line_idx].volumetric_extrusion_rate_end;
assert(circular_buffer[idx].extruding());
while (idx != idx_tail) {
size_t idx_next = circular_buffer_idx_next(idx);
for (; !circular_buffer[idx_next].extruding() && idx_next != idx_tail; idx_next = circular_buffer_idx_next(idx_next));
if (!circular_buffer[idx_next].extruding())
assert(m_gcode_lines[line_idx].extruding());
while (line_idx != last_line_idx) {
size_t idx_next = line_idx + 1;
for (; !m_gcode_lines[idx_next].extruding() && idx_next != last_line_idx; ++idx_next);
if (!m_gcode_lines[idx_next].extruding())
break;
float rate_prec = circular_buffer[idx].volumetric_extrusion_rate_end;
float rate_prec = m_gcode_lines[line_idx].volumetric_extrusion_rate_end;
// What is the gradient of the extrusion rate between idx_prev and idx?
idx = idx_next;
GCodeLine &line = circular_buffer[idx];
for (size_t iRole = 1; iRole < numExtrusionRoles; ++ iRole) {
float rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive;
if (rate_slope == 0)
// The positive rate is unlimited.
continue;
line_idx = idx_next;
GCodeLine &line = m_gcode_lines[line_idx];
for (size_t iRole = 1; iRole < erCount; ++ iRole) {
const float &rate_slope = m_max_volumetric_extrusion_rate_slopes[iRole].positive;
if (rate_slope == 0 || feedrate_per_extrusion_role[iRole] == std::numeric_limits<float>::max())
continue; // The positive rate is unlimited or the rate for ExtrusionRole iRole is unlimited.
float rate_start = feedrate_per_extrusion_role[iRole];
if (iRole == line.extrusion_role && rate_prec < rate_start)
if (line.extrusion_role == erExternalPerimeter || line.extrusion_role == erGapFill || line.extrusion_role == erBridgeInfill) {
rate_start = line.volumetric_extrusion_rate_start;
} else if (iRole == line.extrusion_role && rate_prec < rate_start)
rate_start = rate_prec;
if (line.volumetric_extrusion_rate_start > rate_start) {
line.volumetric_extrusion_rate_start = rate_start;
@ -577,13 +566,10 @@ void PressureEqualizer::adjust_volumetric_rate()
line.modified = true;
} else if (iRole == line.extrusion_role) {
rate_start = line.volumetric_extrusion_rate_start;
} else if (rate_start == FLT_MAX) {
// The rate for ExtrusionRole iRole is unlimited.
continue;
} else {
// Use the original, 'floating' extrusion rate as a starting point for the limiter.
}
float rate_end = (rate_slope == 0) ? FLT_MAX : rate_start + rate_slope * line.time_corrected();
float rate_end = rate_start + rate_slope * line.time_corrected();
if (rate_end < line.volumetric_extrusion_rate_end) {
// Limit the volumetric extrusion rate at the start of this segment due to a segment
// of ExtrusionType iRole, which was extruded before.
@ -591,24 +577,28 @@ void PressureEqualizer::adjust_volumetric_rate()
line.max_volumetric_extrusion_rate_slope_positive = rate_slope;
line.modified = true;
}
feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
// feedrate_per_extrusion_role[iRole] = (iRole == line.extrusion_role) ? line.volumetric_extrusion_rate_end : rate_end;
feedrate_per_extrusion_role[iRole] = line.volumetric_extrusion_rate_end;
}
}
}
void PressureEqualizer::push_axis_to_output(const char axis, const float value, bool add_eol)
inline void PressureEqualizer::push_to_output(GCodeG1Formatter &formatter)
{
char buf[2048];
int len = sprintf(buf, (axis == 'E') ? " %c%.3f" : " %c%.5f", axis, value);
push_to_output(buf, len, add_eol);
return this->push_to_output(formatter.string(), false);
}
void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol)
inline void PressureEqualizer::push_to_output(const std::string &text, bool add_eol)
{
return this->push_to_output(text.data(), text.size(), add_eol);
}
inline void PressureEqualizer::push_to_output(const char *text, const size_t len, bool add_eol)
{
// New length of the output buffer content.
size_t len_new = output_buffer_length + len + 1;
if (add_eol)
++ len_new;
++len_new;
// Resize the output buffer to a power of 2 higher than the required memory.
if (output_buffer.size() < len_new) {
@ -631,29 +621,31 @@ void PressureEqualizer::push_to_output(const char *text, const size_t len, bool
output_buffer_length += len;
}
if (add_eol)
output_buffer[output_buffer_length ++] = '\n';
output_buffer[output_buffer_length++] = '\n';
output_buffer[output_buffer_length] = 0;
}
void PressureEqualizer::push_line_to_output(const GCodeLine &line, const float new_feedrate, const char *comment)
{
push_to_output(EXTRUDE_END_TAG.data(), EXTRUDE_END_TAG_LENGTH, true);
push_to_output(EXTRUDE_END_TAG.data(), EXTRUDE_END_TAG.length(), true);
push_to_output("G1", 2, false);
push_axis_to_output('F', new_feedrate);
push_to_output(EXTRUDE_SET_SPEED_TAG.data(), EXTRUDE_SET_SPEED_TAG_LENGTH, line.extrusion_role != erExternalPerimeter);
GCodeG1Formatter feedrate_formatter;
feedrate_formatter.emit_f(new_feedrate);
feedrate_formatter.emit_string(std::string(EXTRUDE_SET_SPEED_TAG.data(), EXTRUDE_SET_SPEED_TAG.length()));
if (line.extrusion_role == erExternalPerimeter)
push_to_output(EXTERNAL_PERIMETER_TAG.data(), EXTERNAL_PERIMETER_TAG_LENGTH, true);
feedrate_formatter.emit_string(std::string(EXTERNAL_PERIMETER_TAG.data(), EXTERNAL_PERIMETER_TAG.length()));
push_to_output(feedrate_formatter);
push_to_output("G1", 2, false);
for (char i = 0; i < 3; ++ i)
if (line.pos_provided[i])
push_axis_to_output('X'+i, line.pos_end[i]);
push_axis_to_output('E', m_config.use_relative_e_distances.value ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3]);
GCodeG1Formatter extrusion_formatter;
for (size_t axis_idx = 0; axis_idx < 3; ++axis_idx)
if (line.pos_provided[axis_idx])
extrusion_formatter.emit_axis(char('X' + axis_idx), line.pos_end[axis_idx], GCodeFormatter::XYZF_EXPORT_DIGITS);
extrusion_formatter.emit_axis('E', m_use_relative_e_distances ? (line.pos_end[3] - line.pos_start[3]) : line.pos_end[3], GCodeFormatter::E_EXPORT_DIGITS);
// output comment and EOL
push_to_output(comment, (comment == nullptr) ? 0 : strlen(comment), true);
if (comment != nullptr)
extrusion_formatter.emit_string(std::string(comment));
push_to_output(extrusion_formatter);
}
} // namespace Slic3r

View File

@ -5,8 +5,14 @@
#include "../PrintConfig.hpp"
#include "../ExtrusionEntity.hpp"
#include <queue>
namespace Slic3r {
struct LayerResult;
class GCodeG1Formatter;
//#define PRESSURE_EQUALIZER_STATISTIC
//#define PRESSURE_EQUALIZER_DEBUG
@ -15,30 +21,34 @@ namespace Slic3r {
class PressureEqualizer
{
public:
PressureEqualizer() = delete;
explicit PressureEqualizer(const Slic3r::GCodeConfig &config);
~PressureEqualizer() = default;
void reset();
// Process a next batch of G-code lines. Flush the internal buffers if asked for.
const char* process_layer(const char *szGCode, bool flush);
size_t get_output_buffer_length() const { return output_buffer_length; }
// Process a next batch of G-code lines.
// The last LayerResult must be LayerResult::make_nop_layer_result() because it always returns GCode for the previous layer.
// When process_layer is called for the first layer, then LayerResult::make_nop_layer_result() is returned.
LayerResult process_layer(LayerResult &&input);
private:
void process_layer(const std::string &gcode);
#ifdef PRESSURE_EQUALIZER_STATISTIC
struct Statistics
{
void reset() {
volumetric_extrusion_rate_min = std::numeric_limits<float>::max();
void reset()
{
volumetric_extrusion_rate_min = std::numeric_limits<float>::max();
volumetric_extrusion_rate_max = 0.f;
volumetric_extrusion_rate_avg = 0.f;
extrusion_length = 0.f;
extrusion_length = 0.f;
}
void update(float volumetric_extrusion_rate, float length) {
volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate);
volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate);
void update(float volumetric_extrusion_rate, float length)
{
volumetric_extrusion_rate_min = std::min(volumetric_extrusion_rate_min, volumetric_extrusion_rate);
volumetric_extrusion_rate_max = std::max(volumetric_extrusion_rate_max, volumetric_extrusion_rate);
volumetric_extrusion_rate_avg += volumetric_extrusion_rate * length;
extrusion_length += length;
extrusion_length += length;
}
float volumetric_extrusion_rate_min;
float volumetric_extrusion_rate_max;
@ -46,21 +56,16 @@ private:
float extrusion_length;
};
#ifdef PRESSURE_EQUALIZER_STATISTIC
struct Statistics m_stat;
#endif
// Keeps the reference, does not own the config.
const Slic3r::GCodeConfig &m_config;
// Private configuration values
// How fast could the volumetric extrusion rate increase / decrase? mm^3/sec^2
struct ExtrusionRateSlope {
float positive;
float negative;
};
enum { numExtrusionRoles = erSupportMaterialInterface + 1 };
ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[numExtrusionRoles];
ExtrusionRateSlope m_max_volumetric_extrusion_rate_slopes[erCount];
float m_max_volumetric_extrusion_rate_slope_positive;
float m_max_volumetric_extrusion_rate_slope_negative;
// Maximum segment length to split a long segment, if the initial and the final flow rate differ.
@ -76,6 +81,7 @@ private:
size_t m_current_extruder;
ExtrusionRole m_current_extrusion_role;
bool m_retracted;
bool m_use_relative_e_distances;
enum GCodeLineType {
GCODELINETYPE_INVALID,
@ -132,8 +138,6 @@ private:
// or maybe the line needs to be split into multiple lines.
bool modified;
// float timeStart;
// float timeEnd;
// X,Y,Z,E,F. Storing the state of the currently active extruder only.
float pos_start[5];
float pos_end[5];
@ -158,21 +162,14 @@ private:
float max_volumetric_extrusion_rate_slope_negative;
};
// Circular buffer of GCode lines. The circular buffer size will be limited to circular_buffer_size.
std::vector<GCodeLine> circular_buffer;
// Current position of the circular buffer (index, where to write the next line to, the line has to be pushed out before it is overwritten).
size_t circular_buffer_pos;
// Circular buffer size, configuration value.
size_t circular_buffer_size;
// Number of valid lines in the circular buffer. Lower or equal to circular_buffer_size.
size_t circular_buffer_items;
// Output buffer will only grow. It will not be reallocated over and over.
std::vector<char> output_buffer;
size_t output_buffer_length;
#ifdef PRESSURE_EQUALIZER_DEBUG
// For debugging purposes. Index of the G-code line processed.
size_t line_idx;
#endif
bool process_line(const char *line, const char *line_end, GCodeLine &buf);
void output_gcode_line(GCodeLine &buf);
@ -182,33 +179,16 @@ private:
void adjust_volumetric_rate();
// Push the text to the end of the output_buffer.
void push_to_output(const char *text, size_t len, bool add_eol = true);
// Push an axis assignment to the end of the output buffer.
void push_axis_to_output(char axis, float value, bool add_eol = false);
// Push a G-code line to the output,
inline void push_to_output(GCodeG1Formatter &formatter);
inline void push_to_output(const std::string &text, bool add_eol);
inline void push_to_output(const char *text, size_t len, bool add_eol = true);
// Push a G-code line to the output.
void push_line_to_output(const GCodeLine &line, float new_feedrate, const char *comment);
size_t circular_buffer_idx_head() const {
size_t idx = circular_buffer_pos + circular_buffer_size - circular_buffer_items;
if (idx >= circular_buffer_size)
idx -= circular_buffer_size;
return idx;
}
public:
std::queue<LayerResult*> m_layer_results;
size_t circular_buffer_idx_tail() const { return circular_buffer_pos; }
size_t circular_buffer_idx_prev(size_t idx) const {
idx += circular_buffer_size - 1;
if (idx >= circular_buffer_size)
idx -= circular_buffer_size;
return idx;
}
size_t circular_buffer_idx_next(size_t idx) const {
if (++ idx >= circular_buffer_size)
idx -= circular_buffer_size;
return idx;
}
std::vector<GCodeLine> m_gcode_lines;
};
} // namespace Slic3r