From 199dc121a55c0aa0723e5f58417bb2fad1dfc690 Mon Sep 17 00:00:00 2001 From: Vojtech Bubnik Date: Mon, 7 Feb 2022 15:33:34 +0100 Subject: Quantization of G-code export to achieve more precise extrusion width control. --- src/libslic3r/Extruder.cpp | 30 +++++++----- src/libslic3r/Extruder.hpp | 42 ++++++++-------- src/libslic3r/GCode.cpp | 108 ++++++++++++++++++++---------------------- src/libslic3r/GCode.hpp | 9 ++-- src/libslic3r/GCodeWriter.cpp | 76 ++++++++++++----------------- src/libslic3r/GCodeWriter.hpp | 10 +++- 6 files changed, 139 insertions(+), 136 deletions(-) diff --git a/src/libslic3r/Extruder.cpp b/src/libslic3r/Extruder.cpp index f7a5c5007..d2ff65097 100644 --- a/src/libslic3r/Extruder.cpp +++ b/src/libslic3r/Extruder.cpp @@ -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 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 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; + 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 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. diff --git a/src/libslic3r/Extruder.hpp b/src/libslic3r/Extruder.hpp index e9c6927f8..7491b1c8f 100644 --- a/src/libslic3r/Extruder.hpp +++ b/src/libslic3r/Extruder.hpp @@ -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 extrude(double dE); + std::pair retract(double retract_length, double restart_extra); + std::pair 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_ diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 13aedab1b..6a53917de 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -155,63 +154,52 @@ namespace Slic3r { std::string Wipe::wipe(GCode& gcodegen, bool toolchange) { - std::string gcode; - - /* 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 + std::string gcode; + const Extruder &extruder = *gcodegen.writer().extruder(); + + // 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 - this->reset_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(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 diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index f46558c35..c0e636c79 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -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; } diff --git a/src/libslic3r/GCodeWriter.cpp b/src/libslic3r/GCodeWriter.cpp index 233976b19..c5279c0f5 100644 --- a/src/libslic3r/GCodeWriter.cpp +++ b/src/libslic3r/GCodeWriter.cpp @@ -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; diff --git a/src/libslic3r/GCodeWriter.hpp b/src/libslic3r/GCodeWriter.hpp index e8a54737e..6c36c0d3a 100644 --- a/src/libslic3r/GCodeWriter.hpp +++ b/src/libslic3r/GCodeWriter.hpp @@ -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 pow_10 { 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000.}; + static constexpr const std::array 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) { -- cgit v1.2.3