Smooth out the spiral lift when arc fitting is disabled (#15118)

The linear approximation used a heuristic segment count clamped to 4..16, so the
lift ran as a coarse polygon. Every vertex is a direction change large enough to
hit the firmware's jerk limit, forcing a decelerate/accelerate at each corner —
the lift micro-stutters instead of running at speed. The segment count now comes
from the chord deviation against the slicing resolution, reusing
Geometry::ArcWelder::arc_discretization_steps, which keeps the turn at each
vertex shallow enough for the firmware to carry speed through the whole move.

Points are emitted through GCodeG1Formatter so they carry the same quantization
as the rest of the G-code, and the move comment now trails the feedrate line to
match _travel_to_z and the G2/G3 branch. No change when arc fitting is enabled.
This commit is contained in:
SoftFever
2026-08-05 00:09:46 +08:00
committed by GitHub
parent 1d078e005a
commit 0051768206

View File

@@ -3,6 +3,7 @@
#include "I18N.hpp" #include "I18N.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Geometry/ArcWelder.hpp"
#include "Line.hpp" #include "Line.hpp"
#include <algorithm> #include <algorithm>
#include <iomanip> #include <iomanip>
@@ -1018,45 +1019,48 @@ std::string GCodeWriter::_spiral_travel_to_z(double z, const Vec2d &ij_offset, c
} }
if (!this->config.enable_arc_fitting) { // Orca: if arc fitting is disabled, approximate the arc with small linear segments if (!this->config.enable_arc_fitting) { // Orca: if arc fitting is disabled, approximate the arc with small linear segments
std::ostringstream oss;
const double z_start = m_pos(2); // starting Z height const double z_start = m_pos(2); // starting Z height
// --------------------------------------------------------------------
// Determine number of segments based on Resolution
// --------------------------------------------------------------------
const double ref_resolution = 0.01; // reference resolution in mm
const double ref_segments = 8.0; // reference number of segments at reference resolution
// number of linear segments to use for approximating the arc, clamp between 4 and 16
const int segments = std::clamp(int(std::round(ref_segments * (ref_resolution / m_resolution))), 4, 16);
// --------------------------------------------------------------------
const double px = m_pos(0) - m_x_offset; // take plate offset into consideration const double px = m_pos(0) - m_x_offset; // take plate offset into consideration
const double py = m_pos(1) - m_y_offset; // take plate offset into consideration const double py = m_pos(1) - m_y_offset; // take plate offset into consideration
const double cx = px + ij_offset(0); // center x const double cx = px + ij_offset(0); // center x
const double cy = py + ij_offset(1); // center y const double cy = py + ij_offset(1); // center y
const double radius = ij_offset.norm(); // radius const double radius = ij_offset.norm(); // radius
// Number of linear segments approximating the circle, chosen so that a chord never deviates
// from the true arc by more than the slicing resolution. A resolution of 0 means "no
// simplification", which has no finite segment count, so it takes the upper bound.
constexpr size_t min_segments = 8; // keep a small spiral visibly round
constexpr size_t max_segments = 128; // bound the emitted G-code
const int segments = int(m_resolution > 0. ?
std::clamp(Geometry::ArcWelder::arc_discretization_steps(radius, 2. * M_PI, m_resolution), min_segments, max_segments) :
max_segments);
const double a0 = std::atan2(py - cy, px - cx); // start angle const double a0 = std::atan2(py - cy, px - cx); // start angle
const double delta = 2.0 * M_PI; // CCW full circle
if (full_gcode_comment) auto emit_point = [&output](const Vec3d &point) {
oss << ";" << comment << "\n"; GCodeG1Formatter w;
w.emit_xyz(point);
output += w.string();
};
oss << "G1 F" << (speed * 60.0) << "\n"; // set feedrate output.reserve(size_t(segments) * 40); // ~40 characters per emitted G1 line
GCodeG1Formatter w; // set feedrate
w.emit_f(speed * 60.0);
w.emit_comment(GCodeWriter::full_gcode_comment, comment);
output += w.string();
// approximate the arc with small linear segments (without the last point which is added later to ensure exactness) // approximate the arc with small linear segments (without the last point which is added later to ensure exactness)
for (int i = 1; i < segments; ++i) { for (int i = 1; i < segments; ++i) {
double t = double(i) / segments; // parametric position along arc const double t = double(i) / segments; // parametric position along arc
double a = a0 + delta * t; // CCW arc param const double a = a0 + 2. * M_PI * t; // CCW arc param, full circle
double x = cx + radius * std::cos(a); // point on circle emit_point(Vec3d(cx + radius * std::cos(a), // point on circle
double y = cy + radius * std::sin(a); // point on circle cy + radius * std::sin(a),
double zz = z_start + (z - z_start) * t; // interpolated Z height z_start + (z - z_start) * t)); // interpolated Z height
oss << "G1 X" << x << " Y" << y << " Z" << zz << "\n";
} }
oss << "G1 X" << px << " Y" << py << " Z" << z << "\n"; // final point to ensure exactness emit_point(Vec3d(px, py, z)); // final point to ensure exactness
output = oss.str();
} else { // Orca: if arc fitting is enabled emit a G2/G3 command for the spiral lift } else { // Orca: if arc fitting is enabled emit a G2/G3 command for the spiral lift
output = std::string("G17") + (full_gcode_comment ? " ; XY plane for arc\n" : "\n"); output = std::string("G17") + (full_gcode_comment ? " ; XY plane for arc\n" : "\n");