From 0780913848eea5048947808dfcc9143131f094d3 Mon Sep 17 00:00:00 2001
From: Roxy-3D <Roxy-3D@users.noreply.github.com>
Date: Mon, 17 Sep 2018 01:06:22 -0500
Subject: [PATCH] IDEX Improvements (#11848)

---
 .../examples/Formbot/T-Rex_2+/Configuration.h |  2 +-
 .../Formbot/T_Rex_3/Configuration_adv.h       |  4 +-
 Marlin/src/feature/Max7219_Debug_LEDs.cpp     |  2 +
 Marlin/src/feature/pause.cpp                  | 66 +++++++++++++++----
 Marlin/src/feature/pause.h                    | 18 +++--
 Marlin/src/feature/runout.h                   | 39 ++++++-----
 Marlin/src/gcode/calibrate/G28.cpp            |  5 +-
 Marlin/src/gcode/control/M605.cpp             | 65 +++++++++++++++---
 Marlin/src/gcode/feature/leds/M7219.cpp       |  2 +-
 Marlin/src/gcode/feature/pause/M600.cpp       | 31 +++++++--
 Marlin/src/gcode/feature/pause/M701_M702.cpp  |  6 +-
 Marlin/src/gcode/queue.cpp                    |  6 +-
 Marlin/src/gcode/temperature/M104_M109.cpp    |  4 +-
 Marlin/src/lcd/language/language_en.h         | 15 +++++
 Marlin/src/lcd/ultralcd.cpp                   | 30 +++++++--
 Marlin/src/module/configuration_store.cpp     |  3 +
 Marlin/src/module/motion.cpp                  | 22 ++++---
 Marlin/src/module/motion.h                    | 11 ++--
 Marlin/src/module/stepper.cpp                 |  2 +-
 Marlin/src/module/stepper.h                   |  5 +-
 Marlin/src/module/stepper_indirection.h       | 15 ++++-
 Marlin/src/module/tool_change.cpp             |  7 +-
 Marlin/src/pins/pins_FORMBOT_TREX2.h          | 22 +++----
 Marlin/src/pins/pins_FORMBOT_TREX3.h          | 18 ++---
 24 files changed, 285 insertions(+), 115 deletions(-)

diff --git a/Marlin/src/config/examples/Formbot/T-Rex_2+/Configuration.h b/Marlin/src/config/examples/Formbot/T-Rex_2+/Configuration.h
index 408291c938..7079ea881f 100644
--- a/Marlin/src/config/examples/Formbot/T-Rex_2+/Configuration.h
+++ b/Marlin/src/config/examples/Formbot/T-Rex_2+/Configuration.h
@@ -22,7 +22,7 @@
 
 //#define TREX3              // Turn this on for T-Rex 3 features like dual filament run out sensors
 
-#define ROXYs_TRex           // Turn this on to get customizations only available on Roxy's T-Rex 2+
+//#define ROXYs_TRex         // Turn this on to get customizations only available on Roxy's T-Rex 2+
                              // Marlin controlled heat bed, Max7219 debug LED's, less bright LED light level
                              // More aggressive PID numbers for hotends (due to double fans)
 /**
diff --git a/Marlin/src/config/examples/Formbot/T_Rex_3/Configuration_adv.h b/Marlin/src/config/examples/Formbot/T_Rex_3/Configuration_adv.h
index 72d1ce5c2f..522b335756 100644
--- a/Marlin/src/config/examples/Formbot/T_Rex_3/Configuration_adv.h
+++ b/Marlin/src/config/examples/Formbot/T_Rex_3/Configuration_adv.h
@@ -385,9 +385,7 @@
   //    Mode 2 (DXC_DUPLICATION_MODE) :           Duplication mode. The firmware will transparently make the second x-carriage and extruder copy all
   //                                              actions of the first x-carriage. This allows the printer to print 2 arbitrary items at
   //                                              once. (2nd extruder x offset and temp offset are set using: M605 S2 [Xnnn] [Rmmm])
-  //    Mode 3 (DXC_SYMMETRIC_DUPLICATION_MODE) : Symmetric Duplication mode. The firmware will perform similarly to DXC_DUPLICATION_MODE except in a mirror
-  //                                              image of the first x-carriage.  ie. If you are printing a right hand shoe on the 1st extruder, you will
-  //                                              get a left hand shoe on the 2nd extruder.
+  //    Mode 3 (DXC_SCALED_DUPLICATION_MODE) :    Not working yet, but support routines in place
 
   // This is the default power-up mode which can be later using M605.
   #define DEFAULT_DUAL_X_CARRIAGE_MODE DXC_AUTO_PARK_MODE
diff --git a/Marlin/src/feature/Max7219_Debug_LEDs.cpp b/Marlin/src/feature/Max7219_Debug_LEDs.cpp
index 0588c836b5..c8aaaaffeb 100644
--- a/Marlin/src/feature/Max7219_Debug_LEDs.cpp
+++ b/Marlin/src/feature/Max7219_Debug_LEDs.cpp
@@ -56,6 +56,8 @@ uint8_t Max7219::led_line[MAX7219_LINES]; // = { 0 };
 #if _ROT == 0 || _ROT == 270
   #define _LED_BIT(Q)   (7 - ((Q) & 0x7))
   #define _LED_UNIT(Q)  ((Q) & ~0x7)
+  //#define _LED_UNIT(Q)  ((MAX7219_NUMBER_UNITS - 1 - ((Q) >> 3)) << 3)  // some Max7219 boards have rotated the matrix
+                                                                          // this line can be substituted to correct orientation
 #else
   #define _LED_BIT(Q)   ((Q) & 0x7)
   #define _LED_UNIT(Q)  ((MAX7219_NUMBER_UNITS - 1 - ((Q) >> 3)) << 3)
diff --git a/Marlin/src/feature/pause.cpp b/Marlin/src/feature/pause.cpp
index 0af61060d6..2b7f547dd6 100644
--- a/Marlin/src/feature/pause.cpp
+++ b/Marlin/src/feature/pause.cpp
@@ -140,6 +140,7 @@ static void do_pause_e_move(const float &length, const float &fr) {
 bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_length/*=0*/, const float &purge_length/*=0*/, const int8_t max_beep_count/*=0*/,
                    const bool show_lcd/*=false*/, const bool pause_for_user/*=false*/,
                    const AdvancedPauseMode mode/*=ADVANCED_PAUSE_MODE_PAUSE_PRINT*/
+                   DXC_ARGS
 ) {
   #if DISABLED(ULTIPANEL)
     UNUSED(show_lcd);
@@ -184,6 +185,13 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l
       lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_LOAD, mode);
   #endif
 
+  #if ENABLED(DUAL_X_CARRIAGE)
+    const int8_t saved_ext        = active_extruder;
+    const bool saved_ext_dup_mode = extruder_duplication_enabled;
+    active_extruder = DXC_ext;
+    extruder_duplication_enabled = false;
+  #endif
+
   // Slow Load filament
   if (slow_load_length) do_pause_e_move(slow_load_length, FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE);
 
