From 3cade6245e851af9a33d1f395758b898c4a06a30 Mon Sep 17 00:00:00 2001
From: Jason Smith <jason.inet@gmail.com>
Date: Fri, 3 Jan 2020 17:46:26 -0600
Subject: [PATCH] Fix MIN_PROBE_EDGE bug in default ABL G29 (#16367)

---
 Marlin/src/core/utility.cpp                 | 65 ++++++++++++---------
 Marlin/src/feature/bedlevel/ubl/ubl.cpp     |  2 +-
 Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp | 13 +++--
 Marlin/src/gcode/bedlevel/G42.cpp           |  9 +--
 Marlin/src/gcode/bedlevel/abl/G29.cpp       | 15 +++--
 Marlin/src/gcode/calibrate/G28.cpp          |  2 +-
 Marlin/src/gcode/calibrate/M48.cpp          |  9 +--
 Marlin/src/gcode/probe/G30.cpp              |  5 +-
 Marlin/src/gcode/probe/M851.cpp             | 43 +++++++++-----
 Marlin/src/inc/Conditionals_LCD.h           |  1 +
 Marlin/src/inc/SanityCheck.h                | 14 +++++
 Marlin/src/module/configuration_store.cpp   | 20 +++++--
 Marlin/src/module/delta.cpp                 |  8 +--
 Marlin/src/module/motion.h                  | 33 ++++++++---
 Marlin/src/module/probe.cpp                 | 12 +++-
 Marlin/src/module/probe.h                   | 31 ++++------
 buildroot/share/tests/LPC1768-tests         |  3 +-
 17 files changed, 175 insertions(+), 110 deletions(-)

diff --git a/Marlin/src/core/utility.cpp b/Marlin/src/core/utility.cpp
index 4d5f5b2c38..c2f7024320 100644
--- a/Marlin/src/core/utility.cpp
+++ b/Marlin/src/core/utility.cpp
@@ -81,40 +81,49 @@ void safe_delay(millis_t ms) {
     );
 
     #if HAS_BED_PROBE
-      SERIAL_ECHOPAIR_P(PSTR("Probe Offset X"), probe_offset.x, SP_Y_STR, probe_offset.y, SP_Z_STR, probe_offset.z);
-      if (probe_offset.x > 0)
-        SERIAL_ECHOPGM(" (Right");
-      else if (probe_offset.x < 0)
-        SERIAL_ECHOPGM(" (Left");
-      else if (probe_offset.y != 0)
-        SERIAL_ECHOPGM(" (Middle");
-      else
-        SERIAL_ECHOPGM(" (Aligned With");
 
-      if (probe_offset.y > 0) {
-        #if IS_SCARA
-          SERIAL_ECHOPGM("-Distal");
-        #else
-          SERIAL_ECHOPGM("-Back");
-        #endif
-      }
-      else if (probe_offset.y < 0) {
-        #if IS_SCARA
-          SERIAL_ECHOPGM("-Proximal");
-        #else
-          SERIAL_ECHOPGM("-Front");
-        #endif
-      }
-      else if (probe_offset.x != 0)
-        SERIAL_ECHOPGM("-Center");
+      #if !HAS_PROBE_XY_OFFSET
+        SERIAL_ECHOPAIR("Probe Offset X0 Y0 Z", probe_offset.z, " (");
+      #else
+        SERIAL_ECHOPAIR_P(PSTR("Probe Offset X"), probe_offset.x, SP_Y_STR, probe_offset.y, SP_Z_STR, probe_offset.z);
+        if (probe_offset.x > 0)
+          SERIAL_ECHOPGM(" (Right");
+        else if (probe_offset.x < 0)
+          SERIAL_ECHOPGM(" (Left");
+        else if (probe_offset.y != 0)
+          SERIAL_ECHOPGM(" (Middle");
+        else
+          SERIAL_ECHOPGM(" (Aligned With");
+
+        if (probe_offset.y > 0) {
+          #if IS_SCARA
+            SERIAL_ECHOPGM("-Distal");
+          #else
+            SERIAL_ECHOPGM("-Back");
+          #endif
+        }
+        else if (probe_offset.y < 0) {
+          #if IS_SCARA
+            SERIAL_ECHOPGM("-Proximal");
+          #else
+            SERIAL_ECHOPGM("-Front");
+          #endif
+        }
+        else if (probe_offset.x != 0)
+          SERIAL_ECHOPGM("-Center");
+
+        SERIAL_ECHOPGM(" & ");
+
+      #endif
 
       if (probe_offset.z < 0)
-        SERIAL_ECHOPGM(" & Below");
+        SERIAL_ECHOPGM("Below");
       else if (probe_offset.z > 0)
-        SERIAL_ECHOPGM(" & Above");
+        SERIAL_ECHOPGM("Above");
       else
-        SERIAL_ECHOPGM(" & Same Z as");
+        SERIAL_ECHOPGM("Same Z as");
       SERIAL_ECHOLNPGM(" Nozzle)");
+
     #endif
 
     #if HAS_ABL_OR_UBL
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl.cpp b/Marlin/src/feature/bedlevel/ubl/ubl.cpp
index 66eff703c3..adafc6a194 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl.cpp
@@ -176,7 +176,7 @@
     // Add XY probe offset from extruder because probe_at_point() subtracts them when
     // moving to the XY position to be measured. This ensures better agreement between
     // the current Z position after G28 and the mesh values.
-    const xy_int8_t curr = closest_indexes(xy_pos_t(current_position) + xy_pos_t(probe_offset));
+    const xy_int8_t curr = closest_indexes(xy_pos_t(current_position) + probe_offset_xy);
 
     if (!lcd) SERIAL_EOL();
     for (int8_t j = GRID_MAX_POINTS_Y - 1; j >= 0; j--) {
diff --git a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
index ba494aefc5..203d7c7150 100644
--- a/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
+++ b/Marlin/src/feature/bedlevel/ubl/ubl_G29.cpp
@@ -450,7 +450,7 @@
               SERIAL_ECHO(g29_pos.y);
               SERIAL_ECHOLNPGM(").\n");
             }
-            const xy_pos_t near = g29_pos + probe_offset;
+            const xy_pos_t near = g29_pos + probe_offset_xy;
             probe_entire_mesh(near, parser.seen('T'), parser.seen('E'), parser.seen('U'));
 
             report_current_position();
@@ -468,6 +468,7 @@
             do_blocking_move_to_z(Z_CLEARANCE_BETWEEN_PROBES);
 
             if (parser.seen('C') && !xy_seen) {
+
               /**
                * Use a good default location for the path.
                * The flipped > and < operators in these comparisons is intentional.
@@ -479,8 +480,8 @@
                 #if IS_KINEMATIC
                   X_HOME_POS, Y_HOME_POS
                 #else
-                  probe_offset.x > 0 ? X_BED_SIZE : 0,
-                  probe_offset.y < 0 ? Y_BED_SIZE : 0
+                  probe_offset_xy.x > 0 ? X_BED_SIZE : 0,
+                  probe_offset_xy.y < 0 ? Y_BED_SIZE : 0
                 #endif
               );
             }
@@ -805,8 +806,8 @@
       restore_ubl_active_state_and_leave();
 
       do_blocking_move_to_xy(
-        constrain(near.x - probe_offset.x, MESH_MIN_X, MESH_MAX_X),
-        constrain(near.y - probe_offset.y, MESH_MIN_Y, MESH_MAX_Y)
+        constrain(near.x - probe_offset_xy.x, MESH_MIN_X, MESH_MAX_X),
+        constrain(near.y - probe_offset_xy.y, MESH_MIN_Y, MESH_MAX_Y)
       );
     }
 
@@ -1293,7 +1294,7 @@
     closest.distance = -99999.9f;
 
     // Get the reference position, either nozzle or probe
-    const xy_pos_t ref = probe_relative ? pos + probe_offset : pos;
+    const xy_pos_t ref = probe_relative ? pos + probe_offset_xy : pos;
 
     float best_so_far = 99999.99f;
 
diff --git a/Marlin/src/gcode/bedlevel/G42.cpp b/Marlin/src/gcode/bedlevel/G42.cpp
index b285c798f6..28752cab8d 100644
--- a/Marlin/src/gcode/bedlevel/G42.cpp
+++ b/Marlin/src/gcode/bedlevel/G42.cpp
@@ -45,20 +45,21 @@ void GcodeSuite::G42() {
       return;
     }
 
+    // Move to current_position, as modified by I, J, P parameters
     destination = current_position;
 
     if (hasI) destination.x = _GET_MESH_X(ix);
     if (hasJ) destination.y = _GET_MESH_Y(iy);
 
-    #if HAS_BED_PROBE
+    #if HAS_PROBE_XY_OFFSET
       if (parser.boolval('P')) {
-        if (hasI) destination.x -= probe_offset.x;
-        if (hasJ) destination.y -= probe_offset.y;
+        if (hasI) destination.x -= probe_offset_xy.x;
+        if (hasJ) destination.y -= probe_offset_xy.y;
       }
     #endif
 
     const feedRate_t fval = parser.linearval('F'),
-                     fr_mm_s = fval > 0 ? MMM_TO_MMS(fval) : 0.0f;
+                     fr_mm_s = MMM_TO_MMS(fval > 0 ? fval : 0.0f);
 
     // SCARA kinematic has "safe" XY raw moves
     #if IS_SCARA
diff --git a/Marlin/src/gcode/bedlevel/abl/G29.cpp b/Marlin/src/gcode/bedlevel/abl/G29.cpp
index 4503a51cb7..cf9cdf58e6 100644
--- a/Marlin/src/gcode/bedlevel/abl/G29.cpp
+++ b/Marlin/src/gcode/bedlevel/abl/G29.cpp
@@ -228,7 +228,7 @@ G29_TYPE GcodeSuite::G29() {
       ABL_VAR xy_int8_t meshCount;
     #endif
 
-    ABL_VAR xy_float_t probe_position_lf, probe_position_rb;
+    ABL_VAR xy_pos_t probe_position_lf, probe_position_rb;
     ABL_VAR xy_float_t gridSpacing = { 0, 0 };
 
     #if ENABLED(AUTO_BED_LEVELING_LINEAR)
@@ -403,14 +403,13 @@ G29_TYPE GcodeSuite::G29() {
       }
       else {
         probe_position_lf.set(
-          parser.seenval('L') ? (int)RAW_X_POSITION(parser.value_linear_units()) : (_MAX(x_min, X_CENTER - (X_BED_SIZE) / 2)      + MIN_PROBE_EDGE_LEFT),
-          parser.seenval('F') ? (int)RAW_Y_POSITION(parser.value_linear_units()) : (_MAX(y_min, Y_CENTER - (Y_BED_SIZE) / 2)      + MIN_PROBE_EDGE_FRONT)
+          parser.seenval('L') ? RAW_X_POSITION(parser.value_linear_units()) : x_min,
+          parser.seenval('F') ? RAW_Y_POSITION(parser.value_linear_units()) : y_min
         );
         probe_position_rb.set(
-          parser.seenval('R') ? (int)RAW_X_POSITION(parser.value_linear_units()) : (_MIN(x_max, probe_position_lf.x + X_BED_SIZE) - MIN_PROBE_EDGE_RIGHT),
-          parser.seenval('B') ? (int)RAW_Y_POSITION(parser.value_linear_units()) : (_MIN(y_max, probe_position_lf.y + Y_BED_SIZE) - MIN_PROBE_EDGE_BACK)
+          parser.seenval('R') ? RAW_X_POSITION(parser.value_linear_units()) : x_max,
+          parser.seenval('B') ? RAW_Y_POSITION(parser.value_linear_units()) : y_max
         );
-        SERIAL_ECHOLN("Set Trail 1");
       }
 
       if (
@@ -911,8 +910,8 @@ G29_TYPE GcodeSuite::G29() {
         planner.force_unapply_leveling(converted); // use conversion machinery
 
         // Use the last measured distance to the bed, if possible
-        if ( NEAR(current_position.x, probePos.x - probe_offset.x)
-          && NEAR(current_position.y, probePos.y - probe_offset.y)
+        if ( NEAR(current_position.x, probePos.x - probe_offset_xy.x)
+          && NEAR(current_position.y, probePos.y - probe_offset_xy.y)
         ) {
           const float simple_z = current_position.z - measured_z;
           if (DEBUGGING(LEVELING)) DEBUG_ECHOLNPAIR("Probed Z", simple_z, "  Matrix Z", converted.z, "  Discrepancy ", simple_z - converted.z);
diff --git a/Marlin/src/gcode/calibrate/G28.cpp b/Marlin/src/gcode/calibrate/G28.cpp
index b6c0389b31..db6ae7a682 100644
--- a/Marlin/src/gcode/calibrate/G28.cpp
+++ b/Marlin/src/gcode/calibrate/G28.cpp
@@ -133,7 +133,7 @@
     destination.set(safe_homing_xy, current_position.z);
 
     #if HOMING_Z_WITH_PROBE
-      destination -= probe_offset;
+      destination -= probe_offset_xy;
     #endif
 
     if (position_is_reachable(destination)) {
diff --git a/Marlin/src/gcode/calibrate/M48.cpp b/Marlin/src/gcode/calibrate/M48.cpp
index 86b25d8240..f111de4b17 100644
--- a/Marlin/src/gcode/calibrate/M48.cpp
+++ b/Marlin/src/gcode/calibrate/M48.cpp
@@ -77,8 +77,8 @@ void GcodeSuite::M48() {
   xy_float_t next_pos = current_position;
 
   const xy_pos_t probe_pos = {
-    parser.linearval('X', next_pos.x + probe_offset.x),
-    parser.linearval('Y', next_pos.y + probe_offset.y)
+    parser.linearval('X', next_pos.x + probe_offset_xy.x),
+    parser.linearval('Y', next_pos.y + probe_offset_xy.y)
   };
 
   if (!position_is_reachable_by_probe(probe_pos)) {
@@ -166,8 +166,9 @@ void GcodeSuite::M48() {
           while (angle < 0.0) angle += 360.0;   // outside of this range.   It looks like they behave correctly with
                                                 // numbers outside of the range, but just to be safe we clamp them.
 
-          next_pos.set(probe_pos.x - probe_offset.x + cos(RADIANS(angle)) * radius,
-                       probe_pos.y - probe_offset.y + sin(RADIANS(angle)) * radius);
+          const xy_pos_t noz_pos = probe_pos - probe_offset_xy;
+          next_pos.set(noz_pos.x + cos(RADIANS(angle)) * radius,
+                       noz_pos.y + sin(RADIANS(angle)) * radius);
 
           #if DISABLED(DELTA)
             LIMIT(next_pos.x, X_MIN_POS, X_MAX_POS);
diff --git a/Marlin/src/gcode/probe/G30.cpp b/Marlin/src/gcode/probe/G30.cpp
index e91e3e9d8b..a236ce3edf 100644
--- a/Marlin/src/gcode/probe/G30.cpp
+++ b/Marlin/src/gcode/probe/G30.cpp
@@ -39,8 +39,9 @@
  *   E   Engage the probe for each probe (default 1)
  */
 void GcodeSuite::G30() {
-  const xy_pos_t pos = { parser.linearval('X', current_position.x + probe_offset.x),
-                         parser.linearval('Y', current_position.y + probe_offset.y) };
+
+  const xy_pos_t pos = { parser.linearval('X', current_position.x + probe_offset_xy.x),
+                         parser.linearval('Y', current_position.y + probe_offset_xy.y) };
 
   if (!position_is_reachable_by_probe(pos)) return;
 
diff --git a/Marlin/src/gcode/probe/M851.cpp b/Marlin/src/gcode/probe/M851.cpp
index 431fe6fa09..b0a63041fe 100644
--- a/Marlin/src/gcode/probe/M851.cpp
+++ b/Marlin/src/gcode/probe/M851.cpp
@@ -37,32 +37,49 @@ void GcodeSuite::M851() {
 
   // Show usage with no parameters
   if (!parser.seen("XYZ")) {
-    SERIAL_ECHOLNPAIR_P(PSTR(MSG_PROBE_OFFSET " X"), probe_offset.x, SP_Y_STR, probe_offset.y, SP_Z_STR, probe_offset.z);
+    SERIAL_ECHOLNPAIR_P(
+      #if HAS_PROBE_XY_OFFSET
+        PSTR(MSG_PROBE_OFFSET " X"), probe_offset.x, SP_Y_STR, probe_offset.y, SP_Z_STR
+      #else
+        PSTR(MSG_PROBE_OFFSET " X0 Y0 Z")
+      #endif
+      , probe_offset.z
+    );
     return;
   }
 
+  // Start with current offsets and modify
   xyz_pos_t offs = probe_offset;
 
+  // Assume no errors
   bool ok = true;
 
   if (parser.seenval('X')) {
     const float x = parser.value_float();
-    if (WITHIN(x, -(X_BED_SIZE), X_BED_SIZE))
-      offs.x = x;
-    else {
-      SERIAL_ECHOLNPAIR("?X out of range (-", int(X_BED_SIZE), " to ", int(X_BED_SIZE), ")");
-      ok = false;
-    }
+    #if HAS_PROBE_XY_OFFSET
+      if (WITHIN(x, -(X_BED_SIZE), X_BED_SIZE))
+        offs.x = x;
+      else {
+        SERIAL_ECHOLNPAIR("?X out of range (-", int(X_BED_SIZE), " to ", int(X_BED_SIZE), ")");
+        ok = false;
+      }
+    #else
+      if (x) SERIAL_ECHOLNPAIR("?X must be 0 (NOZZLE_AS_PROBE)."); // ...but let 'ok' stay true
+    #endif
   }
 
   if (parser.seenval('Y')) {
     const float y = parser.value_float();
-    if (WITHIN(y, -(Y_BED_SIZE), Y_BED_SIZE))
-      offs.y = y;
-    else {
-      SERIAL_ECHOLNPAIR("?Y out of range (-", int(Y_BED_SIZE), " to ", int(Y_BED_SIZE), ")");
-      ok = false;
-    }
+    #if HAS_PROBE_XY_OFFSET
+      if (WITHIN(y, -(Y_BED_SIZE), Y_BED_SIZE))
+        offs.y = y;
+      else {
+        SERIAL_ECHOLNPAIR("?Y out of range (-", int(Y_BED_SIZE), " to ", int(Y_BED_SIZE), ")");
+        ok = false;
+      }
+    #else
+      if (y) SERIAL_ECHOLNPAIR("?Y must be 0 (NOZZLE_AS_PROBE)."); // ...but let 'ok' stay true
+    #endif
   }
 
   if (parser.seenval('Z')) {
diff --git a/Marlin/src/inc/Conditionals_LCD.h b/Marlin/src/inc/Conditionals_LCD.h
index a471e3d8b1..a3cc222308 100644
--- a/Marlin/src/inc/Conditionals_LCD.h
+++ b/Marlin/src/inc/Conditionals_LCD.h
@@ -500,6 +500,7 @@
 #define PROBE_SELECTED (HAS_BED_PROBE || EITHER(PROBE_MANUALLY, MESH_BED_LEVELING))
 
 #if HAS_BED_PROBE
+  #define HAS_PROBE_XY_OFFSET   DISABLED(NOZZLE_AS_PROBE)
   #define HAS_CUSTOM_PROBE_PIN  DISABLED(Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN)
   #define HOMING_Z_WITH_PROBE   (Z_HOME_DIR < 0 && !HAS_CUSTOM_PROBE_PIN)
   #ifndef Z_PROBE_LOW_POINT
diff --git a/Marlin/src/inc/SanityCheck.h b/Marlin/src/inc/SanityCheck.h
index 9c5b7893fc..35a9ffc9b3 100644
--- a/Marlin/src/inc/SanityCheck.h
+++ b/Marlin/src/inc/SanityCheck.h
@@ -1149,6 +1149,20 @@ static_assert(Y_MAX_LENGTH >= Y_BED_SIZE, "Movement bounds (Y_MIN_POS, Y_MAX_POS
     #error "Z_MIN_PROBE_PIN must be defined if Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN is not enabled."
   #endif
 
+  #if ENABLED(NOZZLE_AS_PROBE)
+    constexpr float sanity_nozzle_to_probe_offset[] = NOZZLE_TO_PROBE_OFFSET;
+    static_assert(sanity_nozzle_to_probe_offset[0] == 0.0 && sanity_nozzle_to_probe_offset[1] == 0.0,
+                  "NOZZLE_AS_PROBE requires the X,Y offsets in NOZZLE_TO_PROBE_OFFSET to be 0,0.");
+  #endif
+
+  #if DISABLED(NOZZLE_AS_PROBE)
+    static_assert(MIN_PROBE_EDGE >= 0, "MIN_PROBE_EDGE must be >= 0.");
+    static_assert(MIN_PROBE_EDGE_BACK >= 0, "MIN_PROBE_EDGE_BACK must be >= 0.");
+    static_assert(MIN_PROBE_EDGE_FRONT >= 0, "MIN_PROBE_EDGE_FRONT must be >= 0.");
+    static_assert(MIN_PROBE_EDGE_LEFT >= 0, "MIN_PROBE_EDGE_LEFT must be >= 0.");
+    static_assert(MIN_PROBE_EDGE_RIGHT >= 0, "MIN_PROBE_EDGE_RIGHT must be >= 0.");
+  #endif
+
   /**
    * Make sure Z raise values are set
    */
diff --git a/Marlin/src/module/configuration_store.cpp b/Marlin/src/module/configuration_store.cpp
index b4de08db78..b389741b79 100644
--- a/Marlin/src/module/configuration_store.cpp
+++ b/Marlin/src/module/configuration_store.cpp
@@ -2354,7 +2354,12 @@ void MarlinSettings::reset() {
   #if HAS_BED_PROBE
     constexpr float dpo[XYZ] = NOZZLE_TO_PROBE_OFFSET;
     static_assert(COUNT(dpo) == 3, "NOZZLE_TO_PROBE_OFFSET must contain offsets for X, Y, and Z.");
-    LOOP_XYZ(a) probe_offset[a] = dpo[a];
+    #if HAS_PROBE_XY_OFFSET
+      LOOP_XYZ(a) probe_offset[a] = dpo[a];
+    #else
+      probe_offset.x = probe_offset.y = 0;
+      probe_offset.z = dpo[Z_AXIS];
+    #endif
   #endif
 
   //
@@ -3102,9 +3107,16 @@ void MarlinSettings::reset() {
         say_units(true);
       }
       CONFIG_ECHO_START();
-      SERIAL_ECHOLNPAIR_P(PSTR("  M851 X"), LINEAR_UNIT(probe_offset.x),
-                                  SP_Y_STR, LINEAR_UNIT(probe_offset.y),
-                                  SP_Z_STR, LINEAR_UNIT(probe_offset.z));
+      SERIAL_ECHOLNPAIR_P(
+        #if HAS_PROBE_XY_OFFSET
+          PSTR("  M851 X"), LINEAR_UNIT(probe_offset_xy.x),
+                  SP_Y_STR, LINEAR_UNIT(probe_offset_xy.y),
+                  SP_Z_STR
+        #else
+          PSTR("  M851 X0 Y0 Z")
+        #endif
+        , LINEAR_UNIT(probe_offset.z)
+      );
     #endif
 
     /**
diff --git a/Marlin/src/module/delta.cpp b/Marlin/src/module/delta.cpp
index bfedf25177..9a57442759 100644
--- a/Marlin/src/module/delta.cpp
+++ b/Marlin/src/module/delta.cpp
@@ -91,13 +91,7 @@ void recalc_delta_settings() {
 #endif
 
 float delta_calibration_radius() {
-  return FLOOR((DELTA_PRINTABLE_RADIUS - (
-    #if HAS_BED_PROBE
-      _MAX(HYPOT(probe_offset.x, probe_offset.y), MIN_PROBE_EDGE)
-    #else
-      MIN_PROBE_EDGE
-    #endif
-  )) * calibration_radius_factor);
+  return FLOOR((DELTA_PRINTABLE_RADIUS - _MAX(HYPOT(probe_offset_xy.x, probe_offset_xy.y), MIN_PROBE_EDGE)) * calibration_radius_factor);
 }
 
 /**
diff --git a/Marlin/src/module/motion.h b/Marlin/src/module/motion.h
index 2e8ff64f52..0128f66463 100644
--- a/Marlin/src/module/motion.h
+++ b/Marlin/src/module/motion.h
@@ -291,6 +291,7 @@ void homeaxis(const AxisEnum axis);
  */
 
 #if IS_KINEMATIC // (DELTA or SCARA)
+
   #if HAS_SCARA_OFFSET
     extern abc_pos_t scara_home_offset; // A and B angular offsets, Z mm offset
   #endif
@@ -315,13 +316,25 @@ void homeaxis(const AxisEnum axis);
   }
 
   #if HAS_BED_PROBE
-    // Return true if the both nozzle and the probe can reach the given point.
-    // Note: This won't work on SCARA since the probe offset rotates with the arm.
-    inline bool position_is_reachable_by_probe(const float &rx, const float &ry) {
-      return position_is_reachable(rx - probe_offset.x, ry - probe_offset.y)
-             && position_is_reachable(rx, ry, ABS(MIN_PROBE_EDGE));
-    }
-  #endif
+
+    #if HAS_PROBE_XY_OFFSET
+
+      // Return true if the both nozzle and the probe can reach the given point.
+      // Note: This won't work on SCARA since the probe offset rotates with the arm.
+      inline bool position_is_reachable_by_probe(const float &rx, const float &ry) {
+        return position_is_reachable(rx - probe_offset.x, ry - probe_offset.y)
+               && position_is_reachable(rx, ry, ABS(MIN_PROBE_EDGE));
+      }
+
+    #else
+
+      FORCE_INLINE bool position_is_reachable_by_probe(const float &rx, const float &ry) {
+        return position_is_reachable(rx, ry, MIN_PROBE_EDGE);
+      }
+
+    #endif
+
+  #endif // HAS_BED_PROBE
 
 #else // CARTESIAN
 
@@ -340,6 +353,7 @@ void homeaxis(const AxisEnum axis);
   inline bool position_is_reachable(const xy_pos_t &pos) { return position_is_reachable(pos.x, pos.y); }
 
   #if HAS_BED_PROBE
+
     /**
      * Return whether the given position is within the bed, and whether the nozzle
      * can reach the position required to put the probe at the given position.
@@ -348,11 +362,12 @@ void homeaxis(const AxisEnum axis);
      *          nozzle must be be able to reach +10,-10.
      */
     inline bool position_is_reachable_by_probe(const float &rx, const float &ry) {
-      return position_is_reachable(rx - probe_offset.x, ry - probe_offset.y)
+      return position_is_reachable(rx - probe_offset_xy.x, ry - probe_offset_xy.y)
           && WITHIN(rx, probe_min_x() - slop, probe_max_x() + slop)
           && WITHIN(ry, probe_min_y() - slop, probe_max_y() + slop);
     }
-  #endif
+
+  #endif // HAS_BED_PROBE
 
 #endif // CARTESIAN
 
diff --git a/Marlin/src/module/probe.cpp b/Marlin/src/module/probe.cpp
index efe8b8a2bd..31c1128369 100644
--- a/Marlin/src/module/probe.cpp
+++ b/Marlin/src/module/probe.cpp
@@ -56,8 +56,6 @@
   #include "../feature/backlash.h"
 #endif
 
-xyz_pos_t probe_offset; // Initialized by settings.load()
-
 #if ENABLED(BLTOUCH)
   #include "../feature/bltouch.h"
 #endif
@@ -86,6 +84,14 @@ xyz_pos_t probe_offset; // Initialized by settings.load()
 #define DEBUG_OUT ENABLED(DEBUG_LEVELING_FEATURE)
 #include "../core/debug_out.h"
 
+
+xyz_pos_t probe_offset; // Initialized by settings.load()
+
+#if HAS_PROBE_XY_OFFSET
+  xyz_pos_t &probe_offset_xy = probe_offset;
+#endif
+
+
 #if ENABLED(Z_PROBE_SLED)
 
   #ifndef SLED_DOCKING_OFFSET
@@ -698,7 +704,7 @@ float probe_at_point(const float &rx, const float &ry, const ProbePtRaise raise_
   xyz_pos_t npos = { rx, ry };
   if (probe_relative) {
     if (!position_is_reachable_by_probe(npos)) return NAN;  // The given position is in terms of the probe
-    npos -= probe_offset;                                   // Get the nozzle position
+    npos -= probe_offset_xy;                                // Get the nozzle position
   }
   else if (!position_is_reachable(npos)) return NAN;        // The given position is in terms of the nozzle
 
diff --git a/Marlin/src/module/probe.h b/Marlin/src/module/probe.h
index 9345787d44..cdb42170e5 100644
--- a/Marlin/src/module/probe.h
+++ b/Marlin/src/module/probe.h
@@ -31,6 +31,12 @@
 
   extern xyz_pos_t probe_offset;
 
+  #if HAS_PROBE_XY_OFFSET
+    extern xyz_pos_t &probe_offset_xy;
+  #else
+    constexpr xy_pos_t probe_offset_xy{0};
+  #endif
+
   bool set_probe_deployed(const bool deploy);
   #ifdef Z_AFTER_PROBING
     void move_z_after_probing();
@@ -54,6 +60,7 @@
 #else
 
   constexpr xyz_pos_t probe_offset{0};
+  constexpr xy_pos_t probe_offset_xy{0};
 
   #define DEPLOY_PROBE()
   #define STOW_PROBE()
@@ -71,13 +78,7 @@
     );
 
     inline float probe_radius() {
-      return printable_radius - (
-        #if HAS_BED_PROBE
-          _MAX(MIN_PROBE_EDGE, HYPOT(probe_offset.x, probe_offset.y))
-        #else
-          MIN_PROBE_EDGE
-        #endif
-      );
+      return printable_radius - _MAX(MIN_PROBE_EDGE, HYPOT(probe_offset_xy.x, probe_offset_xy.y));
     }
   #endif
 
@@ -85,10 +86,8 @@
     return (
       #if IS_KINEMATIC
         (X_CENTER) - probe_radius()
-      #elif ENABLED(NOZZLE_AS_PROBE)
-        _MAX(MIN_PROBE_EDGE_LEFT, X_MIN_POS)
       #else
-        _MAX((X_MIN_BED) + (MIN_PROBE_EDGE_LEFT), (X_MIN_POS) + probe_offset.x)
+        _MAX((X_MIN_BED) + (MIN_PROBE_EDGE_LEFT), (X_MIN_POS) + probe_offset_xy.x)
       #endif
     );
   }
@@ -96,10 +95,8 @@
     return (
       #if IS_KINEMATIC
         (X_CENTER) + probe_radius()
-      #elif ENABLED(NOZZLE_AS_PROBE)
-        _MAX(MIN_PROBE_EDGE_RIGHT, X_MAX_POS)
       #else
-        _MIN((X_MAX_BED) - (MIN_PROBE_EDGE_RIGHT), (X_MAX_POS) + probe_offset.x)
+        _MIN((X_MAX_BED) - (MIN_PROBE_EDGE_RIGHT), (X_MAX_POS) + probe_offset_xy.x)
       #endif
     );
   }
@@ -107,10 +104,8 @@
     return (
       #if IS_KINEMATIC
         (Y_CENTER) - probe_radius()
-      #elif ENABLED(NOZZLE_AS_PROBE)
-        _MIN(MIN_PROBE_EDGE_FRONT, Y_MIN_POS)
       #else
-        _MAX((Y_MIN_BED) + (MIN_PROBE_EDGE_FRONT), (Y_MIN_POS) + probe_offset.y)
+        _MAX((Y_MIN_BED) + (MIN_PROBE_EDGE_FRONT), (Y_MIN_POS) + probe_offset_xy.y)
       #endif
     );
   }
@@ -118,10 +113,8 @@
     return (
       #if IS_KINEMATIC
         (Y_CENTER) + probe_radius()
-      #elif ENABLED(NOZZLE_AS_PROBE)
-        _MAX(MIN_PROBE_EDGE_BACK, Y_MAX_POS)
       #else
-        _MIN((Y_MAX_BED) - (MIN_PROBE_EDGE_BACK), (Y_MAX_POS) + probe_offset.y)
+        _MIN((Y_MAX_BED) - (MIN_PROBE_EDGE_BACK), (Y_MAX_POS) + probe_offset_xy.y)
       #endif
     );
   }
diff --git a/buildroot/share/tests/LPC1768-tests b/buildroot/share/tests/LPC1768-tests
index 881e0534ee..566cbd27a6 100755
--- a/buildroot/share/tests/LPC1768-tests
+++ b/buildroot/share/tests/LPC1768-tests
@@ -45,7 +45,8 @@ opt_enable REPRAP_DISCOUNT_FULL_GRAPHIC_SMART_CONTROLLER SDSUPPORT ADAPTIVE_FAN_
            Z_SAFE_HOMING ADVANCED_PAUSE_FEATURE PARK_HEAD_ON_PAUSE \
            LCD_INFO_MENU ARC_SUPPORT BEZIER_CURVE_SUPPORT EXTENDED_CAPABILITIES_REPORT AUTO_REPORT_TEMPERATURES SDCARD_SORT_ALPHA
 opt_set GRID_MAX_POINTS_X 16
-exec_test $1 $2 "Re-ARM with Many Features"
+opt_set NOZZLE_TO_PROBE_OFFSET "{ 0, 0, 0 }"
+exec_test $1 $2 "Re-ARM with NOZZLE_AS_PROBE and many features."
 
 # clean up
 restore_configs
-- 
GitLab