Wait in the wipe tower with a millisecond dwell on Klipper

The wipe tower's "Delay after unloading" never happened on Klipper. It was
emitted as G4 S<seconds>, and Klipper's G4 reads only the P parameter, in
milliseconds, so the pause was silently skipped. The option now produces a
dwell Klipper actually performs.

Also corrects the planner flush rationale, which cited an extruder position
reset that Klipper resolves at parse time and does not need synchronized, and
adds end-to-end coverage that slices a two-filament print and checks the
emitted wipe tower G-code on both a Klipper and a non-Klipper flavor.

No change to any other firmware flavor's output, and no shipped profile sets a
non-zero delay, so no shipped profile's output moves either.
This commit is contained in:
SoftFever
2026-08-05 17:09:49 +08:00
parent 4e1caa39eb
commit 194ef34080
4 changed files with 125 additions and 12 deletions

View File

@@ -622,6 +622,13 @@ const char* flush_planner_queue_command(GCodeFlavor flavor)
return flavor == gcfKlipper ? "M400\n" : "G4 S0\n";
}
std::string wait_command(GCodeFlavor flavor, float seconds)
{
if (flavor == gcfKlipper)
return "G4 P" + std::to_string(std::lround(seconds * 1000.f)) + "\n";
return "G4 S" + Slic3r::float_to_string_decimal_point(seconds, 3) + "\n";
}
class WipeTowerWriter
{
public:
@@ -1150,7 +1157,7 @@ public:
{
if (time==0.f)
return *this;
m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n";
m_gcode += wait_command(m_gcode_flavor, time);
return *this;
}

View File

@@ -26,14 +26,17 @@ enum GCodeFlavor : unsigned char;
Polylines construct_gap_for_skip_points(
const Polygon& polygon, const std::vector<Vec2f>& skip_points, float wt_width, float gap_length, Polygon& insert_skip_polygon);
// Returns the command that makes the firmware finish its queued moves, so a command or
// custom-G-code boundary right after (resetting the extruder position, entering
// [change_filament_gcode] / [filament_start_gcode]) is not reached early. Klipper acts on
// such commands the moment it parses them, and its G4 reads only P, so the zero dwell the
// other flavors use synchronizes nothing there — M400 does. Defined in WipeTower.cpp, shared
// by WipeTower and WipeTower2.
// Returns the command that makes the firmware finish its queued moves around an M104/M109
// or custom-G-code boundary. Klipper acts on commands the instant it parses them, and its G4
// reads only P, so the zero dwell other flavors use synchronizes nothing there — M400 does.
// Defined in WipeTower.cpp, shared by WipeTower and WipeTower2.
const char* flush_planner_queue_command(GCodeFlavor flavor);
// Returns the command that pauses for `seconds`. Klipper's G4 reads only P, in
// milliseconds, and ignores S, so the seconds form the other flavors use would dwell zero
// there. Defined in WipeTower.cpp, shared by WipeTower and WipeTower2.
std::string wait_command(GCodeFlavor flavor, float seconds);
class WipeTower
{
public:

View File

@@ -636,7 +636,7 @@ public:
{
if (time==0.f)
return *this;
m_gcode += "G4 S" + Slic3r::float_to_string_decimal_point(time, 3) + "\n";
m_gcode += wait_command(m_gcode_flavor, time);
return *this;
}