@@ -201,6 +209,12 @@ bool load_filament(const float &slow_load_length/*=0*/, const float &fast_load_l
     #endif
   }
 
+  #if ENABLED(DUAL_X_CARRIAGE)      // Tie the two extruders movement back together.
+    active_extruder = saved_ext;
+    extruder_duplication_enabled = saved_ext_dup_mode;
+    stepper.set_directions();
+  #endif
+
   #if ENABLED(ADVANCED_PAUSE_CONTINUOUS_PURGE)
 
     #if ENABLED(ULTIPANEL)
@@ -328,7 +342,8 @@ bool unload_filament(const float &unload_length, const bool show_lcd/*=false*/,
  */
 uint8_t did_pause_print = 0;
 
-bool pause_print(const float &retract, const point_t &park_point, const float &unload_length/*=0*/, const bool show_lcd/*=false*/) {
+bool pause_print(const float &retract, const point_t &park_point, const float &unload_length/*=0*/, const bool show_lcd/*=false*/ DXC_ARGS) {
+
   if (did_pause_print) return false; // already paused
 
   #ifdef ACTION_ON_PAUSE
@@ -380,10 +395,22 @@ bool pause_print(const float &retract, const point_t &park_point, const float &u
   if (!axis_unhomed_error())
     Nozzle::park(2, park_point);
 
-  // Unload the filament
-  if (unload_length)
+  #if ENABLED(DUAL_X_CARRIAGE)
+    const int8_t saved_ext        = active_extruder;
+    const bool saved_ext_dup_mode = extruder_duplication_enabled;
+    active_extruder = DXC_ext;
+    extruder_duplication_enabled = false;
+  #endif
+
+  if (unload_length)   // Unload the filament
     unload_filament(unload_length, show_lcd);
 
+  #if ENABLED(DUAL_X_CARRIAGE)
+    active_extruder = saved_ext;
+    extruder_duplication_enabled = saved_ext_dup_mode;
+    stepper.set_directions();
+  #endif
+
   return true;
 }
 
@@ -394,7 +421,7 @@ bool pause_print(const float &retract, const point_t &park_point, const float &u
  *
  * Used by M125 and M600
  */
-void wait_for_filament_reload(const int8_t max_beep_count/*=0*/) {
+void wait_for_filament_reload(const int8_t max_beep_count/*=0*/ DXC_ARGS) {
   bool nozzle_timed_out = false;
 
   #if ENABLED(ULTIPANEL)
@@ -413,6 +440,13 @@ void wait_for_filament_reload(const int8_t max_beep_count/*=0*/) {
   HOTEND_LOOP()
     thermalManager.start_heater_idle_timer(e, nozzle_timeout);
 
+  #if ENABLED(DUAL_X_CARRIAGE)
+    const int8_t saved_ext        = active_extruder;
+    const bool saved_ext_dup_mode = extruder_duplication_enabled;
+    active_extruder = DXC_ext;
+    extruder_duplication_enabled = false;
+  #endif
+
   // Wait for filament insert by user and press button
   KEEPALIVE_STATE(PAUSED_FOR_USER);
   wait_for_user = true;    // LCD click or M108 will clear this
@@ -477,6 +511,11 @@ void wait_for_filament_reload(const int8_t max_beep_count/*=0*/) {
 
     idle(true);
   }
+  #if ENABLED(DUAL_X_CARRIAGE)
+    active_extruder = saved_ext;
+    extruder_duplication_enabled = saved_ext_dup_mode;
+    stepper.set_directions();
+  #endif
   KEEPALIVE_STATE(IN_HANDLER);
 }
 
@@ -498,7 +537,15 @@ void wait_for_filament_reload(const int8_t max_beep_count/*=0*/) {
  * - Send host action for resume, if configured
  * - Resume the current SD print job, if any
  */
-void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_length/*=0*/, const float &purge_length/*=ADVANCED_PAUSE_PURGE_LENGTH*/, const int8_t max_beep_count/*=0*/) {
+void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_length/*=0*/, const float &purge_length/*=ADVANCED_PAUSE_PURGE_LENGTH*/, const int8_t max_beep_count/*=0*/ DXC_ARGS) {
+  /*
+  SERIAL_ECHOPGM("start of resume_print()\n");
+  SERIAL_ECHOPAIR("\ndual_x_carriage_mode:", dual_x_carriage_mode);
+  SERIAL_ECHOPAIR("\nextruder_duplication_enabled:", extruder_duplication_enabled);
+  SERIAL_ECHOPAIR("\nactive_extruder:", active_extruder);
+  SERIAL_ECHOPGM("\n\n");
+  */
+
   if (!did_pause_print) return;
 
   // Re-enable the heaters if they timed out
@@ -508,14 +555,11 @@ void resume_print(const float &slow_load_length/*=0*/, const float &fast_load_le
     thermalManager.reset_heater_idle_timer(e);
   }
 
-  if (nozzle_timed_out || thermalManager.hotEnoughToExtrude(active_extruder)) {
-    // Load the new filament
-    load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, true, nozzle_timed_out);
-  }
+  if (nozzle_timed_out || thermalManager.hotEnoughToExtrude(active_extruder)) // Load the new filament
+    load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, true, nozzle_timed_out, ADVANCED_PAUSE_MODE_PAUSE_PRINT DXC_PASS);
 
   #if ENABLED(ULTIPANEL)
-    // "Wait for print to resume"
-    lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_RESUME);
+    lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_RESUME); // "Wait for print to resume"
   #endif
 
   // Intelligent resuming
diff --git a/Marlin/src/feature/pause.h b/Marlin/src/feature/pause.h
index c920fd599f..5eb39a5e1a 100644
--- a/Marlin/src/feature/pause.h
+++ b/Marlin/src/feature/pause.h
@@ -67,14 +67,24 @@ extern float filament_change_unload_length[EXTRUDERS],
 
 extern uint8_t did_pause_print;
 
-bool pause_print(const float &retract, const point_t &park_point, const float &unload_length=0, const bool show_lcd=false);
+#if ENABLED(DUAL_X_CARRIAGE)
+  #define DXC_PARAMS , const int8_t DXC_ext=-1
+  #define DXC_ARGS   , const int8_t DXC_ext
+  #define DXC_PASS   , DXC_ext
+#else
+  #define DXC_PARAMS
+  #define DXC_ARGS
+  #define DXC_PASS
+#endif
 
-void wait_for_filament_reload(const int8_t max_beep_count=0);
+bool pause_print(const float &retract, const point_t &park_point, const float &unload_length=0, const bool show_lcd=false DXC_PARAMS);
 
-void resume_print(const float &slow_load_length=0, const float &fast_load_length=0, const float &extrude_length=ADVANCED_PAUSE_PURGE_LENGTH, const int8_t max_beep_count=0);
+void wait_for_filament_reload(const int8_t max_beep_count=0 DXC_PARAMS);
+
+void resume_print(const float &slow_load_length=0, const float &fast_load_length=0, const float &extrude_length=ADVANCED_PAUSE_PURGE_LENGTH, const int8_t max_beep_count=0 DXC_PARAMS);
 
 bool load_filament(const float &slow_load_length=0, const float &fast_load_length=0, const float &extrude_length=0, const int8_t max_beep_count=0, const bool show_lcd=false,
-                          const bool pause_for_user=false, const AdvancedPauseMode mode=ADVANCED_PAUSE_MODE_PAUSE_PRINT);
+                          const bool pause_for_user=false, const AdvancedPauseMode mode=ADVANCED_PAUSE_MODE_PAUSE_PRINT DXC_PARAMS);
 
 bool unload_filament(const float &unload_length, const bool show_lcd=false, const AdvancedPauseMode mode=ADVANCED_PAUSE_MODE_PAUSE_PRINT);
 
diff --git a/Marlin/src/feature/runout.h b/Marlin/src/feature/runout.h
index 85e9384fa6..aaabaad6e9 100644
--- a/Marlin/src/feature/runout.h
+++ b/Marlin/src/feature/runout.h
@@ -62,22 +62,31 @@ class FilamentRunoutSensor {
       #else
         // Read the sensor for the active extruder
         bool is_out;
-        switch (active_extruder) {
-          case 0: is_out = READ(FIL_RUNOUT_PIN) == FIL_RUNOUT_INVERTING; break;
-          case 1: is_out = READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT_INVERTING; break;
-          #if NUM_RUNOUT_SENSORS > 2
-            case 2: is_out = READ(FIL_RUNOUT3_PIN) == FIL_RUNOUT_INVERTING; break;
-            #if NUM_RUNOUT_SENSORS > 3
-              case 3: is_out = READ(FIL_RUNOUT4_PIN) == FIL_RUNOUT_INVERTING; break;
-              #if NUM_RUNOUT_SENSORS > 4
-                case 4: is_out = READ(FIL_RUNOUT5_PIN) == FIL_RUNOUT_INVERTING; break;
-                #if NUM_RUNOUT_SENSORS > 5
-                  case 5: is_out = READ(FIL_RUNOUT6_PIN) == FIL_RUNOUT_INVERTING; break;
-                #endif
-              #endif
-            #endif
-          #endif
+        #if ENABLED(DUAL_X_CARRIAGE)
+          const bool out1 = READ(FIL_RUNOUT_PIN ) == FIL_RUNOUT_INVERTING,
+                     out2 = READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT_INVERTING;
+          if (extruder_duplication_enabled)
+            is_out = out1 || out2;
+          else
+            is_out = active_extruder ? out2 : out1;
+        #else
+          switch (active_extruder) {
+            case 0: is_out = READ(FIL_RUNOUT_PIN) == FIL_RUNOUT_INVERTING; break;
+            case 1: is_out = READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT_INVERTING; break;
+            #if NUM_RUNOUT_SENSORS > 2
+              case 2: is_out = READ(FIL_RUNOUT3_PIN) == FIL_RUNOUT_INVERTING; break;
+              #if NUM_RUNOUT_SENSORS > 3
+                case 3: is_out = READ(FIL_RUNOUT4_PIN) == FIL_RUNOUT_INVERTING; break;
+                #if NUM_RUNOUT_SENSORS > 4
+                  case 4: is_out = READ(FIL_RUNOUT5_PIN) == FIL_RUNOUT_INVERTING; break;
+                  #if NUM_RUNOUT_SENSORS > 5
+                    case 5: is_out = READ(FIL_RUNOUT6_PIN) == FIL_RUNOUT_INVERTING; break;
+                  #endif // > 5
+                #endif // > 4
+              #endif // > 3
+            #endif // > 2
         }
+        #endif
       #endif
       return (is_out ? ++runout_count : (runout_count = 0)) > FIL_RUNOUT_THRESHOLD;
     }
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
index 66054d00e5..6553fc5cfc 100644
--- a/Marlin/src/gcode/calibrate/G28.cpp
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -369,7 +369,7 @@ void GcodeSuite::G28(const bool always_home_all) {
    */
   #if ENABLED(DUAL_X_CARRIAGE)
 
-    if (dual_x_carriage_mode == DXC_DUPLICATION_MODE) {
+    if (dxc_is_duplicating()) {
 
       // Always home the 2nd (right) extruder first
       active_extruder = 1;
@@ -387,7 +387,10 @@ void GcodeSuite::G28(const bool always_home_all) {
       delayed_move_time = 0;
       active_extruder_parked = true;
       extruder_duplication_enabled = IDEX_saved_duplication_state;
+      extruder_duplication_enabled = false;
+
       dual_x_carriage_mode         = IDEX_saved_mode;
+      stepper.set_directions();
     }
 
   #endif // DUAL_X_CARRIAGE
diff --git a/Marlin/src/gcode/control/M605.cpp b/Marlin/src/gcode/control/M605.cpp
index 80b39ef301..780a7f57b5 100644
--- a/Marlin/src/gcode/control/M605.cpp
+++ b/Marlin/src/gcode/control/M605.cpp
@@ -44,9 +44,9 @@
    *                         units 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.
-   *    M605 S3 : Enable Symmetric Duplication mode.  The second extruder will duplicate the first extruder's
+   *    M605 S3 : Enable Scaled Duplication mode.  The second extruder will duplicate the first extruder's
    *              movement similar to the M605 S2 mode.   However, the second extruder will be producing
-   *              a mirror image of the first extruder.  The initial x-offset and temperature differential are
+   *              a scaled image of the first extruder.  The initial x-offset and temperature differential are
    *              set with M605 S2 [Xnnn] [Rmmm] and then followed with a M605 S3 to start the mirrored movement.
    *    M605 W  : IDEX What? command.
    *
@@ -56,7 +56,27 @@
     planner.synchronize();
 
     if (parser.seen('S')) {
+      const DualXMode previous_mode = dual_x_carriage_mode;
+
       dual_x_carriage_mode = (DualXMode)parser.value_byte();
+      scaled_duplication_mode = false;
+
+      if (dual_x_carriage_mode == DXC_SCALED_DUPLICATION_MODE) {
+        if (previous_mode != DXC_DUPLICATION_MODE) {
+          SERIAL_ECHOPGM("Printer must be in DXC_DUPLICATION_MODE prior to \n");
+          SERIAL_ECHOPGM("specifying DXC_SCALED_DUPLICATION_MODE.\n");
+          dual_x_carriage_mode = DEFAULT_DUAL_X_CARRIAGE_MODE;
+          return;
+        }
+        scaled_duplication_mode = true;
+        stepper.set_directions();
+        float x_jog = current_position[X_AXIS] - .1;
+        for (uint8_t i = 2; --i;) {
+          planner.buffer_line(x_jog, current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS], feedrate_mm_s, 0);
+          x_jog += .1;
+        }
+        return;
+      }
 
       switch (dual_x_carriage_mode) {
         case DXC_FULL_CONTROL_MODE:
@@ -73,6 +93,7 @@
       }
       active_extruder_parked = false;
       extruder_duplication_enabled = false;
+      stepper.set_directions();
       delayed_move_time = 0;
     }
     else if (!parser.seen('W'))  // if no S or W parameter, the DXC mode gets reset to the user's default
@@ -82,21 +103,45 @@
       SERIAL_ECHO_START();
       SERIAL_ECHOPGM("IDEX mode: ");
       switch (dual_x_carriage_mode) {
-        case DXC_FULL_CONTROL_MODE: SERIAL_ECHOPGM("DXC_FULL_CONTROL_MODE"); break;
-        case DXC_AUTO_PARK_MODE:    SERIAL_ECHOPGM("DXC_AUTO_PARK_MODE");    break;
-        case DXC_DUPLICATION_MODE:  SERIAL_ECHOPGM("DXC_DUPLICATION_MODE");  break;
+        case DXC_FULL_CONTROL_MODE:       SERIAL_ECHOPGM("DXC_FULL_CONTROL_MODE");       break;
+        case DXC_AUTO_PARK_MODE:          SERIAL_ECHOPGM("DXC_AUTO_PARK_MODE");          break;
+        case DXC_DUPLICATION_MODE:        SERIAL_ECHOPGM("DXC_DUPLICATION_MODE");        break;
+        case DXC_SCALED_DUPLICATION_MODE: SERIAL_ECHOPGM("DXC_SCALED_DUPLICATION_MODE"); break;
       }
       SERIAL_ECHOPAIR("\nActive Ext: ", int(active_extruder));
       if (!active_extruder_parked) SERIAL_ECHOPGM(" NOT ");
-      SERIAL_ECHOLNPGM(" parked.");
-      SERIAL_ECHOPAIR("active_extruder_x_pos: ", current_position[X_AXIS]);
-      SERIAL_ECHOPAIR("   inactive_extruder_x_pos: ", inactive_extruder_x_pos);
-      SERIAL_ECHOPAIR("\nT0 Home X: ", x_home_pos(0));
-      SERIAL_ECHOPAIR("\nT1 Home X: ", x_home_pos(1));
+      SERIAL_ECHOPGM(" parked.");
+      SERIAL_ECHOPAIR("\nactive_extruder_x_pos: ", current_position[X_AXIS]);
+      SERIAL_ECHOPAIR("\ninactive_extruder_x_pos: ", inactive_extruder_x_pos);
       SERIAL_ECHOPAIR("\nextruder_duplication_enabled: ", int(extruder_duplication_enabled));
       SERIAL_ECHOPAIR("\nduplicate_extruder_x_offset: ", duplicate_extruder_x_offset);
       SERIAL_ECHOPAIR("\nduplicate_extruder_temp_offset: ", duplicate_extruder_temp_offset);
       SERIAL_ECHOPAIR("\ndelayed_move_time: ", delayed_move_time);
+      SERIAL_ECHOPAIR("\nX1 Home X: ", x_home_pos(0));
+      SERIAL_ECHOPAIR("\nX1_MIN_POS=", int(X1_MIN_POS));
+      SERIAL_ECHOPAIR("\nX1_MAX_POS=", int(X1_MAX_POS));
+      SERIAL_ECHOPAIR("\nX2 Home X: ", x_home_pos(1));
+      SERIAL_ECHOPAIR("\nX2_MIN_POS=", int(X2_MIN_POS));
+      SERIAL_ECHOPAIR("\nX2_MAX_POS=", int(X2_MAX_POS));
+      SERIAL_ECHOPAIR("\nX2_HOME_DIR=", int(X2_HOME_DIR));
+      SERIAL_ECHOPAIR("\nX2_HOME_POS=", int(X2_HOME_POS));
+      SERIAL_ECHOPAIR("\nDEFAULT_DUAL_X_CARRIAGE_MODE=", STRINGIFY(DEFAULT_DUAL_X_CARRIAGE_MODE));
+      SERIAL_ECHOPAIR("\nTOOLCHANGE_PARK_ZLIFT=", float(TOOLCHANGE_PARK_ZLIFT));
+      SERIAL_ECHOPAIR("\nTOOLCHANGE_UNPARK_ZLIFT=", float(TOOLCHANGE_UNPARK_ZLIFT));
+      SERIAL_ECHOPAIR("\nDEFAULT_DUPLICATION_X_OFFSET=", int(DEFAULT_DUPLICATION_X_OFFSET));
+
+      SERIAL_EOL();
+      for (uint8_t i = 0; i < 2; i++) {
+        SERIAL_ECHOPAIR(" nozzle:", int(i));
+        LOOP_XYZ(j) {
+          SERIAL_ECHOPGM("    hotend_offset[");
+          SERIAL_CHAR(axis_codes[j]);
+          SERIAL_ECHOPAIR("_AXIS][", int(i));
+          SERIAL_ECHOPAIR("]=", hotend_offset[j][i]);
+        }
+        SERIAL_EOL();
+      }
+      SERIAL_EOL();
     }
   }
 
diff --git a/Marlin/src/gcode/feature/leds/M7219.cpp b/Marlin/src/gcode/feature/leds/M7219.cpp
index 3621c4ac02..2a141185e0 100644
--- a/Marlin/src/gcode/feature/leds/M7219.cpp
+++ b/Marlin/src/gcode/feature/leds/M7219.cpp
@@ -79,7 +79,7 @@ void GcodeSuite::M7219() {
       SERIAL_ECHOPGM("led_line[");
       if (r < 10) SERIAL_CHAR(' ');
       SERIAL_ECHO(int(r));
-      SERIAL_ECHO("]=");
+      SERIAL_ECHOPGM("]=");
       for (uint8_t b = 8; b--;) SERIAL_CHAR('0' + TEST(max7219.led_line[r], b));
       SERIAL_EOL();
     }
diff --git a/Marlin/src/gcode/feature/pause/M600.cpp b/Marlin/src/gcode/feature/pause/M600.cpp
index 42ed74dbe1..5bb54de65c 100644
--- a/Marlin/src/gcode/feature/pause/M600.cpp
+++ b/Marlin/src/gcode/feature/pause/M600.cpp
@@ -56,6 +56,19 @@ void GcodeSuite::M600() {
 
   if (get_target_extruder_from_command()) return;
 
+  #if ENABLED(DUAL_X_CARRIAGE)
+    int8_t DXC_ext = target_extruder;
+    if (!parser.seen('T')) {  // If no tool index is specified, M600 was (probably) sent in response to filament runout.
+                              // In this case, for duplicating modes set DXC_ext to the extruder that ran out.
+      #if ENABLED(FILAMENT_RUNOUT_SENSOR)
+        if (dxc_is_duplicating())
+          DXC_ext = (READ(FIL_RUNOUT2_PIN) == FIL_RUNOUT_INVERTING) ? 1 : 0;
+      #else
+        DXC_ext = active_extruder;
+      #endif
+    }
+  #endif
+
   // Show initial "wait for start" message
   #if ENABLED(ULTIPANEL)
     lcd_advanced_pause_show_message(ADVANCED_PAUSE_MESSAGE_INIT, ADVANCED_PAUSE_MODE_PAUSE_PRINT, target_extruder);
@@ -63,14 +76,18 @@ void GcodeSuite::M600() {
 
   #if ENABLED(HOME_BEFORE_FILAMENT_CHANGE)
     // Don't allow filament change without homing first
-    if (axis_unhomed_error()) home_all_axes();
+    if (axis_unhomed_error()) gcode.home_all_axes();
   #endif
 
   #if EXTRUDERS > 1
     // Change toolhead if specified
-    uint8_t active_extruder_before_filament_change = active_extruder;
-    if (active_extruder != target_extruder)
-      tool_change(target_extruder, 0, true);
+    const uint8_t active_extruder_before_filament_change = active_extruder;
+    if (
+      active_extruder != target_extruder
+      #if ENABLED(DUAL_X_CARRIAGE)
+        && dual_x_carriage_mode != DXC_DUPLICATION_MODE && dual_x_carriage_mode != DXC_SCALED_DUPLICATION_MODE
+      #endif
+    ) tool_change(target_extruder, 0, true);
   #endif
 
   // Initial retract before move to filament change position
@@ -113,9 +130,9 @@ void GcodeSuite::M600() {
 
   const bool job_running = print_job_timer.isRunning();
 
-  if (pause_print(retract, park_point, unload_length, true)) {
-    wait_for_filament_reload(beep_count);
-    resume_print(slow_load_length, fast_load_length, ADVANCED_PAUSE_PURGE_LENGTH, beep_count);
+  if (pause_print(retract, park_point, unload_length, true DXC_PASS)) {
+    wait_for_filament_reload(beep_count DXC_PASS);
+    resume_print(slow_load_length, fast_load_length, ADVANCED_PAUSE_PURGE_LENGTH, beep_count DXC_PASS);
   }
 
   #if EXTRUDERS > 1
diff --git a/Marlin/src/gcode/feature/pause/M701_M702.cpp b/Marlin/src/gcode/feature/pause/M701_M702.cpp
index d9b4175cea..1203b6bd09 100644
--- a/Marlin/src/gcode/feature/pause/M701_M702.cpp
+++ b/Marlin/src/gcode/feature/pause/M701_M702.cpp
@@ -81,7 +81,11 @@ void GcodeSuite::M701() {
   const float fast_load_length = ABS(parser.seen('L') ? parser.value_axis_units(E_AXIS)
                                                        : filament_change_load_length[active_extruder]);
   load_filament(slow_load_length, fast_load_length, ADVANCED_PAUSE_PURGE_LENGTH, FILAMENT_CHANGE_ALERT_BEEPS,
-                true, thermalManager.wait_for_heating(target_extruder), ADVANCED_PAUSE_MODE_LOAD_FILAMENT);
+                true, thermalManager.wait_for_heating(target_extruder), ADVANCED_PAUSE_MODE_LOAD_FILAMENT
+                #if ENABLED(DUAL_X_CARRIAGE)
+                  , target_extruder
+                #endif
+              );
 
   // Restore Z axis
   if (park_point.z > 0)
diff --git a/Marlin/src/gcode/queue.cpp b/Marlin/src/gcode/queue.cpp
index 7951e5904f..7187f1fee0 100644
--- a/Marlin/src/gcode/queue.cpp
+++ b/Marlin/src/gcode/queue.cpp
@@ -139,12 +139,12 @@ inline bool _enqueuecommand(const char* cmd, bool say_ok=false
  */
 bool enqueue_and_echo_command(const char* cmd) {
 
-  //SERIAL_ECHO("enqueue_and_echo_command(\"");
+  //SERIAL_ECHOPGM("enqueue_and_echo_command(\"");
   //SERIAL_ECHO(cmd);
-  //SERIAL_ECHO("\") \n");
+  //SERIAL_ECHOPGM("\") \n");
 
   if (*cmd == 0 || *cmd == '\n' || *cmd == '\r') {
-    //SERIAL_ECHO("Null command found...   Did not queue!\n");
+    //SERIAL_ECHOPGM("Null command found...   Did not queue!\n");
     return true;
   }
 
diff --git a/Marlin/src/gcode/temperature/M104_M109.cpp b/Marlin/src/gcode/temperature/M104_M109.cpp
index 61099aa208..07859ca21e 100644
--- a/Marlin/src/gcode/temperature/M104_M109.cpp
+++ b/Marlin/src/gcode/temperature/M104_M109.cpp
@@ -53,7 +53,7 @@ void GcodeSuite::M104() {
     thermalManager.setTargetHotend(temp, e);
 
     #if ENABLED(DUAL_X_CARRIAGE)
-      if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && e == 0)
+      if (dxc_is_duplicating() && e == 0)
         thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1);
     #endif
 
@@ -103,7 +103,7 @@ void GcodeSuite::M109() {
     thermalManager.setTargetHotend(temp, target_extruder);
 
     #if ENABLED(DUAL_X_CARRIAGE)
-      if (dual_x_carriage_mode == DXC_DUPLICATION_MODE && target_extruder == 0)
+      if (dxc_is_duplicating() && target_extruder == 0)
         thermalManager.setTargetHotend(temp ? temp + duplicate_extruder_temp_offset : 0, 1);
     #endif
 
diff --git a/Marlin/src/lcd/language/language_en.h b/Marlin/src/lcd/language/language_en.h
index 78280b8977..d02927bf3f 100644
--- a/Marlin/src/lcd/language/language_en.h
+++ b/Marlin/src/lcd/language/language_en.h
@@ -214,9 +214,24 @@
 #ifndef MSG_IDEX_MODE_DUPLICATE
   #define MSG_IDEX_MODE_DUPLICATE             _UxGT("Duplication")
 #endif
+#ifndef MSG_IDEX_MODE_SCALED_COPY
+  #define MSG_IDEX_MODE_SCALED_COPY           _UxGT("Scaled copy")
+#endif
 #ifndef MSG_IDEX_MODE_FULL_CTRL
   #define MSG_IDEX_MODE_FULL_CTRL             _UxGT("Full control")
 #endif
+#ifndef MSG_IDEX_X_OFFSET
+  #define MSG_IDEX_X_OFFSET                   _UxGT("2nd nozzle X")
+#endif
+#ifndef MSG_IDEX_Y_OFFSET
+  #define MSG_IDEX_Y_OFFSET                   _UxGT("2nd nozzle Y")
+#endif
+#ifndef MSG_IDEX_Z_OFFSET
+  #define MSG_IDEX_Z_OFFSET                   _UxGT("2nd nozzle Z")
+#endif
+#ifndef MSG_IDEX_SAVE_OFFSETS
+  #define MSG_IDEX_SAVE_OFFSETS               _UxGT("Save Offsets")
+#endif
 #ifndef MSG_UBL_MANUAL_MESH
   #define MSG_UBL_MANUAL_MESH                 _UxGT("Manually Build Mesh")
 #endif
diff --git a/Marlin/src/lcd/ultralcd.cpp b/Marlin/src/lcd/ultralcd.cpp
index 070d127850..1a0d5f9839 100644
--- a/Marlin/src/lcd/ultralcd.cpp
+++ b/Marlin/src/lcd/ultralcd.cpp
@@ -38,6 +38,8 @@
 #include "../gcode/gcode.h"
 #include "../gcode/queue.h"
 #include "../module/configuration_store.h"
+#include "../module/tool_change.h"
+
 
 #include "../Marlin.h"
 
@@ -521,7 +523,7 @@ uint16_t max_display_update_time = 0;
           if (currentScreen == lcd_status_screen)
             doubleclick_expire_ms = millis() + DOUBLECLICK_MAX_INTERVAL;
         }
-        else if (screen == lcd_status_screen && currentScreen == lcd_main_menu && PENDING(millis(), doubleclick_expire_ms) && (planner.movesplanned() || IS_SD_PRINTING))
+        else if (screen == lcd_status_screen && currentScreen == lcd_main_menu && PENDING(millis(), doubleclick_expire_ms)/* && (planner.movesplanned() || IS_SD_PRINTING)*/)
           screen =
             #if ENABLED(BABYSTEP_ZPROBE_OFFSET)
               lcd_babystep_zoffset
@@ -1038,15 +1040,31 @@ void lcd_quick_feedback(const bool clear_buttons) {
    * IDEX submenu
    */
   #if ENABLED(DUAL_X_CARRIAGE)
+    static void _recalc_IDEX_settings() {
+      if (active_extruder) {                      // For the 2nd extruder re-home so the next tool-change gets the new offsets.
+        enqueue_and_echo_commands_P(PSTR("G28")); // In future, we can babystep the 2nd extruder (if active), making homing unnecessary.
+        active_extruder = 0;
+      }
+    }
+
     static void IDEX_menu() {
       START_MENU();
       MENU_BACK(MSG_MAIN);
-      MENU_ITEM(gcode, MSG_IDEX_MODE_AUTOPARK, PSTR("M605 S1\nG28 X\nG1 X100"));
-      if (!TEST(axis_known_position, Y_AXIS) || !TEST(axis_known_position, Z_AXIS))
-        MENU_ITEM(gcode, MSG_IDEX_MODE_DUPLICATE, PSTR("T0\nG28\nM605 S2 X200\nG28 X\nG1 X100"));  // If Y or Z is not homed, a full G28 is done first.
-      else
-        MENU_ITEM(gcode, MSG_IDEX_MODE_DUPLICATE, PSTR("T0\nM605 S2 X200\nG28 X\nG1 X100"));       // If Y and Z is homed, a full G28 is not needed first.
+      MENU_ITEM(gcode, MSG_IDEX_MODE_AUTOPARK,  PSTR("M605 S1\nG28 X\nG1 X100"));
+      const bool need_g28 = !(TEST(axis_known_position, Y_AXIS) && TEST(axis_known_position, Z_AXIS));
+      MENU_ITEM(gcode, MSG_IDEX_MODE_DUPLICATE, need_g28
+        ? PSTR("M605 S1\nT0\nG28\nM605 S2 X200\nG28 X\nG1 X100")                // If Y or Z is not homed, do a full G28 first
+        : PSTR("M605 S1\nT0\nM605 S2 X200\nG28 X\nG1 X100")
+      );
+      MENU_ITEM(gcode, MSG_IDEX_MODE_SCALED_COPY, need_g28
+        ? PSTR("M605 S1\nT0\nG28\nM605 S2 X200\nG28 X\nG1 X100\nM605 S3 X200")  // If Y or Z is not homed, do a full G28 first
+        : PSTR("M605 S1\nT0\nM605 S2 X200\nG28 X\nG1 X100\nM605 S3 X200")
+      );
       MENU_ITEM(gcode, MSG_IDEX_MODE_FULL_CTRL, PSTR("M605 S0\nG28 X"));
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float52, MSG_IDEX_X_OFFSET , &hotend_offset[X_AXIS][1], MIN(X2_HOME_POS, X2_MAX_POS) - 25.0, MAX(X2_HOME_POS, X2_MAX_POS) + 25.0, _recalc_IDEX_settings);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float52, MSG_IDEX_Y_OFFSET , &hotend_offset[Y_AXIS][1], -10.0, 10.0, _recalc_IDEX_settings);
+      MENU_MULTIPLIER_ITEM_EDIT_CALLBACK(float52, MSG_IDEX_Z_OFFSET , &hotend_offset[Z_AXIS][1], -10.0, 10.0, _recalc_IDEX_settings);
+      MENU_ITEM(gcode, MSG_IDEX_SAVE_OFFSETS, PSTR("M500"));
       END_MENU();
     }
   #endif // DUAL_X_CARRIAGE
diff --git a/Marlin/src/module/configuration_store.cpp b/Marlin/src/module/configuration_store.cpp
index 031bb2830a..101175b229 100644
--- a/Marlin/src/module/configuration_store.cpp
+++ b/Marlin/src/module/configuration_store.cpp
@@ -1886,6 +1886,9 @@ void MarlinSettings::reset(PORTARG_SOLO) {
       "Offsets for the first hotend must be 0.0."
     );
     LOOP_XYZ(i) HOTEND_LOOP() hotend_offset[i][e] = tmp4[i][e];
+    #if ENABLED(DUAL_X_CARRIAGE)
+      hotend_offset[X_AXIS][1] = MAX(X2_HOME_POS, X2_MAX_POS);
+    #endif
   #endif
 
   //
diff --git a/Marlin/src/module/motion.cpp b/Marlin/src/module/motion.cpp
index 06db3caf04..5094a0952a 100644
--- a/Marlin/src/module/motion.cpp
+++ b/Marlin/src/module/motion.cpp
@@ -760,7 +760,7 @@ float soft_endstop_min[XYZ] = { X_MIN_BED, Y_MIN_BED, Z_MIN_POS },
 
 #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
   bool extruder_duplication_enabled = false,                              // Used in Dual X mode 2 & 3
-       symmetric_duplication_mode   = false;                              // Used in Dual X mode 2 & 3
+       scaled_duplication_mode      = false;                              // Used in Dual X mode 2 & 3
 #endif
 
 #if ENABLED(DUAL_X_CARRIAGE)
@@ -818,8 +818,6 @@ float soft_endstop_min[XYZ] = { X_MIN_BED, Y_MIN_BED, Z_MIN_POS },
             #define RAISED_Y raised_parked_position[Y_AXIS]
             #define RAISED_Z raised_parked_position[Z_AXIS]
 
-            //SERIAL_ECHOLNPGM("dual_x_carriage_unpark()\n");
-
             if (  planner.buffer_line(RAISED_X, RAISED_Y, RAISED_Z, CUR_E, planner.max_feedrate_mm_s[Z_AXIS], active_extruder))
               if (planner.buffer_line(   CUR_X,    CUR_Y, RAISED_Z, CUR_E, PLANNER_XY_FEEDRATE(),             active_extruder))
                   planner.buffer_line(   CUR_X,    CUR_Y,    CUR_Z, CUR_E, planner.max_feedrate_mm_s[Z_AXIS], active_extruder);
@@ -829,6 +827,7 @@ float soft_endstop_min[XYZ] = { X_MIN_BED, Y_MIN_BED, Z_MIN_POS },
             if (DEBUGGING(LEVELING)) SERIAL_ECHOLNPGM("Clear active_extruder_parked");
           #endif
           break;
+        case DXC_SCALED_DUPLICATION_MODE:
         case DXC_DUPLICATION_MODE:
           if (active_extruder == 0) {
             #if ENABLED(DEBUG_LEVELING_FEATURE)
@@ -839,10 +838,12 @@ float soft_endstop_min[XYZ] = { X_MIN_BED, Y_MIN_BED, Z_MIN_POS },
             #endif
             // move duplicate extruder into correct duplication position.
             planner.set_position_mm(inactive_extruder_x_pos, current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS]);
+
             if (!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)
+                dual_x_carriage_mode == DXC_DUPLICATION_MODE ? duplicate_extruder_x_offset + current_position[X_AXIS] : inactive_extruder_x_pos,
+                current_position[Y_AXIS], current_position[Z_AXIS], current_position[E_AXIS],
+                planner.max_feedrate_mm_s[X_AXIS], 1
+              )
             ) break;
             planner.synchronize();
             sync_plan_position();
@@ -860,6 +861,7 @@ float soft_endstop_min[XYZ] = { X_MIN_BED, Y_MIN_BED, Z_MIN_POS },
           break;
       }
     }
+    stepper.set_directions();
     return false;
   }
 
@@ -906,9 +908,9 @@ void prepare_move_to_destination() {
 
   if (
     #if UBL_SEGMENTED
-//    ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s))   // This does not seem to work correctly on UBL.
-      #if ENABLED(DELTA)                                                      // A Delta case and a Cartesian case can work
-        ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s)) // around the problem until it is fixed.
+      //ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s))   // This doesn't seem to work correctly on UBL.
+      #if IS_KINEMATIC                                                          // Use Kinematic / Cartesian cases as a workaround for now.
+        ubl.prepare_segmented_line_to(destination, MMS_SCALED(feedrate_mm_s))
       #else
         prepare_move_to_destination_cartesian()
       #endif
@@ -1499,7 +1501,7 @@ void homeaxis(const AxisEnum axis) {
           soft_endstop_min[X_AXIS] = X2_MIN_POS;
           soft_endstop_max[X_AXIS] = dual_max_x;
         }
-        else if (dual_x_carriage_mode == DXC_DUPLICATION_MODE) {
+        else if (dxc_is_duplicating()) {
           // In Duplication Mode, T0 can move as far left as X_MIN_POS
           // but not so far to the right that T1 would move past the end
           soft_endstop_min[X_AXIS] = base_min_pos(X_AXIS);
diff --git a/Marlin/src/module/motion.h b/Marlin/src/module/motion.h
index 7d05a0ebd4..1b4f727c8e 100644
--- a/Marlin/src/module/motion.h
+++ b/Marlin/src/module/motion.h
@@ -305,7 +305,7 @@ void homeaxis(const AxisEnum axis);
  */
 #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
   extern bool extruder_duplication_enabled,       // Used in Dual X mode 2
-              symmetric_duplication_mode;         // Used in Dual X mode 2
+              scaled_duplication_mode;            // Used in Dual X mode 3
 #endif
 
 /**
@@ -314,9 +314,10 @@ void homeaxis(const AxisEnum axis);
 #if ENABLED(DUAL_X_CARRIAGE)
 
   enum DualXMode : char {
-    DXC_FULL_CONTROL_MODE,  // DUAL_X_CARRIAGE only
-    DXC_AUTO_PARK_MODE,     // DUAL_X_CARRIAGE only
-    DXC_DUPLICATION_MODE
+    DXC_FULL_CONTROL_MODE,
+    DXC_AUTO_PARK_MODE,
+    DXC_DUPLICATION_MODE,
+    DXC_SCALED_DUPLICATION_MODE
   };
 
   extern DualXMode dual_x_carriage_mode;
@@ -327,6 +328,8 @@ void homeaxis(const AxisEnum axis);
   extern millis_t delayed_move_time;              // used in mode 1
   extern int16_t duplicate_extruder_temp_offset;  // used in mode 2 & 3
 
+  FORCE_INLINE bool dxc_is_duplicating() { return dual_x_carriage_mode >= DXC_DUPLICATION_MODE; }
+
   float x_home_pos(const int extruder);
 
   FORCE_INLINE int x_home_dir(const uint8_t extruder) { return extruder ? X2_HOME_DIR : X_HOME_DIR; }
diff --git a/Marlin/src/module/stepper.cpp b/Marlin/src/module/stepper.cpp
index a83fbd85b0..76c068977f 100644
--- a/Marlin/src/module/stepper.cpp
+++ b/Marlin/src/module/stepper.cpp
@@ -251,7 +251,7 @@ int8_t Stepper::count_direction[NUM_AXIS] = { 0, 0, 0, 0 };
   #define X_APPLY_DIR(v,ALWAYS) \
     if (extruder_duplication_enabled || ALWAYS) { \
       X_DIR_WRITE(v); \
-      X2_DIR_WRITE(bool(v)); \
+      X2_DIR_WRITE(v); \
     } \
     else { \
       if (movement_extruder()) X2_DIR_WRITE(v); else X_DIR_WRITE(v); \
diff --git a/Marlin/src/module/stepper.h b/Marlin/src/module/stepper.h
index 2585181973..0ebe1d6e20 100644
--- a/Marlin/src/module/stepper.h
+++ b/Marlin/src/module/stepper.h
@@ -472,13 +472,10 @@ class Stepper {
       #endif
     }
 
-  private:
-
     // Set direction bits for all steppers
     static void set_directions();
 
-    // Allow reset_stepper_drivers to access private set_directions
-    friend void reset_stepper_drivers();
+  private:
 
     // Set the current position in steps
     static void _set_position(const int32_t &a, const int32_t &b, const int32_t &c, const int32_t &e);
diff --git a/Marlin/src/module/stepper_indirection.h b/Marlin/src/module/stepper_indirection.h
index 28a1e54069..1a615e395c 100644
--- a/Marlin/src/module/stepper_indirection.h
+++ b/Marlin/src/module/stepper_indirection.h
@@ -562,9 +562,18 @@ void reset_stepper_drivers();    // Called by settings.load / settings.reset
   #define    REV_E_DIR(E)   do{ switch (E) { case 0: E0_DIR_WRITE( INVERT_E0_DIR); break; case 1: E1_DIR_WRITE( INVERT_E1_DIR); break; case 2: E2_DIR_WRITE( INVERT_E2_DIR); } }while(0)
 #elif E_STEPPERS > 1
   #if ENABLED(DUAL_X_CARRIAGE) || ENABLED(DUAL_NOZZLE_DUPLICATION_MODE)
-    #define E_STEP_WRITE(E,V) do{ if (extruder_duplication_enabled) { E0_STEP_WRITE(V); E1_STEP_WRITE(V); } else if (E == 0) { E0_STEP_WRITE(V); } else { E1_STEP_WRITE(V); } }while(0)
-    #define   NORM_E_DIR(E)   do{ if (extruder_duplication_enabled) { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); } else if (E == 0) { E0_DIR_WRITE(!INVERT_E0_DIR); } else { E1_DIR_WRITE(!INVERT_E1_DIR); } }while(0)
-    #define    REV_E_DIR(E)   do{ if (extruder_duplication_enabled) { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); } else if (E == 0) { E0_DIR_WRITE( INVERT_E0_DIR); } else { E1_DIR_WRITE( INVERT_E1_DIR); } }while(0)
+
+    #define E_STEP_WRITE(E,V) do{ if (extruder_duplication_enabled)  { E0_STEP_WRITE(V); E1_STEP_WRITE(V); } \
+                                                  else if ((E) == 0) { E0_STEP_WRITE(V); } \
+                                                  else               { E1_STEP_WRITE(V); } }while(0)
+
+    #define   NORM_E_DIR(E)   do{ if (extruder_duplication_enabled)  { E0_DIR_WRITE(!INVERT_E0_DIR); E1_DIR_WRITE(!INVERT_E1_DIR); } \
+                                                  else if ((E) == 0) { E0_DIR_WRITE(!INVERT_E0_DIR); } \
+                                                  else               { E1_DIR_WRITE(!INVERT_E1_DIR); } }while(0)
+
+    #define    REV_E_DIR(E)   do{ if (extruder_duplication_enabled)  { E0_DIR_WRITE( INVERT_E0_DIR); E1_DIR_WRITE( INVERT_E1_DIR); } \
+                                                  else if ((E) == 0) { E0_DIR_WRITE( INVERT_E0_DIR); } \
+                                                  else               { E1_DIR_WRITE( INVERT_E1_DIR); } }while(0)
   #else
     #define E_STEP_WRITE(E,V) do{ if (E == 0) { E0_STEP_WRITE(V); } else { E1_STEP_WRITE(V); } }while(0)
     #define   NORM_E_DIR(E)   do{ if (E == 0) { E0_DIR_WRITE(!INVERT_E0_DIR); } else { E1_DIR_WRITE(!INVERT_E1_DIR); } }while(0)
diff --git a/Marlin/src/module/tool_change.cpp b/Marlin/src/module/tool_change.cpp
index a1cb42aa23..a1e6f3c17c 100644
--- a/Marlin/src/module/tool_change.cpp
+++ b/Marlin/src/module/tool_change.cpp
@@ -366,6 +366,8 @@ inline void invalid_extruder_error(const uint8_t e) {
         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;
+          case DXC_SCALED_DUPLICATION_MODE: SERIAL_ECHOLNPGM("DXC_SCALED_DUPLICATION_MODE"); break;
         }
       }
     #endif
@@ -455,9 +457,8 @@ 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*/) {
   planner.synchronize();
 
-  #if ENABLED(DUAL_X_CARRIAGE)
-    // Only T0 allowed in DXC_DUPLICATION_MODE
-    if (tmp_extruder != 0 && dual_x_carriage_mode == DXC_DUPLICATION_MODE)
+  #if ENABLED(DUAL_X_CARRIAGE)  // Only T0 allowed if the Printer is in DXC_DUPLICATION_MODE or DXC_SCALED_DUPLICATION_MODE
+    if (tmp_extruder != 0 && dxc_is_duplicating())
        return invalid_extruder_error(tmp_extruder);
   #endif
 
diff --git a/Marlin/src/pins/pins_FORMBOT_TREX2.h b/Marlin/src/pins/pins_FORMBOT_TREX2.h
index 9af13bdb38..f98bb37a13 100644
--- a/Marlin/src/pins/pins_FORMBOT_TREX2.h
+++ b/Marlin/src/pins/pins_FORMBOT_TREX2.h
@@ -28,8 +28,8 @@
   #error "Oops!  Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
 #endif
 
-#if E_STEPPERS > 3 || HOTENDS > 3
-  #error "Formbot supports up to 3 hotends / E-steppers. Comment this line to keep going."
+#if E_STEPPERS > 2 || HOTENDS > 2
+  #error "Formbot supports up to 2 hotends / E-steppers. Comment this line to keep going."
 #endif
 
 #define DEFAULT_MACHINE_NAME "Formbot"
@@ -143,17 +143,15 @@
 #define HEATER_BED_PIN     58
 
 #define FAN_PIN             9
-#define FAN1_PIN            4
+//#define FAN1_PIN            4
 
 
 #if DISABLED(ICSP_PORT_SWITCHES)
-  #define FIL_RUNOUT_PIN    22
-  #define FIL_RUNOUT2_PIN   21
-#else
-  #if ENABLED(FILAMENT_RUNOUT_SENSOR)
-    #define FIL_RUNOUT_PIN  52
-    #define FIL_RUNOUT2_PIN 50
-  #endif
+  //#define FIL_RUNOUT_PIN    22
+  //#define FIL_RUNOUT2_PIN   21
+#elif ENABLED(FILAMENT_RUNOUT_SENSOR)
+  #define FIL_RUNOUT_PIN   52
+  #define FIL_RUNOUT2_PIN  50
 #endif
 
 //
@@ -162,8 +160,8 @@
 #define CASE_LIGHT_PIN      8
 #define SDSS               53
 #ifndef ROXYs_TRex
-  #define LED_PIN          13
-#endif
+  #define LED_PIN          13   // The Formbot v 1 board has almost no unassigned pins on it.  The Board's LED
+#endif                          // is a good place to get a signal to control the Max7219 LED Matrix.
 
 // Use the RAMPS 1.4 Analog input 5 on the AUX2 connector
 #define FILWIDTH_PIN        5   // Analog Input
diff --git a/Marlin/src/pins/pins_FORMBOT_TREX3.h b/Marlin/src/pins/pins_FORMBOT_TREX3.h
index 322e0f2ea0..5591edcbfe 100644
--- a/Marlin/src/pins/pins_FORMBOT_TREX3.h
+++ b/Marlin/src/pins/pins_FORMBOT_TREX3.h
@@ -28,8 +28,8 @@
   #error "Oops!  Make sure you have 'Arduino Mega' selected from the 'Tools -> Boards' menu."
 #endif
 
-#if E_STEPPERS > 3 || HOTENDS > 3
-  #error "Formbot supports up to 3 hotends / E-steppers. Comment this line to keep going."
+#if E_STEPPERS > 2 || HOTENDS > 2
+  #error "Formbot supports up to 2 hotends / E-steppers. Comment this line to keep going."
 #endif
 
 #define DEFAULT_MACHINE_NAME "Formbot"
@@ -143,18 +143,10 @@
 #define HEATER_BED_PIN      8
 
 #define FAN_PIN             9
-#define FAN1_PIN            4
+//#define FAN1_PIN          4
 
-
-#if DISABLED(ICSP_PORT_SWITCHES)
-  #define FIL_RUNOUT_PIN    22
-  #define FIL_RUNOUT2_PIN   21
-#else
-  #if ENABLED(FILAMENT_RUNOUT_SENSOR)
-    #define FIL_RUNOUT_PIN  52
-    #define FIL_RUNOUT2_PIN 50
-  #endif
-#endif
+#define FIL_RUNOUT_PIN     23
+#define FIL_RUNOUT2_PIN    21
 
 //
 // Misc. Functions
-- 
GitLab