|  |  |  | @ -1303,11 +1303,7 @@ bool get_target_extruder_from_command(int code) { | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | #if ENABLED(DUAL_X_CARRIAGE) | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   #define DXC_FULL_CONTROL_MODE 0 | 
			
		
	
		
			
				
					|  |  |  |  |   #define DXC_AUTO_PARK_MODE    1 | 
			
		
	
		
			
				
					|  |  |  |  |   #define DXC_DUPLICATION_MODE  2 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   static int dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; | 
			
		
	
		
			
				
					|  |  |  |  |   static DualXMode dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   static float x_home_pos(int extruder) { | 
			
		
	
		
			
				
					|  |  |  |  |     if (extruder == 0) | 
			
		
	
	
		
			
				
					|  |  |  | @ -6950,8 +6946,11 @@ inline void gcode_M503() { | 
			
		
	
		
			
				
					|  |  |  |  |    */ | 
			
		
	
		
			
				
					|  |  |  |  |   inline void gcode_M605() { | 
			
		
	
		
			
				
					|  |  |  |  |     stepper.synchronize(); | 
			
		
	
		
			
				
					|  |  |  |  |     if (code_seen('S')) dual_x_carriage_mode = code_value_byte(); | 
			
		
	
		
			
				
					|  |  |  |  |     if (code_seen('S')) dual_x_carriage_mode = (DualXMode)code_value_byte(); | 
			
		
	
		
			
				
					|  |  |  |  |     switch (dual_x_carriage_mode) { | 
			
		
	
		
			
				
					|  |  |  |  |       case DXC_FULL_CONTROL_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |       case DXC_AUTO_PARK_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |         break; | 
			
		
	
		
			
				
					|  |  |  |  |       case DXC_DUPLICATION_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |         if (code_seen('X')) duplicate_extruder_x_offset = max(code_value_axis_units(X_AXIS), X2_MIN_POS - x_home_pos(0)); | 
			
		
	
		
			
				
					|  |  |  |  |         if (code_seen('R')) duplicate_extruder_temp_offset = code_value_temp_diff(); | 
			
		
	
	
		
			
				
					|  |  |  | @ -6966,9 +6965,6 @@ inline void gcode_M503() { | 
			
		
	
		
			
				
					|  |  |  |  |         SERIAL_CHAR(','); | 
			
		
	
		
			
				
					|  |  |  |  |         SERIAL_ECHOLN(hotend_offset[Y_AXIS][1]); | 
			
		
	
		
			
				
					|  |  |  |  |         break; | 
			
		
	
		
			
				
					|  |  |  |  |       case DXC_FULL_CONTROL_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |       case DXC_AUTO_PARK_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |         break; | 
			
		
	
		
			
				
					|  |  |  |  |       default: | 
			
		
	
		
			
				
					|  |  |  |  |         dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; | 
			
		
	
		
			
				
					|  |  |  |  |         break; | 
			
		
	
	
		
			
				
					|  |  |  | @ -7258,9 +7254,9 @@ void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool n | 
			
		
	
		
			
				
					|  |  |  |  |             if (DEBUGGING(LEVELING)) { | 
			
		
	
		
			
				
					|  |  |  |  |               SERIAL_ECHOPGM("Dual X Carriage Mode "); | 
			
		
	
		
			
				
					|  |  |  |  |               switch (dual_x_carriage_mode) { | 
			
		
	
		
			
				
					|  |  |  |  |                 case DXC_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_DUPLICATION_MODE"); break; | 
			
		
	
		
			
				
					|  |  |  |  |                 case DXC_AUTO_PARK_MODE: SERIAL_ECHOLNPGM("DXC_AUTO_PARK_MODE"); break; | 
			
		
	
		
			
				
					|  |  |  |  |                 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 | 
			
		
	
	
		
			
				
					|  |  |  | @ -7305,6 +7301,16 @@ void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool n | 
			
		
	
		
			
				
					|  |  |  |  |               current_position[X_AXIS] = LOGICAL_X_POSITION(inactive_extruder_x_pos); | 
			
		
	
		
			
				
					|  |  |  |  |               inactive_extruder_x_pos = RAW_X_POSITION(destination[X_AXIS]); | 
			
		
	
		
			
				
					|  |  |  |  |               break; | 
			
		
	
		
			
				
					|  |  |  |  |             case DXC_AUTO_PARK_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |               // record raised toolhead position for use by unpark
 | 
			
		
	
		
			
				
					|  |  |  |  |               memcpy(raised_parked_position, current_position, sizeof(raised_parked_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: | 
			
		
	
		
			
				
					|  |  |  |  |               active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
 | 
			
		
	
		
			
				
					|  |  |  |  |               if (active_extruder_parked) | 
			
		
	
	
		
			
				
					|  |  |  | @ -7314,13 +7320,6 @@ void tool_change(const uint8_t tmp_extruder, const float fr_mm_s/*=0.0*/, bool n | 
			
		
	
		
			
				
					|  |  |  |  |               inactive_extruder_x_pos = RAW_X_POSITION(destination[X_AXIS]); | 
			
		
	
		
			
				
					|  |  |  |  |               extruder_duplication_enabled = false; | 
			
		
	
		
			
				
					|  |  |  |  |               break; | 
			
		
	
		
			
				
					|  |  |  |  |             default: | 
			
		
	
		
			
				
					|  |  |  |  |               // record raised toolhead position for use by unpark
 | 
			
		
	
		
			
				
					|  |  |  |  |               memcpy(raised_parked_position, current_position, sizeof(raised_parked_position)); | 
			
		
	
		
			
				
					|  |  |  |  |               raised_parked_position[Z_AXIS] += TOOLCHANGE_UNPARK_ZLIFT; | 
			
		
	
		
			
				
					|  |  |  |  |               active_extruder_parked = true; | 
			
		
	
		
			
				
					|  |  |  |  |               delayed_move_time = 0; | 
			
		
	
		
			
				
					|  |  |  |  |               break; | 
			
		
	
		
			
				
					|  |  |  |  |           } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |           #if ENABLED(DEBUG_LEVELING_FEATURE) | 
			
		
	
	
		
			
				
					|  |  |  | @ -8975,39 +8974,45 @@ void set_current_from_steppers_for_axis(const AxisEnum axis) { | 
			
		
	
		
			
				
					|  |  |  |  |    */ | 
			
		
	
		
			
				
					|  |  |  |  |   inline bool prepare_move_to_destination_dualx() { | 
			
		
	
		
			
				
					|  |  |  |  |     if (active_extruder_parked) { | 
			
		
	
		
			
				
					|  |  |  |  |       if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && active_extruder == 0) { | 
			
		
	
		
			
				
					|  |  |  |  |         // move duplicate extruder into correct duplication position.
 | 
			
		
	
		
			
				
					|  |  |  |  |         planner.set_position_mm( | 
			
		
	
		
			
				
					|  |  |  |  |           LOGICAL_X_POSITION(inactive_extruder_x_pos), | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[Y_AXIS], | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[Z_AXIS], | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[E_AXIS] | 
			
		
	
		
			
				
					|  |  |  |  |         ); | 
			
		
	
		
			
				
					|  |  |  |  |         planner.buffer_line(current_position[X_AXIS] + duplicate_extruder_x_offset, | 
			
		
	
		
			
				
					|  |  |  |  |                          current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], planner.max_feedrate_mm_s[X_AXIS], 1); | 
			
		
	
		
			
				
					|  |  |  |  |         SYNC_PLAN_POSITION_KINEMATIC(); | 
			
		
	
		
			
				
					|  |  |  |  |         stepper.synchronize(); | 
			
		
	
		
			
				
					|  |  |  |  |         extruder_duplication_enabled = true; | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder_parked = false; | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       else if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE) { // handle unparking of head
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (current_position[E_AXIS] == destination[E_AXIS]) { | 
			
		
	
		
			
				
					|  |  |  |  |           // This is a travel move (with no extrusion)
 | 
			
		
	
		
			
				
					|  |  |  |  |           // Skip it, but keep track of the current position
 | 
			
		
	
		
			
				
					|  |  |  |  |           // (so it can be used as the start of the next non-travel move)
 | 
			
		
	
		
			
				
					|  |  |  |  |           if (delayed_move_time != 0xFFFFFFFFUL) { | 
			
		
	
		
			
				
					|  |  |  |  |             set_current_to_destination(); | 
			
		
	
		
			
				
					|  |  |  |  |             NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]); | 
			
		
	
		
			
				
					|  |  |  |  |             delayed_move_time = millis(); | 
			
		
	
		
			
				
					|  |  |  |  |             return false; | 
			
		
	
		
			
				
					|  |  |  |  |       switch (dual_x_carriage_mode) { | 
			
		
	
		
			
				
					|  |  |  |  |         case DXC_FULL_CONTROL_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |           break; | 
			
		
	
		
			
				
					|  |  |  |  |         case DXC_DUPLICATION_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |           if (active_extruder == 0) { | 
			
		
	
		
			
				
					|  |  |  |  |             // move duplicate extruder into correct duplication position.
 | 
			
		
	
		
			
				
					|  |  |  |  |             planner.set_position_mm( | 
			
		
	
		
			
				
					|  |  |  |  |               LOGICAL_X_POSITION(inactive_extruder_x_pos), | 
			
		
	
		
			
				
					|  |  |  |  |               current_position[Y_AXIS], | 
			
		
	
		
			
				
					|  |  |  |  |               current_position[Z_AXIS], | 
			
		
	
		
			
				
					|  |  |  |  |               current_position[E_AXIS] | 
			
		
	
		
			
				
					|  |  |  |  |             ); | 
			
		
	
		
			
				
					|  |  |  |  |             planner.buffer_line(current_position[X_AXIS] + duplicate_extruder_x_offset, | 
			
		
	
		
			
				
					|  |  |  |  |                              current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], planner.max_feedrate_mm_s[X_AXIS], 1); | 
			
		
	
		
			
				
					|  |  |  |  |             SYNC_PLAN_POSITION_KINEMATIC(); | 
			
		
	
		
			
				
					|  |  |  |  |             stepper.synchronize(); | 
			
		
	
		
			
				
					|  |  |  |  |             extruder_duplication_enabled = true; | 
			
		
	
		
			
				
					|  |  |  |  |             active_extruder_parked = false; | 
			
		
	
		
			
				
					|  |  |  |  |           } | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         delayed_move_time = 0; | 
			
		
	
		
			
				
					|  |  |  |  |         // unpark extruder: 1) raise, 2) move into starting XY position, 3) lower
 | 
			
		
	
		
			
				
					|  |  |  |  |         planner.buffer_line(raised_parked_position[X_AXIS], raised_parked_position[Y_AXIS], raised_parked_position[Z_AXIS], current_position[E_AXIS], planner.max_feedrate_mm_s[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |         planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], raised_parked_position[Z_AXIS], current_position[E_AXIS], PLANNER_XY_FEEDRATE(), active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |         planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], planner.max_feedrate_mm_s[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder_parked = false; | 
			
		
	
		
			
				
					|  |  |  |  |           break; | 
			
		
	
		
			
				
					|  |  |  |  |         case DXC_AUTO_PARK_MODE: | 
			
		
	
		
			
				
					|  |  |  |  |           if (current_position[E_AXIS] == destination[E_AXIS]) { | 
			
		
	
		
			
				
					|  |  |  |  |             // This is a travel move (with no extrusion)
 | 
			
		
	
		
			
				
					|  |  |  |  |             // Skip it, but keep track of the current position
 | 
			
		
	
		
			
				
					|  |  |  |  |             // (so it can be used as the start of the next non-travel move)
 | 
			
		
	
		
			
				
					|  |  |  |  |             if (delayed_move_time != 0xFFFFFFFFUL) { | 
			
		
	
		
			
				
					|  |  |  |  |               set_current_to_destination(); | 
			
		
	
		
			
				
					|  |  |  |  |               NOLESS(raised_parked_position[Z_AXIS], destination[Z_AXIS]); | 
			
		
	
		
			
				
					|  |  |  |  |               delayed_move_time = millis(); | 
			
		
	
		
			
				
					|  |  |  |  |               return false; | 
			
		
	
		
			
				
					|  |  |  |  |             } | 
			
		
	
		
			
				
					|  |  |  |  |           } | 
			
		
	
		
			
				
					|  |  |  |  |           delayed_move_time = 0; | 
			
		
	
		
			
				
					|  |  |  |  |           // unpark extruder: 1) raise, 2) move into starting XY position, 3) lower
 | 
			
		
	
		
			
				
					|  |  |  |  |           planner.buffer_line(raised_parked_position[X_AXIS], raised_parked_position[Y_AXIS], raised_parked_position[Z_AXIS], current_position[E_AXIS], planner.max_feedrate_mm_s[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |           planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], raised_parked_position[Z_AXIS], current_position[E_AXIS], PLANNER_XY_FEEDRATE(), active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |           planner.buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], planner.max_feedrate_mm_s[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |           active_extruder_parked = false; | 
			
		
	
		
			
				
					|  |  |  |  |           break; | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |     return true; | 
			
		
	
	
		
			
				
					|  |  |  | 
 |