Improve retract() for G10/G11/autoretract

This commit is contained in:
Scott Lahteine 2017-07-17 22:01:41 -05:00
parent 423b0f3a1e
commit fee696db5d
3 changed files with 185 additions and 127 deletions

View File

@ -383,10 +383,15 @@ extern float soft_endstop_min[XYZ], soft_endstop_max[XYZ];
#endif #endif
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
extern bool autoretract_enabled; extern bool autoretract_enabled; // M209 S - Autoretract switch
extern bool retracted[EXTRUDERS]; // extruder[n].retracted extern float retract_length, // M207 S - G10 Retract length
extern float retract_length, retract_length_swap, retract_feedrate_mm_s, retract_zlift; retract_feedrate_mm_s, // M207 F - G10 Retract feedrate
extern float retract_recover_length, retract_recover_length_swap, retract_recover_feedrate_mm_s; retract_zlift, // M207 Z - G10 Retract hop size
retract_recover_length, // M208 S - G11 Recover length
retract_recover_feedrate_mm_s, // M208 F - G11 Recover feedrate
retract_length_swap, // M207 W - G10 Swap Retract length
retract_recover_length_swap, // M208 W - G11 Swap Recover length
swap_retract_recover_feedrate_mm_s; // M208 R - G11 Swap Recover feedrate
#endif #endif
// Print job timer // Print job timer

View File

