|  |  |  | @ -139,6 +139,7 @@ | 
			
		
	
		
			
				
					|  |  |  |  | // M503 - print the current settings (from memory not from eeprom)
 | 
			
		
	
		
			
				
					|  |  |  |  | // M540 - Use S[0|1] to enable or disable the stop SD card print on endstop hit (requires ABORT_ON_ENDSTOP_HIT_FEATURE_ENABLED)
 | 
			
		
	
		
			
				
					|  |  |  |  | // M600 - Pause for filament change X[pos] Y[pos] Z[relative lift] E[initial retract] L[later retract distance for removal]
 | 
			
		
	
		
			
				
					|  |  |  |  | // M605 - Set dual x-carriage movement mode: S<mode> [ X<duplication x-offset> R<duplication temp offset> ]
 | 
			
		
	
		
			
				
					|  |  |  |  | // M907 - Set digital trimpot motor current using axis codes.
 | 
			
		
	
		
			
				
					|  |  |  |  | // M908 - Control digital trimpot directly.
 | 
			
		
	
		
			
				
					|  |  |  |  | // M350 - Set microstepping mode.
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -168,9 +169,15 @@ float current_position[NUM_AXIS] = { 0.0, 0.0, 0.0, 0.0 }; | 
			
		
	
		
			
				
					|  |  |  |  | float add_homeing[3]={0,0,0}; | 
			
		
	
		
			
				
					|  |  |  |  | float min_pos[3] = { X_MIN_POS, Y_MIN_POS, Z_MIN_POS }; | 
			
		
	
		
			
				
					|  |  |  |  | float max_pos[3] = { X_MAX_POS, Y_MAX_POS, Z_MAX_POS }; | 
			
		
	
		
			
				
					|  |  |  |  | // Extruder offset, only in XY plane
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | // Extruder offset
 | 
			
		
	
		
			
				
					|  |  |  |  | #if EXTRUDERS > 1 | 
			
		
	
		
			
				
					|  |  |  |  | float extruder_offset[2][EXTRUDERS] = { | 
			
		
	
		
			
				
					|  |  |  |  | #ifndef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |   #define NUM_EXTRUDER_OFFSETS 2 // only in XY plane
 | 
			
		
	
		
			
				
					|  |  |  |  | #else | 
			
		
	
		
			
				
					|  |  |  |  |   #define NUM_EXTRUDER_OFFSETS 3 // supports offsets in XYZ plane
 | 
			
		
	
		
			
				
					|  |  |  |  | #endif | 
			
		
	
		
			
				
					|  |  |  |  | float extruder_offset[NUM_EXTRUDER_OFFSETS][EXTRUDERS] = { | 
			
		
	
		
			
				
					|  |  |  |  | #if defined(EXTRUDER_OFFSET_X) && defined(EXTRUDER_OFFSET_Y) | 
			
		
	
		
			
				
					|  |  |  |  |   EXTRUDER_OFFSET_X, EXTRUDER_OFFSET_Y | 
			
		
	
		
			
				
					|  |  |  |  | #endif | 
			
		
	
	
		
			
				
					|  |  |  | @ -693,6 +700,11 @@ XYZ_CONSTS_FROM_CONFIG(signed char, home_dir,  HOME_DIR); | 
			
		
	
		
			
				
					|  |  |  |  |     #error "Please use canonical x-carriage assignment" // the x-carriages are defined by their homing directions
 | 
			
		
	
		
			
				
					|  |  |  |  |   #endif   | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | #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 float x_home_pos(int extruder) { | 
			
		
	
		
			
				
					|  |  |  |  |   if (extruder == 0) | 
			
		
	
		
			
				
					|  |  |  |  |     return base_home_pos(X_AXIS) + add_homeing[X_AXIS]; | 
			
		
	
	
		
			
				
					|  |  |  | @ -708,16 +720,31 @@ static int x_home_dir(int extruder) { | 
			
		
	
		
			
				
					|  |  |  |  |   return (extruder == 0) ? X_HOME_DIR : X2_HOME_DIR; | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | static float inactive_x_carriage_pos = X2_MAX_POS; | 
			
		
	
		
			
				
					|  |  |  |  | #endif | 
			
		
	
		
			
				
					|  |  |  |  | static float inactive_extruder_x_pos = X2_MAX_POS; // used in mode 0 & 1
 | 
			
		
	
		
			
				
					|  |  |  |  | static bool active_extruder_parked = false; // used in mode 1 & 2
 | 
			
		
	
		
			
				
					|  |  |  |  | static float raised_parked_position[NUM_AXIS]; // used in mode 1 
 | 
			
		
	
		
			
				
					|  |  |  |  | static unsigned long delayed_move_time = 0; // used in mode 1 
 | 
			
		
	
		
			
				
					|  |  |  |  | static float duplicate_extruder_x_offset = DEFAULT_DUPLICATION_X_OFFSET; // used in mode 2
 | 
			
		
	
		
			
				
					|  |  |  |  | static float duplicate_extruder_temp_offset = 0; // used in mode 2
 | 
			
		
	
		
			
				
					|  |  |  |  | bool extruder_duplication_enabled = false; // used in mode 2
 | 
			
		
	
		
			
				
					|  |  |  |  | #endif //DUAL_X_CARRIAGE    
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | static void axis_is_at_home(int axis) { | 
			
		
	
		
			
				
					|  |  |  |  | #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |   if (axis == X_AXIS && active_extruder != 0) { | 
			
		
	
		
			
				
					|  |  |  |  |     current_position[X_AXIS] = x_home_pos(active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |     min_pos[X_AXIS] =          X2_MIN_POS; | 
			
		
	
		
			
				
					|  |  |  |  |     max_pos[X_AXIS] =          max(extruder_offset[X_AXIS][1], X2_MAX_POS); | 
			
		
	
		
			
				
					|  |  |  |  |     return; | 
			
		
	
		
			
				
					|  |  |  |  |   if (axis == X_AXIS) { | 
			
		
	
		
			
				
					|  |  |  |  |     if (active_extruder != 0) { | 
			
		
	
		
			
				
					|  |  |  |  |       current_position[X_AXIS] = x_home_pos(active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |       min_pos[X_AXIS] =          X2_MIN_POS; | 
			
		
	
		
			
				
					|  |  |  |  |       max_pos[X_AXIS] =          max(extruder_offset[X_AXIS][1], X2_MAX_POS); | 
			
		
	
		
			
				
					|  |  |  |  |       return; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |     else if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && active_extruder == 0) { | 
			
		
	
		
			
				
					|  |  |  |  |       current_position[X_AXIS] = base_home_pos(X_AXIS) + add_homeing[X_AXIS]; | 
			
		
	
		
			
				
					|  |  |  |  |       min_pos[X_AXIS] =          base_min_pos(X_AXIS) + add_homeing[X_AXIS];  | 
			
		
	
		
			
				
					|  |  |  |  |       max_pos[X_AXIS] =          min(base_max_pos(X_AXIS) + add_homeing[X_AXIS],  | 
			
		
	
		
			
				
					|  |  |  |  |                                   max(extruder_offset[X_AXIS][1], X2_MAX_POS) - duplicate_extruder_x_offset); | 
			
		
	
		
			
				
					|  |  |  |  |       return; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | #endif | 
			
		
	
		
			
				
					|  |  |  |  |   current_position[axis] = base_home_pos(axis) + add_homeing[axis]; | 
			
		
	
	
		
			
				
					|  |  |  | @ -869,7 +896,7 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |       for(int8_t i=0; i < NUM_AXIS; i++) { | 
			
		
	
		
			
				
					|  |  |  |  |         destination[i] = current_position[i]; | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |           feedrate = 0.0; | 
			
		
	
		
			
				
					|  |  |  |  |       feedrate = 0.0; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | #ifdef DELTA | 
			
		
	
		
			
				
					|  |  |  |  |           // A delta can only safely home all axis at the same time
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -920,6 +947,7 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |         int x_axis_home_dir = home_dir(X_AXIS); | 
			
		
	
		
			
				
					|  |  |  |  |        #else | 
			
		
	
		
			
				
					|  |  |  |  |         int x_axis_home_dir = x_home_dir(active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |         extruder_duplication_enabled = false; | 
			
		
	
		
			
				
					|  |  |  |  |        #endif | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); | 
			
		
	
	
		
			
				
					|  |  |  | @ -950,12 +978,19 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |       { | 
			
		
	
		
			
				
					|  |  |  |  |       #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |         int tmp_extruder = active_extruder; | 
			
		
	
		
			
				
					|  |  |  |  |         extruder_duplication_enabled = false; | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder = !active_extruder; | 
			
		
	
		
			
				
					|  |  |  |  |         HOMEAXIS(X); | 
			
		
	
		
			
				
					|  |  |  |  |         inactive_x_carriage_pos = current_position[X_AXIS]; | 
			
		
	
		
			
				
					|  |  |  |  |         inactive_extruder_x_pos = current_position[X_AXIS]; | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder = tmp_extruder; | 
			
		
	
		
			
				
					|  |  |  |  |       #endif | 
			
		
	
		
			
				
					|  |  |  |  |         HOMEAXIS(X); | 
			
		
	
		
			
				
					|  |  |  |  |         // reset state used by the different modes
 | 
			
		
	
		
			
				
					|  |  |  |  |         memcpy(raised_parked_position, current_position, sizeof(raised_parked_position)); | 
			
		
	
		
			
				
					|  |  |  |  |         delayed_move_time = 0; | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder_parked = true;  | 
			
		
	
		
			
				
					|  |  |  |  |       #else       | 
			
		
	
		
			
				
					|  |  |  |  |         HOMEAXIS(X); | 
			
		
	
		
			
				
					|  |  |  |  |       #endif          | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |       if((home_all_axis) || (code_seen(axis_codes[Y_AXIS]))) { | 
			
		
	
	
		
			
				
					|  |  |  | @ -1199,6 +1234,10 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |         break; | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       if (code_seen('S')) setTargetHotend(code_value(), tmp_extruder); | 
			
		
	
		
			
				
					|  |  |  |  | #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |       if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && tmp_extruder == 0) | 
			
		
	
		
			
				
					|  |  |  |  |         setTargetHotend1(code_value() == 0.0 ? 0.0 : code_value() + duplicate_extruder_temp_offset); | 
			
		
	
		
			
				
					|  |  |  |  | #endif           | 
			
		
	
		
			
				
					|  |  |  |  |       setWatch(); | 
			
		
	
		
			
				
					|  |  |  |  |       break; | 
			
		
	
		
			
				
					|  |  |  |  |     case 140: // M140 set bed temp
 | 
			
		
	
	
		
			
				
					|  |  |  | @ -1252,9 +1291,17 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |       #endif | 
			
		
	
		
			
				
					|  |  |  |  |       if (code_seen('S')) { | 
			
		
	
		
			
				
					|  |  |  |  |         setTargetHotend(code_value(), tmp_extruder); | 
			
		
	
		
			
				
					|  |  |  |  | #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |         if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && tmp_extruder == 0) | 
			
		
	
		
			
				
					|  |  |  |  |           setTargetHotend1(code_value() == 0.0 ? 0.0 : code_value() + duplicate_extruder_temp_offset); | 
			
		
	
		
			
				
					|  |  |  |  | #endif           | 
			
		
	
		
			
				
					|  |  |  |  |         CooldownNoWait = true; | 
			
		
	
		
			
				
					|  |  |  |  |       } else if (code_seen('R')) { | 
			
		
	
		
			
				
					|  |  |  |  |         setTargetHotend(code_value(), tmp_extruder); | 
			
		
	
		
			
				
					|  |  |  |  | #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |         if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && tmp_extruder == 0) | 
			
		
	
		
			
				
					|  |  |  |  |           setTargetHotend1(code_value() == 0.0 ? 0.0 : code_value() + duplicate_extruder_temp_offset); | 
			
		
	
		
			
				
					|  |  |  |  | #endif           | 
			
		
	
		
			
				
					|  |  |  |  |         CooldownNoWait = false; | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       #ifdef AUTOTEMP | 
			
		
	
	
		
			
				
					|  |  |  | @ -1671,6 +1718,12 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |       { | 
			
		
	
		
			
				
					|  |  |  |  |         extruder_offset[Y_AXIS][tmp_extruder] = code_value(); | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |       if(code_seen('Z')) | 
			
		
	
		
			
				
					|  |  |  |  |       { | 
			
		
	
		
			
				
					|  |  |  |  |         extruder_offset[Z_AXIS][tmp_extruder] = code_value(); | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       #endif        | 
			
		
	
		
			
				
					|  |  |  |  |       SERIAL_ECHO_START; | 
			
		
	
		
			
				
					|  |  |  |  |       SERIAL_ECHOPGM(MSG_HOTEND_OFFSET); | 
			
		
	
		
			
				
					|  |  |  |  |       for(tmp_extruder = 0; tmp_extruder < EXTRUDERS; tmp_extruder++) | 
			
		
	
	
		
			
				
					|  |  |  | @ -1679,6 +1732,10 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |          SERIAL_ECHO(extruder_offset[X_AXIS][tmp_extruder]); | 
			
		
	
		
			
				
					|  |  |  |  |          SERIAL_ECHO(","); | 
			
		
	
		
			
				
					|  |  |  |  |          SERIAL_ECHO(extruder_offset[Y_AXIS][tmp_extruder]); | 
			
		
	
		
			
				
					|  |  |  |  |       #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |          SERIAL_ECHO(","); | 
			
		
	
		
			
				
					|  |  |  |  |          SERIAL_ECHO(extruder_offset[Z_AXIS][tmp_extruder]); | 
			
		
	
		
			
				
					|  |  |  |  |       #endif | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       SERIAL_ECHOLN(""); | 
			
		
	
		
			
				
					|  |  |  |  |     }break; | 
			
		
	
	
		
			
				
					|  |  |  | @ -2013,6 +2070,53 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |     break; | 
			
		
	
		
			
				
					|  |  |  |  |     #endif //FILAMENTCHANGEENABLE
 | 
			
		
	
		
			
				
					|  |  |  |  |     #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |     case 605: // Set dual x-carriage movement mode:
 | 
			
		
	
		
			
				
					|  |  |  |  |               //    M605 S0: Full control mode. The slicer has full control over x-carriage movement
 | 
			
		
	
		
			
				
					|  |  |  |  |               //    M605 S1: Auto-park mode. The inactive head will auto park/unpark without slicer involvement
 | 
			
		
	
		
			
				
					|  |  |  |  |               //    M605 S2 [Xnnn] [Rmmm]: Duplication mode. The second extruder will duplicate the first with nnn
 | 
			
		
	
		
			
				
					|  |  |  |  |               //                         millimeters x-offset and an optional differential hotend temperature of 
 | 
			
		
	
		
			
				
					|  |  |  |  |               //                         mmm degrees. E.g., with "M605 S2 X100 R2" the second extruder will duplicate
 | 
			
		
	
		
			
				
					|  |  |  |  |               //                         the first with a spacing of 100mm in the x direction and 2 degrees hotter.
 | 
			
		
	
		
			
				
					|  |  |  |  |               //
 | 
			
		
	
		
			
				
					|  |  |  |  |               //    Note: the X axis should be homed after changing dual x-carriage mode.
 | 
			
		
	
		
			
				
					|  |  |  |  |     { | 
			
		
	
		
			
				
					|  |  |  |  |         st_synchronize(); | 
			
		
	
		
			
				
					|  |  |  |  |          | 
			
		
	
		
			
				
					|  |  |  |  |         if (code_seen('S')) | 
			
		
	
		
			
				
					|  |  |  |  |           dual_x_carriage_mode = code_value(); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (dual_x_carriage_mode == DXC_DUPLICATION_MODE) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           if (code_seen('X')) | 
			
		
	
		
			
				
					|  |  |  |  |             duplicate_extruder_x_offset = max(code_value(),X2_MIN_POS - x_home_pos(0)); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |           if (code_seen('R')) | 
			
		
	
		
			
				
					|  |  |  |  |             duplicate_extruder_temp_offset = code_value(); | 
			
		
	
		
			
				
					|  |  |  |  |              | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO_START; | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHOPGM(MSG_HOTEND_OFFSET); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(" "); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(extruder_offset[X_AXIS][0]); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(","); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(extruder_offset[Y_AXIS][0]); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(" "); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(duplicate_extruder_x_offset); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHO(","); | 
			
		
	
		
			
				
					|  |  |  |  |           SERIAL_ECHOLN(extruder_offset[Y_AXIS][1]); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         else if (dual_x_carriage_mode != DXC_FULL_CONTROL_MODE && dual_x_carriage_mode != DXC_AUTO_PARK_MODE) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |          | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder_parked = false; | 
			
		
	
		
			
				
					|  |  |  |  |         extruder_duplication_enabled = false; | 
			
		
	
		
			
				
					|  |  |  |  |         delayed_move_time = 0; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |     break; | 
			
		
	
		
			
				
					|  |  |  |  |     #endif //DUAL_X_CARRIAGE         
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |     case 907: // M907 Set digital trimpot motor current using axis codes.
 | 
			
		
	
		
			
				
					|  |  |  |  |     { | 
			
		
	
		
			
				
					|  |  |  |  |       #if defined(DIGIPOTSS_PIN) && DIGIPOTSS_PIN > -1 | 
			
		
	
	
		
			
				
					|  |  |  | @ -2092,18 +2196,55 @@ void process_commands() | 
			
		
	
		
			
				
					|  |  |  |  |         // Save current position to return to after applying extruder offset
 | 
			
		
	
		
			
				
					|  |  |  |  |         memcpy(destination, current_position, sizeof(destination)); | 
			
		
	
		
			
				
					|  |  |  |  |       #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |         // only apply Y extruder offset in dual x carriage mode (x offset is already used in determining home pos)
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (dual_x_carriage_mode == DXC_AUTO_PARK_MODE && Stopped == false &&  | 
			
		
	
		
			
				
					|  |  |  |  |             (delayed_move_time != 0 || current_position[X_AXIS] != x_home_pos(active_extruder))) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           // Park old head: 1) raise 2) move to park position 3) lower
 | 
			
		
	
		
			
				
					|  |  |  |  |           plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,  | 
			
		
	
		
			
				
					|  |  |  |  |                 current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |           plan_buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS] + TOOLCHANGE_PARK_ZLIFT,  | 
			
		
	
		
			
				
					|  |  |  |  |                 current_position[E_AXIS], max_feedrate[X_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |           plan_buffer_line(x_home_pos(active_extruder), current_position[Y_AXIS], current_position[Z_AXIS],  | 
			
		
	
		
			
				
					|  |  |  |  |                 current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |           st_synchronize(); | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |          | 
			
		
	
		
			
				
					|  |  |  |  |         // apply Y & Z extruder offset (x offset is already used in determining home pos)
 | 
			
		
	
		
			
				
					|  |  |  |  |         current_position[Y_AXIS] = current_position[Y_AXIS] - | 
			
		
	
		
			
				
					|  |  |  |  |                      extruder_offset[Y_AXIS][active_extruder] + | 
			
		
	
		
			
				
					|  |  |  |  |                      extruder_offset[Y_AXIS][tmp_extruder]; | 
			
		
	
		
			
				
					|  |  |  |  |         current_position[Z_AXIS] = current_position[Z_AXIS] - | 
			
		
	
		
			
				
					|  |  |  |  |                      extruder_offset[Z_AXIS][active_extruder] + | 
			
		
	
		
			
				
					|  |  |  |  |                      extruder_offset[Z_AXIS][tmp_extruder]; | 
			
		
	
		
			
				
					|  |  |  |  |                       | 
			
		
	
		
			
				
					|  |  |  |  |         float tmp_x_pos = current_position[X_AXIS]; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // Set the new active extruder and position
 | 
			
		
	
		
			
				
					|  |  |  |  |         active_extruder = tmp_extruder; | 
			
		
	
		
			
				
					|  |  |  |  |         axis_is_at_home(X_AXIS); //this function updates X min/max values.
 | 
			
		
	
		
			
				
					|  |  |  |  |         current_position[X_AXIS] = inactive_x_carriage_pos; | 
			
		
	
		
			
				
					|  |  |  |  |         inactive_x_carriage_pos = tmp_x_pos; | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         // This function resets the max/min values - the current position may be overwritten below.
 | 
			
		
	
		
			
				
					|  |  |  |  |         axis_is_at_home(X_AXIS); | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (dual_x_carriage_mode == DXC_FULL_CONTROL_MODE) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[X_AXIS] = inactive_extruder_x_pos;  | 
			
		
	
		
			
				
					|  |  |  |  |           inactive_extruder_x_pos = destination[X_AXIS]; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         else if (dual_x_carriage_mode == DXC_DUPLICATION_MODE) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           active_extruder_parked = (active_extruder == 0); // this triggers the second extruder to move into the duplication position
 | 
			
		
	
		
			
				
					|  |  |  |  |           if (active_extruder == 0 || 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;  | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |         else | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           // 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; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |       #else     | 
			
		
	
		
			
				
					|  |  |  |  |         // Offset extruder (only by XY)
 | 
			
		
	
		
			
				
					|  |  |  |  |         int i; | 
			
		
	
	
		
			
				
					|  |  |  | @ -2309,6 +2450,48 @@ void prepare_move() | 
			
		
	
		
			
				
					|  |  |  |  |                      active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | #else | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  | #ifdef DUAL_X_CARRIAGE | 
			
		
	
		
			
				
					|  |  |  |  |   if (active_extruder_parked) | 
			
		
	
		
			
				
					|  |  |  |  |   { | 
			
		
	
		
			
				
					|  |  |  |  |     if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && active_extruder == 0) | 
			
		
	
		
			
				
					|  |  |  |  |     { | 
			
		
	
		
			
				
					|  |  |  |  |       // move duplicate extruder into correct duplication position.
 | 
			
		
	
		
			
				
					|  |  |  |  |       plan_set_position(inactive_extruder_x_pos, current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); | 
			
		
	
		
			
				
					|  |  |  |  |       plan_buffer_line(current_position[X_AXIS] + duplicate_extruder_x_offset, current_position[Y_AXIS], current_position[Z_AXIS],  | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[E_AXIS], max_feedrate[X_AXIS], 1); | 
			
		
	
		
			
				
					|  |  |  |  |       plan_set_position(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]); | 
			
		
	
		
			
				
					|  |  |  |  |       st_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 - skit it but keep track of current position (so that it can later
 | 
			
		
	
		
			
				
					|  |  |  |  |         // be used as start of first non-travel move)
 | 
			
		
	
		
			
				
					|  |  |  |  |         if (delayed_move_time != 0xFFFFFFFFUL) | 
			
		
	
		
			
				
					|  |  |  |  |         { | 
			
		
	
		
			
				
					|  |  |  |  |           memcpy(current_position, destination, sizeof(current_position));  | 
			
		
	
		
			
				
					|  |  |  |  |           if (destination[Z_AXIS] > raised_parked_position[Z_AXIS]) | 
			
		
	
		
			
				
					|  |  |  |  |             raised_parked_position[Z_AXIS] = destination[Z_AXIS]; | 
			
		
	
		
			
				
					|  |  |  |  |           delayed_move_time = millis(); | 
			
		
	
		
			
				
					|  |  |  |  |           return; | 
			
		
	
		
			
				
					|  |  |  |  |         } | 
			
		
	
		
			
				
					|  |  |  |  |       } | 
			
		
	
		
			
				
					|  |  |  |  |       delayed_move_time = 0; | 
			
		
	
		
			
				
					|  |  |  |  |       // unpark extruder: 1) raise, 2) move into starting XY position, 3) lower
 | 
			
		
	
		
			
				
					|  |  |  |  |       plan_buffer_line(raised_parked_position[X_AXIS], raised_parked_position[Y_AXIS], raised_parked_position[Z_AXIS],    current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |       plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], raised_parked_position[Z_AXIS],  | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[E_AXIS], min(max_feedrate[X_AXIS],max_feedrate[Y_AXIS]), active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |       plan_buffer_line(current_position[X_AXIS], current_position[Y_AXIS], current_position[Z_AXIS],  | 
			
		
	
		
			
				
					|  |  |  |  |           current_position[E_AXIS], max_feedrate[Z_AXIS], active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |       active_extruder_parked = false; | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | #endif //DUAL_X_CARRIAGE
 | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
		
			
				
					|  |  |  |  |   // Do not use feedmultiply for E or Z only moves
 | 
			
		
	
		
			
				
					|  |  |  |  |   if( (current_position[X_AXIS] == destination [X_AXIS]) && (current_position[Y_AXIS] == destination [Y_AXIS])) { | 
			
		
	
		
			
				
					|  |  |  |  |       plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate/60, active_extruder); | 
			
		
	
	
		
			
				
					|  |  |  | @ -2316,7 +2499,7 @@ void prepare_move() | 
			
		
	
		
			
				
					|  |  |  |  |   else { | 
			
		
	
		
			
				
					|  |  |  |  |     plan_buffer_line(destination[X_AXIS], destination[Y_AXIS], destination[Z_AXIS], destination[E_AXIS], feedrate*feedmultiply/60/100.0, active_extruder); | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
		
			
				
					|  |  |  |  | #endif | 
			
		
	
		
			
				
					|  |  |  |  | #endif //else DELTA
 | 
			
		
	
		
			
				
					|  |  |  |  |   for(int8_t i=0; i < NUM_AXIS; i++) { | 
			
		
	
		
			
				
					|  |  |  |  |     current_position[i] = destination[i]; | 
			
		
	
		
			
				
					|  |  |  |  |   } | 
			
		
	
	
		
			
				
					|  |  |  | @ -2428,6 +2611,16 @@ void manage_inactivity() | 
			
		
	
		
			
				
					|  |  |  |  |      WRITE(E0_ENABLE_PIN,oldstatus); | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |   #endif | 
			
		
	
		
			
				
					|  |  |  |  |   #if defined(DUAL_X_CARRIAGE) | 
			
		
	
		
			
				
					|  |  |  |  |     // handle delayed move timeout
 | 
			
		
	
		
			
				
					|  |  |  |  |     if (delayed_move_time != 0 && (millis() - delayed_move_time) > 1000 && Stopped == false) | 
			
		
	
		
			
				
					|  |  |  |  |     { | 
			
		
	
		
			
				
					|  |  |  |  |       // travel moves have been received so enact them
 | 
			
		
	
		
			
				
					|  |  |  |  |       delayed_move_time = 0xFFFFFFFFUL; // force moves to be done
 | 
			
		
	
		
			
				
					|  |  |  |  |       memcpy(destination,current_position,sizeof(destination)); | 
			
		
	
		
			
				
					|  |  |  |  |       prepare_move();  | 
			
		
	
		
			
				
					|  |  |  |  |     } | 
			
		
	
		
			
				
					|  |  |  |  |   #endif   | 
			
		
	
		
			
				
					|  |  |  |  |   check_axes_activity(); | 
			
		
	
		
			
				
					|  |  |  |  | } | 
			
		
	
		
			
				
					|  |  |  |  | 
 | 
			
		
	
	
		
			
				
					|  |  |  | 
 |