Quantization of G-code export to achieve more precise extrusion

width control.
This commit is contained in:
Vojtech Bubnik 2022-02-07 15:33:34 +01:00
parent 1138c563b2
commit 199dc121a5
6 changed files with 138 additions and 135 deletions

View File

@ -1,4 +1,5 @@
#include "Extruder.hpp"
#include "GCodeWriter.hpp"
#include "PrintConfig.hpp"
namespace Slic3r {
@ -7,24 +8,24 @@ Extruder::Extruder(unsigned int id, GCodeConfig *config) :
m_id(id),
m_config(config)
{
reset();
// cache values that are going to be called often
m_e_per_mm3 = this->extrusion_multiplier();
if (! m_config->use_volumetric_e)
m_e_per_mm3 /= this->filament_crossection();
}
double Extruder::extrude(double dE)
std::pair<double, double> Extruder::extrude(double dE)
{
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
// Quantize extruder delta to G-code resolution.
dE = GCodeFormatter::quantize_e(dE);
m_E += dE;
m_absolute_E += dE;
if (dE < 0.)
m_retracted -= dE;
return dE;
return std::make_pair(dE, m_E);
}
/* This method makes sure the extruder is retracted by the specified amount
@ -34,28 +35,33 @@ double Extruder::extrude(double dE)
The restart_extra argument sets the extra length to be used for
unretraction. If we're actually performing a retraction, any restart_extra
value supplied will overwrite the previous one if any. */
double Extruder::retract(double length, double restart_extra)
std::pair<double, double> Extruder::retract(double retract_length, double restart_extra)
{
// in case of relative E distances we always reset to 0 before any output
if (m_config->use_relative_e_distances)
m_E = 0.;
double to_retract = std::max(0., length - m_retracted);
// Quantize extruder delta to G-code resolution.
double to_retract = this->retract_to_go(retract_length);
if (to_retract > 0.) {
m_E -= to_retract;
m_absolute_E -= to_retract;
m_retracted += to_retract;
m_restart_extra = restart_extra;
}
return to_retract;
return std::make_pair(to_retract, m_E);
}
double Extruder::unretract()
double Extruder::retract_to_go(double retract_length) const
{
double dE = m_retracted + m_restart_extra;
this->extrude(dE);
return std::max(0., GCodeFormatter::quantize_e(retract_length - m_retracted));
}
std::pair<double, double> Extruder::unretract()
{
auto [dE, emitE] = this->extrude(m_retracted + m_restart_extra);
m_retracted = 0.;
m_restart_extra = 0.;
return dE;
return std::make_pair(dE, emitE);
}
// Used filament volume in mm^3.

View File

@ -12,22 +12,24 @@ class Extruder
{
public:
Extruder(unsigned int id, GCodeConfig *config);
virtual ~Extruder() {}
void reset() {
m_E = 0;
m_absolute_E = 0;
m_retracted = 0;
m_restart_extra = 0;
}
~Extruder() = default;
unsigned int id() const { return m_id; }
double extrude(double dE);
double retract(double length, double restart_extra);
double unretract();
double E() const { return m_E; }
void reset_E() { m_E = 0.; }
// Following three methods emit:
// first - extrusion delta
// second - number to emit to G-code: This may be delta for relative mode or a distance from last reset_E() for absolute mode.
// They also quantize the E axis to G-code resolution.
std::pair<double, double> extrude(double dE);
std::pair<double, double> retract(double retract_length, double restart_extra);
std::pair<double, double> unretract();
// How much to retract yet before retract_length is reached?
// The value is quantized to G-code resolution.
double retract_to_go(double retract_length) const;
// Reset the current state of the E axis (this is only needed for relative extruder addressing mode anyways).
// Returns true if the extruder was non-zero before reset.
bool reset_E() { bool modified = m_E != 0; m_E = 0.; return modified; }
double e_per_mm(double mm3_per_mm) const { return mm3_per_mm * m_e_per_mm3; }
double e_per_mm3() const { return m_e_per_mm3; }
// Used filament volume in mm^3.
@ -57,14 +59,16 @@ private:
GCodeConfig *m_config;
// Print-wide global ID of this extruder.
unsigned int m_id;
// Current state of the extruder axis, may be resetted if use_relative_e_distances.
double m_E;
// Current state of the extruder axis.
// For absolute extruder addressing, it is the current state since the last reset (G92 E0) issued at the end of the last retraction.
// For relative extruder addressing, it is the E axis difference emitted into the G-code the last time.
double m_E { 0 };
// Current state of the extruder tachometer, used to output the extruded_volume() and used_filament() statistics.
double m_absolute_E;
double m_absolute_E { 0 };
// Current positive amount of retraction.
double m_retracted;
double m_retracted { 0 };
// When retracted, this value stores the extra amount of priming on deretraction.
double m_restart_extra;
double m_restart_extra { 0 };
double m_e_per_mm3;
};
@ -76,4 +80,4 @@ inline bool operator> (const Extruder &e1, const Extruder &e2) { return e1.id()
}
#endif
#endif // slic3r_Extruder_hpp_

View File

@ -27,7 +27,6 @@
#include <boost/foreach.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <boost/nowide/iostream.hpp>
#include <boost/nowide/cstdio.hpp>
@ -156,62 +155,51 @@ namespace Slic3r {
std::string Wipe::wipe(GCode& gcodegen, bool toolchange)
{
std::string gcode;
const Extruder &extruder = *gcodegen.writer().extruder();
/* Reduce feedrate a bit; travel speed is often too high to move on existing material.
Too fast = ripping of existing material; too slow = short wipe path, thus more blob. */
double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
// get the retraction length
double length = toolchange
? gcodegen.writer().extruder()->retract_length_toolchange()
: gcodegen.writer().extruder()->retract_length();
// Shorten the retraction length by the amount already retracted before wipe.
length *= (1. - gcodegen.writer().extruder()->retract_before_wipe());
if (length > 0) {
/* Calculate how long we need to travel in order to consume the required
amount of retraction. In other words, how far do we move in XY at wipe_speed
for the time needed to consume retract_length at retract_speed? */
double wipe_dist = scale_(length / gcodegen.writer().extruder()->retract_speed() * wipe_speed);
/* Take the stored wipe path and replace first point with the current actual position
(they might be different, for example, in case of loop clipping). */
Polyline wipe_path;
wipe_path.append(gcodegen.last_pos());
wipe_path.append(
this->path.points.begin() + 1,
this->path.points.end()
);
wipe_path.clip_end(wipe_path.length() - wipe_dist);
// subdivide the retraction in segments
if (!wipe_path.empty()) {
// add tag for processor
// Remaining quantized retraction length.
if (double retract_length = extruder.retract_to_go(toolchange ? extruder.retract_length_toolchange() : extruder.retract_length());
retract_length > 0 && this->path.size() >= 2) {
// Reduce feedrate a bit; travel speed is often too high to move on existing material.
// Too fast = ripping of existing material; too slow = short wipe path, thus more blob.
const double wipe_speed = gcodegen.writer().config.travel_speed.value * 0.8;
// Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
// due to rounding (TODO: test and/or better math for this).
const double xy_to_e = 0.95 * extruder.retract_speed() / wipe_speed;
// Start with the current position, which may be different from the wipe path start in case of loop clipping.
Vec2d prev = gcodegen.point_to_gcode_quantized(gcodegen.last_pos());
auto it = this->path.points.begin();
Vec2d p = gcodegen.point_to_gcode_quantized(*(++ it));
if (p != prev) {
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_Start) + "\n";
for (const Line& line : wipe_path.lines()) {
double segment_length = line.length();
/* Reduce retraction length a bit to avoid effective retraction speed to be greater than the configured one
due to rounding (TODO: test and/or better math for this) */
double dE = length * (segment_length / wipe_dist) * 0.95;
auto end = this->path.points.end();
bool done = false;
for (; it != end; ++ it) {
p = gcodegen.point_to_gcode_quantized(*it);
double segment_length = (p - prev).norm();
double dE = GCodeFormatter::quantize_e(xy_to_e * segment_length);
if (dE > retract_length - EPSILON) {
if (dE > retract_length + EPSILON)
// Shorten the segment.
p = prev + (p - prev) * (retract_length / dE);
dE = retract_length;
done = true;
}
//FIXME one shall not generate the unnecessary G1 Fxxx commands, here wipe_speed is a constant inside this cycle.
// Is it here for the cooling markers? Or should it be outside of the cycle?
gcode += gcodegen.writer().set_speed(wipe_speed * 60, "", gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
gcode += gcodegen.writer().extrude_to_xy(
gcodegen.point_to_gcode(line.b),
-dE,
"wipe and retract"
);
gcode += gcodegen.writer().set_speed(wipe_speed * 60, {}, gcodegen.enable_cooling_markers() ? ";_WIPE" : "");
gcode += gcodegen.writer().extrude_to_xy(p, -dE, "wipe and retract");
prev = p;
retract_length -= dE;
}
// add tag for processor
gcode += ";" + GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Wipe_End) + "\n";
gcodegen.set_last_pos(wipe_path.points.back());
gcodegen.set_last_pos(gcodegen.gcode_to_point(prev));
}
}
// prevent wiping again on same path
// Prevent wiping again on the same path.
this->reset_path();
}
return gcode;
}
@ -3010,13 +2998,15 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
double path_length = 0.;
{
std::string comment = m_config.gcode_comments ? description : "";
for (const Line &line : path.polyline.lines()) {
const double line_length = line.length() * SCALING_FACTOR;
Vec2d prev = this->point_to_gcode_quantized(path.polyline.points.front());
auto it = path.polyline.points.begin();
auto end = path.polyline.points.end();
for (++ it; it != end; ++ it) {
Vec2d p = this->point_to_gcode_quantized(*it);
const double line_length = (p - prev).norm();
path_length += line_length;
gcode += m_writer.extrude_to_xy(
this->point_to_gcode(line.b),
e_per_mm * line_length,
comment);
gcode += m_writer.extrude_to_xy(p, e_per_mm * line_length, comment);
prev = p;
}
}
if (m_enable_cooling_markers)
@ -3239,7 +3229,13 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
Vec2d GCode::point_to_gcode(const Point &point) const
{
Vec2d extruder_offset = EXTRUDER_CONFIG(extruder_offset);
return unscale(point) + m_origin - extruder_offset;
return unscaled<double>(point) + m_origin - extruder_offset;
}
Vec2d GCode::point_to_gcode_quantized(const Point &point) const
{
Vec2d p = this->point_to_gcode(point);
return { GCodeFormatter::quantize_xyzf(p.x()), GCodeFormatter::quantize_xyzf(p.y()) };
}
// convert a model-space scaled point into G-code coordinates

View File

@ -55,9 +55,9 @@ public:
Polyline path;
Wipe() : enable(false) {}
bool has_path() const { return !this->path.points.empty(); }
void reset_path() { this->path = Polyline(); }
std::string wipe(GCode &gcodegen, bool toolchange = false);
bool has_path() const { return ! this->path.empty(); }
void reset_path() { this->path.clear(); }
std::string wipe(GCode &gcodegen, bool toolchange);
};
class WipeTowerIntegration {
@ -151,7 +151,10 @@ public:
void set_origin(const Vec2d &pointf);
void set_origin(const coordf_t x, const coordf_t y) { this->set_origin(Vec2d(x, y)); }
const Point& last_pos() const { return m_last_pos; }
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset.
Vec2d point_to_gcode(const Point &point) const;
// Convert coordinates of the active object to G-code coordinates, possibly adjusted for extruder offset and quantized to G-code resolution.
Vec2d point_to_gcode_quantized(const Point &point) const;
Point gcode_to_point(const Vec2d &point) const;
const FullPrintConfig &config() const { return m_config; }
const Layer* layer() const { return m_layer; }

View File

@ -79,7 +79,7 @@ std::string GCodeWriter::postamble() const
std::string GCodeWriter::set_temperature(unsigned int temperature, bool wait, int tool) const
{
if (wait && (FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish)))
return "";
return {};
std::string code, comment;
if (wait && FLAVOR_IS_NOT(gcfTeacup) && FLAVOR_IS_NOT(gcfRepRapFirmware)) {
@ -192,32 +192,18 @@ std::string GCodeWriter::set_acceleration(unsigned int acceleration)
std::string GCodeWriter::reset_e(bool force)
{
if (FLAVOR_IS(gcfMach3)
|| FLAVOR_IS(gcfMakerWare)
|| FLAVOR_IS(gcfSailfish))
return "";
if (m_extruder != nullptr) {
if (m_extruder->E() == 0. && ! force)
return "";
m_extruder->reset_E();
}
if (! m_extrusion_axis.empty() && ! this->config.use_relative_e_distances) {
std::ostringstream gcode;
gcode << "G92 " << m_extrusion_axis << "0";
if (this->config.gcode_comments) gcode << " ; reset extrusion distance";
gcode << "\n";
return gcode.str();
} else {
return "";
}
return
FLAVOR_IS(gcfMach3) || FLAVOR_IS(gcfMakerWare) || FLAVOR_IS(gcfSailfish) || this->config.use_relative_e_distances ||
(m_extruder != nullptr && ! m_extruder->reset_E() && ! force) ||
m_extrusion_axis.empty() ?
std::string{} :
std::string("G92 ") + m_extrusion_axis + (this->config.gcode_comments ? "0 ; reset extrusion distance\n" : "0\n");
}
std::string GCodeWriter::update_progress(unsigned int num, unsigned int tot, bool allow_100) const
{
if (FLAVOR_IS_NOT(gcfMakerWare) && FLAVOR_IS_NOT(gcfSailfish))
return "";
return {};
unsigned int percent = (unsigned int)floor(100.0 * num / tot + 0.5);
if (!allow_100) percent = std::min(percent, (unsigned int)99);
@ -269,8 +255,8 @@ std::string GCodeWriter::set_speed(double F, const std::string &comment, const s
std::string GCodeWriter::travel_to_xy(const Vec2d &point, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
m_pos.x() = point.x();
m_pos.y() = point.y();
GCodeG1Formatter w;
w.emit_xy(point);
@ -290,9 +276,9 @@ std::string GCodeWriter::travel_to_xyz(const Vec3d &point, const std::string &co
don't perform the Z move but we only move in the XY plane and
adjust the nominal Z by reducing the lift amount that will be
used for unlift. */
if (!this->will_move_z(point(2))) {
double nominal_z = m_pos(2) - m_lifted;
m_lifted -= (point(2) - nominal_z);
if (!this->will_move_z(point.z())) {
double nominal_z = m_pos.z() - m_lifted;
m_lifted -= (point.z() - nominal_z);
// In case that retract_lift == layer_height we could end up with almost zero in_m_lifted
// and a retract could be skipped (https://github.com/prusa3d/PrusaSlicer/issues/2154
if (std::abs(m_lifted) < EPSILON)
@ -318,11 +304,11 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
we don't perform the move but we only adjust the nominal Z by
reducing the lift amount that will be used for unlift. */
if (!this->will_move_z(z)) {
double nominal_z = m_pos(2) - m_lifted;
double nominal_z = m_pos.z() - m_lifted;
m_lifted -= (z - nominal_z);
if (std::abs(m_lifted) < EPSILON)
m_lifted = 0.;
return "";
return {};
}
/* In all the other cases, we perform an actual Z move and cancel
@ -333,7 +319,7 @@ std::string GCodeWriter::travel_to_z(double z, const std::string &comment)
std::string GCodeWriter::_travel_to_z(double z, const std::string &comment)
{
m_pos(2) = z;
m_pos.z() = z;
double speed = this->config.travel_speed_z.value;
if (speed == 0.)
@ -351,8 +337,8 @@ bool GCodeWriter::will_move_z(double z) const
/* If target Z is lower than current Z but higher than nominal Z
we don't perform an actual Z move. */
if (m_lifted > 0) {
double nominal_z = m_pos(2) - m_lifted;
if (z >= nominal_z && z <= m_pos(2))
double nominal_z = m_pos.z() - m_lifted;
if (z >= nominal_z && z <= m_pos.z())
return false;
}
return true;
@ -360,17 +346,17 @@ bool GCodeWriter::will_move_z(double z) const
std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std::string &comment)
{
m_pos(0) = point(0);
m_pos(1) = point(1);
m_extruder->extrude(dE);
m_pos.x() = point.x();
m_pos.y() = point.y();
GCodeG1Formatter w;
w.emit_xy(point);
w.emit_e(m_extrusion_axis, m_extruder->E());
w.emit_e(m_extrusion_axis, m_extruder->extrude(dE).second);
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
#if 0
std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment)
{
m_pos = point;
@ -383,6 +369,7 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
w.emit_comment(this->config.gcode_comments, comment);
return w.string();
}
#endif
std::string GCodeWriter::retract(bool before_wipe)
{
@ -422,14 +409,13 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
restart_extra = restart_extra * area;
}
std::string gcode;
if (double dE = m_extruder->retract(length, restart_extra); dE != 0) {
if (auto [dE, emitE] = m_extruder->retract(length, restart_extra); dE != 0) {
if (this->config.use_firmware_retraction) {
gcode = FLAVOR_IS(gcfMachinekit) ? "G22 ; retract\n" : "G10 ; retract\n";
} else if (! m_extrusion_axis.empty()) {
GCodeG1Formatter w;
w.emit_e(m_extrusion_axis, m_extruder->E());
w.emit_e(m_extrusion_axis, emitE);
w.emit_f(m_extruder->retract_speed() * 60.);
w.emit_comment(this->config.gcode_comments, comment);
gcode = w.string();
@ -449,14 +435,14 @@ std::string GCodeWriter::unretract()
if (FLAVOR_IS(gcfMakerWare))
gcode = "M101 ; extruder on\n";
if (double dE = m_extruder->unretract(); dE != 0) {
if (auto [dE, emitE] = m_extruder->unretract(); dE != 0) {
if (this->config.use_firmware_retraction) {
gcode += FLAVOR_IS(gcfMachinekit) ? "G23 ; unretract\n" : "G11 ; unretract\n";
gcode += this->reset_e();
} else if (! m_extrusion_axis.empty()) {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
GCodeG1Formatter w;
w.emit_e(m_extrusion_axis, m_extruder->E());
w.emit_e(m_extrusion_axis, emitE);
w.emit_f(m_extruder->deretract_speed() * 60.);
w.emit_comment(this->config.gcode_comments, " ; unretract");
gcode += w.string();
@ -476,21 +462,21 @@ std::string GCodeWriter::lift()
{
double above = this->config.retract_lift_above.get_at(m_extruder->id());
double below = this->config.retract_lift_below.get_at(m_extruder->id());
if (m_pos(2) >= above && (below == 0 || m_pos(2) <= below))
if (m_pos.z() >= above && (below == 0 || m_pos.z() <= below))
target_lift = this->config.retract_lift.get_at(m_extruder->id());
}
if (m_lifted == 0 && target_lift > 0) {
m_lifted = target_lift;
return this->_travel_to_z(m_pos(2) + target_lift, "lift Z");
return this->_travel_to_z(m_pos.z() + target_lift, "lift Z");
}
return "";
return {};
}
std::string GCodeWriter::unlift()
{
std::string gcode;
if (m_lifted > 0) {
gcode += this->_travel_to_z(m_pos(2) - m_lifted, "restore layer Z");
gcode += this->_travel_to_z(m_pos.z() - m_lifted, "restore layer Z");
m_lifted = 0;
}
return gcode;

View File

@ -61,7 +61,7 @@ public:
std::string travel_to_z(double z, const std::string &comment = std::string());
bool will_move_z(double z) const;
std::string extrude_to_xy(const Vec2d &point, double dE, const std::string &comment = std::string());
std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
// std::string extrude_to_xyz(const Vec3d &point, double dE, const std::string &comment = std::string());
std::string retract(bool before_wipe = false);
std::string retract_for_toolchange(bool before_wipe = false);
std::string unretract();
@ -121,6 +121,14 @@ public:
// static constexpr const int E_EXPORT_DIGITS = 9;
#endif
static constexpr const std::array<double, 10> pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000.};
static constexpr const std::array<double, 10> pow_10_inv{1./1., 1./10., 1./100., 1./1000., 1./10000., 1./100000., 1./1000000., 1./10000000., 1./100000000., 1./1000000000.};
// Quantize doubles to a resolution of the G-code.
static double quantize(double v, size_t ndigits) { return std::round(v * pow_10[ndigits]) * pow_10_inv[ndigits]; }
static double quantize_xyzf(double v) { return quantize(v, XYZF_EXPORT_DIGITS); }
static double quantize_e(double v) { return quantize(v, E_EXPORT_DIGITS); }
void emit_axis(const char axis, const double v, size_t digits);
void emit_xy(const Vec2d &point) {