@ -557,20 +557,22 @@ static uint8_t target_extruder;
baricuda_e_to_p_pressure = 0; baricuda_e_to_p_pressure = 0;
#endif #endif
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT) // Initialized by settings.load()...
bool autoretract_enabled, // M209 S - Autoretract switch
bool autoretract_enabled = false; retracted[EXTRUDERS] = { false }; // Which extruders are currently retracted
bool retracted[EXTRUDERS] = { false }; float retract_length, // M207 S - G10 Retract length
bool retracted_swap[EXTRUDERS] = { false }; retract_feedrate_mm_s, // M207 F - G10 Retract feedrate
retract_zlift, // M207 Z - G10 Retract hop size
float retract_length = RETRACT_LENGTH; retract_recover_length, // M208 S - G11 Recover length
float retract_length_swap = RETRACT_LENGTH_SWAP; retract_recover_feedrate_mm_s, // M208 F - G11 Recover feedrate
float retract_feedrate_mm_s = RETRACT_FEEDRATE; retract_length_swap, // M207 W - G10 Swap Retract length
float retract_zlift = RETRACT_ZLIFT; retract_recover_length_swap, // M208 W - G11 Swap Recover length
float retract_recover_length = RETRACT_RECOVER_LENGTH; swap_retract_recover_feedrate_mm_s; // M208 R - G11 Swap Recover feedrate
float retract_recover_length_swap = RETRACT_RECOVER_LENGTH_SWAP; #if EXTRUDERS > 1
float retract_recover_feedrate_mm_s = RETRACT_RECOVER_FEEDRATE; bool retracted_swap[EXTRUDERS] = { false }; // Which extruders are swap-retracted
#else
constexpr bool retracted_swap[1] = { false };
#endif
#endif // FWRETRACT #endif // FWRETRACT
#if HAS_POWER_SWITCH #if HAS_POWER_SWITCH
@ -3100,55 +3102,120 @@ static void homeaxis(const AxisEnum axis) {
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
void retract(const bool retracting, const bool swapping = false) { /**
* Retract or recover according to firmware settings
*
* This function handles retract/recover moves for G10 and G11,
* plus auto-retract moves sent from G0/G1 when E-only moves are done.
*
* To simplify the logic, doubled retract/recover moves are ignored.
*
* Note: Z lift is done transparently to the planner. Aborting
* a print between G10 and G11 may corrupt the Z position.
*
* Note: Auto-retract will apply the set Z hop in addition to any Z hop
* included in the G-code. Use M207 Z0 to to prevent double hop.
*/
void retract(const bool retracting
#if EXTRUDERS > 1
, bool swapping = false
#endif
) {
static float hop_height; static float hop_height, // Remember where the Z height started
hop_amount = 0.0; // Total amount lifted, for use in recover
if (retracting == retracted[active_extruder]) return; // Simply never allow two retracts or recovers in a row
if (retracted[active_extruder] == retracting) return;
#if EXTRUDERS < 2
bool swapping = false;
#endif
if (!retracting) swapping = retracted_swap[active_extruder];
/* // debugging
SERIAL_ECHOLNPAIR("retracting ", retracting);
SERIAL_ECHOLNPAIR("swapping ", swapping);
SERIAL_ECHOLNPAIR("active extruder ", active_extruder);
for (uint8_t i = 0; i < EXTRUDERS; ++i) {
SERIAL_ECHOPAIR("retracted[", i);
SERIAL_ECHOLNPAIR("] ", retracted[i]);
SERIAL_ECHOPAIR("retracted_swap[", i);
SERIAL_ECHOLNPAIR("] ", retracted_swap[i]);
}
SERIAL_ECHOLNPAIR("current_position[z] ", current_position[Z_AXIS]);
SERIAL_ECHOLNPAIR("hop_amount ", hop_amount);
//*/
const bool has_zhop = retract_zlift > 0.01; // Is there a hop set?
const float old_feedrate_mm_s = feedrate_mm_s; const float old_feedrate_mm_s = feedrate_mm_s;
// The current position will be the destination for E and Z moves
set_destination_to_current(); set_destination_to_current();
if (retracting) { if (retracting) {
// Remember the Z height since G-code may include its own Z-hop
// For best results turn off Z hop if G-code already includes it
hop_height = destination[Z_AXIS];
// Retract by moving from a faux E position back to the current E position
feedrate_mm_s = retract_feedrate_mm_s; feedrate_mm_s = retract_feedrate_mm_s;
current_position[E_AXIS] += (swapping ? retract_length_swap : retract_length) / volumetric_multiplier[active_extruder]; current_position[E_AXIS] += (swapping ? retract_length_swap : retract_length) / volumetric_multiplier[active_extruder];
sync_plan_position_e(); sync_plan_position_e();
prepare_move_to_destination(); prepare_move_to_destination();
if (retract_zlift > 0.01) { // Is a Z hop set, and has the hop not yet been done?
hop_height = current_position[Z_AXIS]; if (has_zhop) {
// Pretend current position is lower hop_amount += retract_zlift; // Carriage is raised for retraction hop
current_position[Z_AXIS] -= retract_zlift; current_position[Z_AXIS] -= retract_zlift; // Pretend current pos is lower. Next move raises Z.
SYNC_PLAN_POSITION_KINEMATIC(); SYNC_PLAN_POSITION_KINEMATIC(); // Set the planner to the new position
// Raise up to the old current_position prepare_move_to_destination(); // Raise up to the old current pos
prepare_move_to_destination();
} }
} }
else { else {
// If a hop was done and Z hasn't changed, undo the Z hop
// If the height hasn't been lowered, undo the Z hop if (hop_amount && NEAR(hop_height, destination[Z_AXIS])) {
if (retract_zlift > 0.01 && hop_height <= current_position[Z_AXIS]) { current_position[Z_AXIS] += hop_amount; // Pretend current pos is higher. Next move lowers Z.
// Pretend current position is higher. Z will lower on the next move SYNC_PLAN_POSITION_KINEMATIC(); // Set the planner to the new position
current_position[Z_AXIS] += retract_zlift; prepare_move_to_destination(); // Lower to the old current pos
SYNC_PLAN_POSITION_KINEMATIC(); hop_amount = 0.0;
// Lower Z
prepare_move_to_destination();
} }
feedrate_mm_s = retract_recover_feedrate_mm_s; // A retract multiplier has been added here to get faster swap recovery
feedrate_mm_s = swapping ? swap_retract_recover_feedrate_mm_s : retract_recover_feedrate_mm_s;
const float move_e = swapping ? retract_length_swap + retract_recover_length_swap : retract_length + retract_recover_length; const float move_e = swapping ? retract_length_swap + retract_recover_length_swap : retract_length + retract_recover_length;
current_position[E_AXIS] -= move_e / volumetric_multiplier[active_extruder]; current_position[E_AXIS] -= move_e / volumetric_multiplier[active_extruder];
sync_plan_position_e(); sync_plan_position_e();
// Recover E prepare_move_to_destination(); // Recover E
prepare_move_to_destination();
} }
feedrate_mm_s = old_feedrate_mm_s; feedrate_mm_s = old_feedrate_mm_s;
// The active extruder is now retracted or recovered
retracted[active_extruder] = retracting; retracted[active_extruder] = retracting;
// If swap retract/recover then update the retracted_swap flag too
#if EXTRUDERS > 1
if (swapping) retracted_swap[active_extruder] = retracting;
#endif
/* // debugging
SERIAL_ECHOLNPAIR("retracting ", retracting);
SERIAL_ECHOLNPAIR("swapping ", swapping);
SERIAL_ECHOLNPAIR("active_extruder ", active_extruder);
for (uint8_t i = 0; i < EXTRUDERS; ++i) {
SERIAL_ECHOPAIR("retracted[", i);
SERIAL_ECHOLNPAIR("] ", retracted[i]);
SERIAL_ECHOPAIR("retracted_swap[", i);
SERIAL_ECHOLNPAIR("] ", retracted_swap[i]);
}
SERIAL_ECHOLNPAIR("current_position[z] ", current_position[Z_AXIS]);
SERIAL_ECHOLNPAIR("hop_amount ", hop_amount);
//*/
} // retract() } // retract()
#endif // FWRETRACT #endif // FWRETRACT
@ -3277,18 +3344,16 @@ inline void gcode_G0_G1(
gcode_get_destination(); // For X Y Z E F gcode_get_destination(); // For X Y Z E F
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
// When M209 Autoretract is enabled, convert E-only moves to firmware retract/recover moves
if (autoretract_enabled && !(parser.seen('X') || parser.seen('Y') || parser.seen('Z')) && parser.seen('E')) { if (autoretract_enabled && parser.seen('E') && !(parser.seen('X') || parser.seen('Y') || parser.seen('Z'))) {
const float echange = destination[E_AXIS] - current_position[E_AXIS]; const float echange = destination[E_AXIS] - current_position[E_AXIS];
// Is this move an attempt to retract or recover? // Is this a retract or recover move?
if (WITHIN(FABS(echange), MIN_AUTORETRACT, MAX_AUTORETRACT) && retracted[active_extruder] == (echange > 0.0)) { if (WITHIN(FABS(echange), MIN_AUTORETRACT, MAX_AUTORETRACT) && retracted[active_extruder] == (echange > 0.0)) {
current_position[E_AXIS] = destination[E_AXIS]; // hide the slicer-generated retract/recover from calculations current_position[E_AXIS] = destination[E_AXIS]; // Hide a G1-based retract/recover from calculations
sync_plan_position_e(); // AND from the planner sync_plan_position_e(); // AND from the planner
retract(!retracted[active_extruder]); return retract(echange < 0.0); // Firmware-based retract/recover (double-retract ignored)
return;
} }
} }
#endif // FWRETRACT #endif // FWRETRACT
#if IS_SCARA #if IS_SCARA
@ -8489,9 +8554,7 @@ inline void gcode_M205() {
if (parser.seen('S')) retract_length = parser.value_axis_units(E_AXIS); if (parser.seen('S')) retract_length = parser.value_axis_units(E_AXIS);
if (parser.seen('F')) retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); if (parser.seen('F')) retract_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
if (parser.seen('Z')) retract_zlift = parser.value_linear_units(); if (parser.seen('Z')) retract_zlift = parser.value_linear_units();
#if EXTRUDERS > 1
if (parser.seen('W')) retract_length_swap = parser.value_axis_units(E_AXIS); if (parser.seen('W')) retract_length_swap = parser.value_axis_units(E_AXIS);
#endif
} }
/** /**
@ -8500,13 +8563,13 @@ inline void gcode_M205() {
* S[+units] retract_recover_length (in addition to M207 S*) * S[+units] retract_recover_length (in addition to M207 S*)
* W[+units] retract_recover_length_swap (multi-extruder) * W[+units] retract_recover_length_swap (multi-extruder)
* F[units/min] retract_recover_feedrate_mm_s * F[units/min] retract_recover_feedrate_mm_s
* R[units/min] swap_retract_recover_feedrate_mm_s
*/ */
inline void gcode_M208() { inline void gcode_M208() {
if (parser.seen('S')) retract_recover_length = parser.value_axis_units(E_AXIS); if (parser.seen('S')) retract_recover_length = parser.value_axis_units(E_AXIS);
if (parser.seen('F')) retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS)); if (parser.seen('F')) retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
#if EXTRUDERS > 1 if (parser.seen('R')) swap_retract_recover_feedrate_mm_s = MMM_TO_MMS(parser.value_axis_units(E_AXIS));
if (parser.seen('W')) retract_recover_length_swap = parser.value_axis_units(E_AXIS); if (parser.seen('W')) retract_recover_length_swap = parser.value_axis_units(E_AXIS);
#endif
} }
/** /**

View File

@ -36,7 +36,7 @@
* *
*/ */
#define EEPROM_VERSION "V39" #define EEPROM_VERSION "V40"
// Change EEPROM version if these are changed: // Change EEPROM version if these are changed:
#define EEPROM_OFFSET 100 #define EEPROM_OFFSET 100
@ -125,44 +125,45 @@
* DOGLCD: 2 bytes * DOGLCD: 2 bytes
* 502 M250 C lcd_contrast (uint16_t) * 502 M250 C lcd_contrast (uint16_t)
* *
* FWRETRACT: 29 bytes * FWRETRACT: 33 bytes
* 504 M209 S autoretract_enabled (bool) * 504 M209 S autoretract_enabled (bool)
* 505 M207 S retract_length (float) * 505 M207 S retract_length (float)
* 509 M207 W retract_length_swap (float) * 509 M207 F retract_feedrate_mm_s (float)
* 513 M207 F retract_feedrate_mm_s (float) * 513 M207 Z retract_zlift (float)
* 517 M207 Z retract_zlift (float) * 517 M208 S retract_recover_length (float)
* 521 M208 S retract_recover_length (float) * 521 M208 F retract_recover_feedrate_mm_s (float)
* 525 M208 W retract_recover_length_swap (float) * 525 M207 W retract_length_swap (float)
* 529 M208 F retract_recover_feedrate_mm_s (float) * 529 M208 W retract_recover_length_swap (float)
* 533 M208 R swap_retract_recover_feedrate_mm_s (float)
* *
* Volumetric Extrusion: 21 bytes * Volumetric Extrusion: 21 bytes
* 533 M200 D volumetric_enabled (bool) * 537 M200 D volumetric_enabled (bool)
* 534 M200 T D filament_size (float x5) (T0..3) * 538 M200 T D filament_size (float x5) (T0..3)
* *
* HAVE_TMC2130: 20 bytes * HAVE_TMC2130: 20 bytes
* 554 M906 X Stepper X current (uint16_t) * 558 M906 X Stepper X current (uint16_t)
* 556 M906 Y Stepper Y current (uint16_t) * 560 M906 Y Stepper Y current (uint16_t)
* 558 M906 Z Stepper Z current (uint16_t) * 562 M906 Z Stepper Z current (uint16_t)
* 560 M906 X2 Stepper X2 current (uint16_t) * 564 M906 X2 Stepper X2 current (uint16_t)
* 562 M906 Y2 Stepper Y2 current (uint16_t) * 566 M906 Y2 Stepper Y2 current (uint16_t)
* 564 M906 Z2 Stepper Z2 current (uint16_t) * 568 M906 Z2 Stepper Z2 current (uint16_t)
* 566 M906 E0 Stepper E0 current (uint16_t) * 570 M906 E0 Stepper E0 current (uint16_t)
* 568 M906 E1 Stepper E1 current (uint16_t) * 572 M906 E1 Stepper E1 current (uint16_t)
* 570 M906 E2 Stepper E2 current (uint16_t) * 574 M906 E2 Stepper E2 current (uint16_t)
* 572 M906 E3 Stepper E3 current (uint16_t) * 576 M906 E3 Stepper E3 current (uint16_t)
* 576 M906 E4 Stepper E4 current (uint16_t) * 580 M906 E4 Stepper E4 current (uint16_t)
* *
* LIN_ADVANCE: 8 bytes * LIN_ADVANCE: 8 bytes
* 580 M900 K extruder_advance_k (float) * 584 M900 K extruder_advance_k (float)
* 584 M900 WHD advance_ed_ratio (float) * 588 M900 WHD advance_ed_ratio (float)
* *
* HAS_MOTOR_CURRENT_PWM: * HAS_MOTOR_CURRENT_PWM:
* 588 M907 X Stepper XY current (uint32_t) * 592 M907 X Stepper XY current (uint32_t)
* 592 M907 Z Stepper Z current (uint32_t) * 596 M907 Z Stepper Z current (uint32_t)
* 596 M907 E Stepper E current (uint32_t) * 600 M907 E Stepper E current (uint32_t)
* *
* 600 Minimum end-point * 604 Minimum end-point
* 1921 (600 + 36 + 9 + 288 + 988) Maximum end-point * 1925 (604 + 36 + 9 + 288 + 988) Maximum end-point
* *
* ======================================================================== * ========================================================================
* meshes_begin (between max and min end-point, directly above) * meshes_begin (between max and min end-point, directly above)
@ -520,26 +521,26 @@ void MarlinSettings::postprocess() {
#endif #endif
EEPROM_WRITE(lcd_contrast); EEPROM_WRITE(lcd_contrast);
#if ENABLED(FWRETRACT) #if DISABLED(FWRETRACT)
const bool autoretract_enabled = false;
const float retract_length = 3,
retract_feedrate_mm_s = 45,
retract_zlift = 0,
retract_recover_length = 0,
retract_recover_feedrate_mm_s = 0,
retract_length_swap = 13,
retract_recover_length_swap = 0,
swap_retract_recover_feedrate_mm_s = 8;
#endif
EEPROM_WRITE(autoretract_enabled); EEPROM_WRITE(autoretract_enabled);
EEPROM_WRITE(retract_length); EEPROM_WRITE(retract_length);
#if EXTRUDERS > 1
EEPROM_WRITE(retract_length_swap);
#else
dummy = 0.0f;
EEPROM_WRITE(dummy);
#endif
EEPROM_WRITE(retract_feedrate_mm_s); EEPROM_WRITE(retract_feedrate_mm_s);
EEPROM_WRITE(retract_zlift); EEPROM_WRITE(retract_zlift);
EEPROM_WRITE(retract_recover_length); EEPROM_WRITE(retract_recover_length);
#if EXTRUDERS > 1
EEPROM_WRITE(retract_recover_length_swap);
#else
dummy = 0.0f;
EEPROM_WRITE(dummy);
#endif
EEPROM_WRITE(retract_recover_feedrate_mm_s); EEPROM_WRITE(retract_recover_feedrate_mm_s);
#endif // FWRETRACT EEPROM_WRITE(retract_length_swap);
EEPROM_WRITE(retract_recover_length_swap);
EEPROM_WRITE(swap_retract_recover_feedrate_mm_s);
EEPROM_WRITE(volumetric_enabled); EEPROM_WRITE(volumetric_enabled);
@ -620,7 +621,7 @@ void MarlinSettings::postprocess() {
EEPROM_WRITE(val); EEPROM_WRITE(val);
#else #else
val = 0; val = 0;
for (uint8_t q = 0; q < 11; ++q) EEPROM_WRITE(val); for (uint8_t q = 11; q--;) EEPROM_WRITE(val);
#endif #endif
// //
@ -701,6 +702,7 @@ void MarlinSettings::postprocess() {
} }
else { else {
float dummy = 0; float dummy = 0;
bool dummyb;
working_crc = 0; //clear before reading first "real data" working_crc = 0; //clear before reading first "real data"
@ -830,7 +832,6 @@ void MarlinSettings::postprocess() {
EEPROM_READ(ubl.state.z_offset); EEPROM_READ(ubl.state.z_offset);
EEPROM_READ(ubl.state.storage_slot); EEPROM_READ(ubl.state.storage_slot);
#else #else
bool dummyb;
uint8_t dummyui8; uint8_t dummyui8;
EEPROM_READ(dummyb); EEPROM_READ(dummyb);
EEPROM_READ(dummy); EEPROM_READ(dummy);
@ -915,21 +916,17 @@ void MarlinSettings::postprocess() {
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
EEPROM_READ(autoretract_enabled); EEPROM_READ(autoretract_enabled);
EEPROM_READ(retract_length); EEPROM_READ(retract_length);
#if EXTRUDERS > 1
EEPROM_READ(retract_length_swap);
#else
EEPROM_READ(dummy);
#endif
EEPROM_READ(retract_feedrate_mm_s); EEPROM_READ(retract_feedrate_mm_s);
EEPROM_READ(retract_zlift); EEPROM_READ(retract_zlift);
EEPROM_READ(retract_recover_length); EEPROM_READ(retract_recover_length);
#if EXTRUDERS > 1
EEPROM_READ(retract_recover_length_swap);
#else
EEPROM_READ(dummy);
#endif
EEPROM_READ(retract_recover_feedrate_mm_s); EEPROM_READ(retract_recover_feedrate_mm_s);
#endif // FWRETRACT EEPROM_READ(retract_length_swap);
EEPROM_READ(retract_recover_length_swap);
EEPROM_READ(swap_retract_recover_feedrate_mm_s);
#else
EEPROM_READ(dummyb);
for (uint8_t q=8; q--;) EEPROM_READ(dummy);
#endif
EEPROM_READ(volumetric_enabled); EEPROM_READ(volumetric_enabled);
@ -1291,17 +1288,14 @@ void MarlinSettings::reset() {
#if ENABLED(FWRETRACT) #if ENABLED(FWRETRACT)
autoretract_enabled = false; autoretract_enabled = false;
retract_length = RETRACT_LENGTH; retract_length = RETRACT_LENGTH;
#if EXTRUDERS > 1
retract_length_swap = RETRACT_LENGTH_SWAP;
#endif
retract_feedrate_mm_s = RETRACT_FEEDRATE; retract_feedrate_mm_s = RETRACT_FEEDRATE;
retract_zlift = RETRACT_ZLIFT; retract_zlift = RETRACT_ZLIFT;
retract_recover_length = RETRACT_RECOVER_LENGTH; retract_recover_length = RETRACT_RECOVER_LENGTH;
#if EXTRUDERS > 1
retract_recover_length_swap = RETRACT_RECOVER_LENGTH_SWAP;
#endif
retract_recover_feedrate_mm_s = RETRACT_RECOVER_FEEDRATE; retract_recover_feedrate_mm_s = RETRACT_RECOVER_FEEDRATE;
#endif retract_length_swap = RETRACT_LENGTH_SWAP;
retract_recover_length_swap = RETRACT_RECOVER_LENGTH_SWAP;
swap_retract_recover_feedrate_mm_s = RETRACT_RECOVER_FEEDRATE_SWAP;
#endif // FWRETRACT
volumetric_enabled = volumetric_enabled =
#if ENABLED(VOLUMETRIC_DEFAULT_ON) #if ENABLED(VOLUMETRIC_DEFAULT_ON)
@ -1753,9 +1747,7 @@ void MarlinSettings::reset() {
} }
CONFIG_ECHO_START; CONFIG_ECHO_START;
SERIAL_ECHOPAIR(" M207 S", LINEAR_UNIT(retract_length)); SERIAL_ECHOPAIR(" M207 S", LINEAR_UNIT(retract_length));
#if EXTRUDERS > 1
SERIAL_ECHOPAIR(" W", LINEAR_UNIT(retract_length_swap)); SERIAL_ECHOPAIR(" W", LINEAR_UNIT(retract_length_swap));
#endif
SERIAL_ECHOPAIR(" F", MMS_TO_MMM(LINEAR_UNIT(retract_feedrate_mm_s))); SERIAL_ECHOPAIR(" F", MMS_TO_MMM(LINEAR_UNIT(retract_feedrate_mm_s)));
SERIAL_ECHOLNPAIR(" Z", LINEAR_UNIT(retract_zlift)); SERIAL_ECHOLNPAIR(" Z", LINEAR_UNIT(retract_zlift));
@ -1765,9 +1757,7 @@ void MarlinSettings::reset() {
} }
CONFIG_ECHO_START; CONFIG_ECHO_START;
SERIAL_ECHOPAIR(" M208 S", LINEAR_UNIT(retract_recover_length)); SERIAL_ECHOPAIR(" M208 S", LINEAR_UNIT(retract_recover_length));
#if EXTRUDERS > 1
SERIAL_ECHOPAIR(" W", LINEAR_UNIT(retract_recover_length_swap)); SERIAL_ECHOPAIR(" W", LINEAR_UNIT(retract_recover_length_swap));
#endif
SERIAL_ECHOLNPAIR(" F", MMS_TO_MMM(LINEAR_UNIT(retract_recover_feedrate_mm_s))); SERIAL_ECHOLNPAIR(" F", MMS_TO_MMM(LINEAR_UNIT(retract_recover_feedrate_mm_s)));
if (!forReplay) { if (!forReplay) {