diff --git a/Marlin/src/module/tool_change.cpp b/Marlin/src/module/tool_change.cpp index 7b3bf480bd..4913f8ed15 100644 --- a/Marlin/src/module/tool_change.cpp +++ b/Marlin/src/module/tool_change.cpp @@ -115,8 +115,114 @@ #endif } -#endif // PARKING_EXTRUDER + inline void parking_extruder_tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool no_move/*=false*/) { + constexpr float z_raise = PARKING_EXTRUDER_SECURITY_RAISE; + if (!no_move) { + + const float parkingposx[] = PARKING_EXTRUDER_PARKING_X, + midpos = (parkingposx[0] + parkingposx[1]) * 0.5 + hotend_offset[X_AXIS][active_extruder], + grabpos = parkingposx[tmp_extruder] + hotend_offset[X_AXIS][active_extruder] + + (tmp_extruder == 0 ? -(PARKING_EXTRUDER_GRAB_DISTANCE) : PARKING_EXTRUDER_GRAB_DISTANCE); + /** + * Steps: + * 1. Raise Z-Axis to give enough clearance + * 2. Move to park position of old extruder + * 3. Disengage magnetic field, wait for delay + * 4. Move near new extruder + * 5. Engage magnetic field for new extruder + * 6. Move to parking incl. offset of new extruder + * 7. Lower Z-Axis + */ + + // STEP 1 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("Starting Autopark"); + if (DEBUGGING(LEVELING)) DEBUG_POS("current position:", current_position); + #endif + current_position[Z_AXIS] += z_raise; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(1) Raise Z-Axis "); + if (DEBUGGING(LEVELING)) DEBUG_POS("Moving to Raised Z-Position", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); + stepper.synchronize(); + + // STEP 2 + current_position[X_AXIS] = parkingposx[active_extruder] + hotend_offset[X_AXIS][active_extruder]; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPAIR("(2) Park extruder ", active_extruder); + if (DEBUGGING(LEVELING)) DEBUG_POS("Moving ParkPos", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + stepper.synchronize(); + + // STEP 3 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(3) Disengage magnet "); + #endif + pe_deactivate_magnet(active_extruder); + + // STEP 4 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(4) Move to position near new extruder"); + #endif + current_position[X_AXIS] += (active_extruder == 0 ? 10 : -10); // move 10mm away from parked extruder + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Moving away from parked extruder", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + stepper.synchronize(); + + // STEP 5 + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(5) Engage magnetic field"); + #endif + + #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) + pe_activate_magnet(active_extruder); //just save power for inverted magnets + #endif + pe_activate_magnet(tmp_extruder); + + // STEP 6 + current_position[X_AXIS] = grabpos + (tmp_extruder == 0 ? (+10) : (-10)); + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + current_position[X_AXIS] = grabpos; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPAIR("(6) Unpark extruder ", tmp_extruder); + if (DEBUGGING(LEVELING)) DEBUG_POS("Move UnparkPos", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS]/2, active_extruder); + stepper.synchronize(); + + // Step 7 + current_position[X_AXIS] = midpos - hotend_offset[X_AXIS][tmp_extruder]; + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("(7) Move midway between hotends"); + if (DEBUGGING(LEVELING)) DEBUG_POS("Move midway to new extruder", current_position); + #endif + planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); + stepper.synchronize(); + #if ENABLED(DEBUG_LEVELING_FEATURE) + SERIAL_ECHOLNPGM("Autopark done."); + #endif + } + else { // nomove == true + // Only engage magnetic field for new extruder + pe_activate_magnet(tmp_extruder); + #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) + pe_activate_magnet(active_extruder); // Just save power for inverted magnets + #endif + } + current_position[Z_AXIS] += hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("Applying Z-offset", current_position); + #endif + } + +#endif // PARKING_EXTRUDER inline void invalid_extruder_error(const uint8_t e) { SERIAL_ECHO_START(); @@ -126,6 +232,124 @@ inline void invalid_extruder_error(const uint8_t e) { SERIAL_ECHOLNPGM(MSG_INVALID_EXTRUDER); } +#if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1 + + inline void mixing_tool_change(const uint8_t tmp_extruder) { + if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) + return invalid_extruder_error(tmp_extruder); + + // T0-Tnnn: Switch virtual tool by changing the mix + for (uint8_t j = 0; j < MIXING_STEPPERS; j++) + mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j]; + } + +#endif // MIXING_EXTRUDER && MIXING_VIRTUAL_TOOLS > 1 + +#if ENABLED(DUAL_X_CARRIAGE) + + inline void dualx_tool_change(const uint8_t tmp_extruder, bool &no_move) { + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOPGM("Dual X Carriage Mode "); + switch (dual_x_carriage_mode) { + case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break; + case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break; + case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break; + } + } + #endif + + const float xhome = x_home_pos(active_extruder); + if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE + && IsRunning() + && (delayed_move_time || current_position[X_AXIS] != xhome) + ) { + float raised_z = current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT; + #if ENABLED(MAX_SOFTWARE_ENDSTOPS) + NOMORE(raised_z, soft_endstop_max[Z_AXIS]); + #endif + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPAIR("Raise to ", raised_z); + SERIAL_ECHOLNPAIR("MoveX to ", xhome); + SERIAL_ECHOLNPAIR("Lower to ", current_position[Z_AXIS]); + } + #endif + // Park old head: 1) raise 2) move to park position 3) lower + for (uint8_t i = 0; i < 3; i++) + planner.buffer_line( + i == 0 ? current_position[X_AXIS] : xhome, + current_position[Y_AXIS], + i == 2 ? current_position[Z_AXIS] : raised_z, + current_position[E_AXIS], + planner.max_feedrate_mm_s[i == 1 ? X_AXIS : Z_AXIS], + active_extruder + ); + stepper.synchronize(); + } + + // Apply Y & Z extruder offset (X offset is used as home pos with Dual X) + current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder]; + current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; + + // Activate the new extruder ahead of calling set_axis_is_at_home! + active_extruder = tmp_extruder; + + // This function resets the max/min values - the current position may be overwritten below. + set_axis_is_at_home(X_AXIS); + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position); + #endif + + // Only when auto-parking are carriages safe to move + if (dual_x_carriage_mode != DXC_AUTO_PARK_MODE) no_move = true; + + switch (dual_x_carriage_mode) { + case DXC_FULL_CONTROL_MODE: + // New current position is the position of the activated extruder + current_position[X_AXIS] = inactive_extruder_x_pos; + // Save the inactive extruder's position (from the old current_position) + inactive_extruder_x_pos = destination[X_AXIS]; + break; + case DXC_AUTO_PARK_MODE: + // record raised toolhead position for use by unpark + COPY(raised_parked_position, current_position); + raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT; + #if ENABLED(MAX_SOFTWARE_ENDSTOPS) + NOMORE(raised_parked_position[Z_AXIS], soft_endstop_max[Z_AXIS]); + #endif + active_extruder_parked = true; + delayed_move_time = 0; + break; + case DXC_DUPLICATION_MODE: + // If the new extruder is the left one, set it "parked" + // This triggers the second extruder to move into the duplication position + active_extruder_parked = (active_extruder == 0); + current_position[X_AXIS] = active_extruder_parked ? inactive_extruder_x_pos : destination[X_AXIS] + duplicate_extruder_x_offset; + inactive_extruder_x_pos = destination[X_AXIS]; + extruder_duplication_enabled = false; + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPAIR("Set inactive_extruder_x_pos=", inactive_extruder_x_pos); + SERIAL_ECHOLNPGM("Clear extruder_duplication_enabled"); + } + #endif + break; + } + + #if ENABLED(DEBUG_LEVELING_FEATURE) + if (DEBUGGING(LEVELING)) { + SERIAL_ECHOLNPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no"); + DEBUG_POS("New extruder (parked)", current_position); + } + #endif + + // No extra case for HAS_ABL in DUAL_X_CARRIAGE. Does that mean they don't work together? + } + +#endif // DUAL_X_CARRIAGE + /** * Perform a tool-change, which may result in moving the * previous tool out of the way and the new tool into place. @@ -133,12 +357,7 @@ inline void invalid_extruder_error(const uint8_t e) { void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool no_move/*=false*/) { #if ENABLED(MIXING_EXTRUDER) && MIXING_VIRTUAL_TOOLS > 1 - if (tmp_extruder >= MIXING_VIRTUAL_TOOLS) - return invalid_extruder_error(tmp_extruder); - - // T0-Tnnn: Switch virtual tool by changing the mix - for (uint8_t j = 0; j < MIXING_STEPPERS; j++) - mixing_factor[j] = mixing_virtual_tool_mix[tmp_extruder][j]; + mixing_tool_change(tmp_extruder); #else // !MIXING_EXTRUDER || MIXING_VIRTUAL_TOOLS <= 1 @@ -164,217 +383,13 @@ void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool n #if ENABLED(DUAL_X_CARRIAGE) - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOPGM("Dual X Carriage Mode "); - switch (dual_x_carriage_mode) { - case DXC_FULL_CONTROL_MODE: SERIAL_ECHOLNPGM("DXC_FULL_CONTROL_MODE"); break; - case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break; - case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break; - } - } - #endif - - const float xhome = x_home_pos(active_extruder); - if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE - && IsRunning() - && (delayed_move_time || current_position[X_AXIS] != xhome) - ) { - float raised_z = current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT; - #if ENABLED(MAX_SOFTWARE_ENDSTOPS) - NOMORE(raised_z, soft_endstop_max[Z_AXIS]); - #endif - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPAIR("Raise to ", raised_z); - SERIAL_ECHOLNPAIR("MoveX to ", xhome); - SERIAL_ECHOLNPAIR("Lower to ", current_position[Z_AXIS]); - } - #endif - // Park old head: 1) raise 2) move to park position 3) lower - for (uint8_t i = 0; i < 3; i++) - planner.buffer_line( - i == 0 ? current_position[X_AXIS] : xhome, - current_position[Y_AXIS], - i == 2 ? current_position[Z_AXIS] : raised_z, - current_position[E_AXIS], - planner.max_feedrate_mm_s[i == 1 ? X_AXIS : Z_AXIS], - active_extruder - ); - stepper.synchronize(); - } - - // Apply Y & Z extruder offset (X offset is used as home pos with Dual X) - current_position[Y_AXIS] -= hotend_offset[Y_AXIS][active_extruder] - hotend_offset[Y_AXIS][tmp_extruder]; - current_position[Z_AXIS] -= hotend_offset[Z_AXIS][active_extruder] - hotend_offset[Z_AXIS][tmp_extruder]; - - // Activate the new extruder ahead of calling set_axis_is_at_home! - active_extruder = tmp_extruder; - - // This function resets the max/min values - the current position may be overwritten below. - set_axis_is_at_home(X_AXIS); - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("New Extruder", current_position); - #endif - - // Only when auto-parking are carriages safe to move - if (dual_x_carriage_mode != DXC_AUTO_PARK_MODE) no_move = true; - - switch (dual_x_carriage_mode) { - case DXC_FULL_CONTROL_MODE: - // New current position is the position of the activated extruder - current_position[X_AXIS] = inactive_extruder_x_pos; - // Save the inactive extruder's position (from the old current_position) - inactive_extruder_x_pos = destination[X_AXIS]; - break; - case DXC_AUTO_PARK_MODE: - // record raised toolhead position for use by unpark - COPY(raised_parked_position, current_position); - raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT; - #if ENABLED(MAX_SOFTWARE_ENDSTOPS) - NOMORE(raised_parked_position[Z_AXIS], soft_endstop_max[Z_AXIS]); - #endif - active_extruder_parked = true; - delayed_move_time = 0; - break; - case DXC_DUPLICATION_MODE: - // If the new extruder is the left one, set it "parked" - // This triggers the second extruder to move into the duplication position - active_extruder_parked = (active_extruder == 0); - - if (active_extruder_parked) - current_position[X_AXIS] = inactive_extruder_x_pos; - else - current_position[X_AXIS] = destination[X_AXIS] + duplicate_extruder_x_offset; - inactive_extruder_x_pos = destination[X_AXIS]; - extruder_duplication_enabled = false; - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPAIR("Set inactive_extruder_x_pos=", inactive_extruder_x_pos); - SERIAL_ECHOLNPGM("Clear extruder_duplication_enabled"); - } - #endif - break; - } - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) { - SERIAL_ECHOLNPAIR("Active extruder parked: ", active_extruder_parked ? "yes" : "no"); - DEBUG_POS("New extruder (parked)", current_position); - } - #endif - - // No extra case for HAS_ABL in DUAL_X_CARRIAGE. Does that mean they don't work together? + dualx_tool_change(tmp_extruder, no_move); // Can modify no_move #else // !DUAL_X_CARRIAGE - #if ENABLED(PARKING_EXTRUDER) // Dual Parking extruder - float z_raise = PARKING_EXTRUDER_SECURITY_RAISE; - if (!no_move) { - - const float parkingposx[] = PARKING_EXTRUDER_PARKING_X, - midpos = (parkingposx[0] + parkingposx[1]) * 0.5 + hotend_offset[X_AXIS][active_extruder], - grabpos = parkingposx[tmp_extruder] + hotend_offset[X_AXIS][active_extruder] - + (tmp_extruder == 0 ? -(PARKING_EXTRUDER_GRAB_DISTANCE) : PARKING_EXTRUDER_GRAB_DISTANCE); - /** - * Steps: - * 1. Raise Z-Axis to give enough clearance - * 2. Move to park position of old extruder - * 3. Disengage magnetic field, wait for delay - * 4. Move near new extruder - * 5. Engage magnetic field for new extruder - * 6. Move to parking incl. offset of new extruder - * 7. Lower Z-Axis - */ - - // STEP 1 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("Starting Autopark"); - if (DEBUGGING(LEVELING)) DEBUG_POS("current position:", current_position); - #endif - current_position[Z_AXIS] += z_raise; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(1) Raise Z-Axis "); - if (DEBUGGING(LEVELING)) DEBUG_POS("Moving to Raised Z-Position", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[Z_AXIS], active_extruder); - stepper.synchronize(); - - // STEP 2 - current_position[X_AXIS] = parkingposx[active_extruder] + hotend_offset[X_AXIS][active_extruder]; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPAIR("(2) Park extruder ", active_extruder); - if (DEBUGGING(LEVELING)) DEBUG_POS("Moving ParkPos", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - stepper.synchronize(); - - // STEP 3 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(3) Disengage magnet "); - #endif - pe_deactivate_magnet(active_extruder); - - // STEP 4 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(4) Move to position near new extruder"); - #endif - current_position[X_AXIS] += (active_extruder == 0 ? 10 : -10); // move 10mm away from parked extruder - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Moving away from parked extruder", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - stepper.synchronize(); - - // STEP 5 - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(5) Engage magnetic field"); - #endif - - #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) - pe_activate_magnet(active_extruder); //just save power for inverted magnets - #endif - pe_activate_magnet(tmp_extruder); - - // STEP 6 - current_position[X_AXIS] = grabpos + (tmp_extruder == 0 ? (+10) : (-10)); - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - current_position[X_AXIS] = grabpos; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPAIR("(6) Unpark extruder ", tmp_extruder); - if (DEBUGGING(LEVELING)) DEBUG_POS("Move UnparkPos", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS]/2, active_extruder); - stepper.synchronize(); - - // Step 7 - current_position[X_AXIS] = midpos - hotend_offset[X_AXIS][tmp_extruder]; - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("(7) Move midway between hotends"); - if (DEBUGGING(LEVELING)) DEBUG_POS("Move midway to new extruder", current_position); - #endif - planner.buffer_line_kinematic(current_position, planner.max_feedrate_mm_s[X_AXIS], active_extruder); - stepper.synchronize(); - #if ENABLED(DEBUG_LEVELING_FEATURE) - SERIAL_ECHOLNPGM("Autopark done."); - #endif - } - else { // nomove == true - // Only engage magnetic field for new extruder - pe_activate_magnet(tmp_extruder); - #if ENABLED(PARKING_EXTRUDER_SOLENOIDS_INVERT) - pe_activate_magnet(active_extruder); // Just save power for inverted magnets - #endif - } - current_position[Z_AXIS] -= hotend_offset[Z_AXIS][tmp_extruder] - hotend_offset[Z_AXIS][active_extruder]; // Apply Zoffset - - #if ENABLED(DEBUG_LEVELING_FEATURE) - if (DEBUGGING(LEVELING)) DEBUG_POS("Applying Z-offset", current_position); - #endif - - #endif // dualParking extruder + #if ENABLED(PARKING_EXTRUDER) + parking_extruder_tool_change(tmp_extruder, no_move); + #endif #if ENABLED(SWITCHING_NOZZLE) #define DONT_SWITCH (SWITCHING_EXTRUDER_SERVO_NR == SWITCHING_NOZZLE_SERVO_NR)