Skip to content

Fix bilinear ABL grid_start probe/nozzle coordinate mixup - #28380

Open
PenguinAkiko wants to merge 6 commits into
MarlinFirmware:bugfix-2.1.xfrom
PenguinAkiko:fix-abl-bilinear-coordinate-mixup
Open

Fix bilinear ABL grid_start probe/nozzle coordinate mixup#28380
PenguinAkiko wants to merge 6 commits into
MarlinFirmware:bugfix-2.1.xfrom
PenguinAkiko:fix-abl-bilinear-coordinate-mixup

Conversation

@PenguinAkiko

Copy link
Copy Markdown

Description

This PR fixes a critical coordinate-space mixup for the bedlevel.grid_start position when using Bilinear Auto Bed Leveling (AUTO_BED_LEVELING_BILINEAR).

The Problem:

The bilinear ABL probing process builds its mesh in probe-space, where abl.probe_position_lf represents the coordinate directly under the probe. However, the resulting bedlevel.grid_start value is later consumed by other systems (like bedlevel.get_z_correction()) as a motion/nozzle-space origin.

On machines with a non-zero NOZZLE_TO_PROBE_OFFSET in the XY plane, this discrepancy caused the entire leveling mesh to be incorrectly shifted by the probe's XY offset, leading to inaccurate bed compensation.

The Solution:

To resolve this, this PR introduces a centralized utility function, Probe::convert_to_nozzle_xy(), to handle the conversion from probe-space to nozzle-space. This function is now used in two key locations:

  1. In G29: The bilinear grid origin is now converted to nozzle-space before it is compared with a stored mesh or saved with bedlevel.set_grid().
  2. In M420 S2: The test pattern generator is also updated to use the same conversion, ensuring that all methods of generating a bilinear mesh use consistent, nozzle-space coordinates for the grid origin.

This change has no functional effect on setups where the XY probe offset is zero or when using NOZZLE_AS_PROBE.

Requirements

Any 3D printer configured with AUTO_BED_LEVELING_BILINEAR.

Benefits

Fixes a bug that incorrectly shifted the entire Bilinear ABL mesh by the probe's XY offset.

Configurations

No configuration files are attached as the change is hardware-independent.

To test this PR, any configuration with the following enabled will reproduce the issue and verify the fix:

// in Configuration.h
#define AUTO_BED_LEVELING_BILINEAR

// A probe with an XY offset from the nozzle
#define NOZZLE_TO_PROBE_OFFSET { 10, -5, 0 }

Related Issues

Fixes a bug that does not appear to have a corresponding open issue.

The variable abl.probe_position_lf is in probe-space, but bedlevel.grid_start is consumed as a nozzle-space origin for mesh interpolation. This caused the entire bilinear mesh to be shifted by the probe offset on machines with a non-zero XY offset.

Convert the bilinear lower-left grid origin before comparing or storing the mesh, and apply the same conversion in M420 S2.

Add Probe::convert_to_nozzle_xy() to centralize the conversion.
@DerAndere1

DerAndere1 commented Apr 11, 2026

Copy link
Copy Markdown
Contributor

In my recent experiments with probing, I set up the probe as a dedicated tool (dummy extruder) with hotend offsets for the probe being equal to NOZZLE_TO_PROBE_OFFSET with good success.:

#define EXTRUDERS 3
#define NOZZLE_TO_PROBE_OFFSET {40, 40, -10}
#define HOTEND_OFFSET_X {0, 20, 40}
#define HOTEND_OFFSET_Y {0, 20, 40}
#define HOTEND_OFFSET_Z {0, -5, -10}
#define PROBING_TOOL 2

The trouble I had in getting probing to work indicates that the proposed changes are indeed needed. Or maybe we just need to improve documentation on how to set up a probe for G30, G38 and G29.

@Maker-Paul

Maker-Paul commented Apr 28, 2026

Copy link
Copy Markdown

Hi. I have a pull request I think yours might be connected to. My issue involves using a separate Z stop and probe #28418. I have extensively tested and regularly printed graphic representations of the bed map and still have tiny discrepancies in the mesh values I haven't identified. My machine now probes at the XY home position before commencing with G29. The probe trigger height is then taken off the probe readings for the rest of the mesh readings (probe Z offset). That should mean that when the probe measures the bed at the XY home position (bed center) during the mesh scan it should read 0 but it never does.

I can see that an area roughly corresponding to the XY probe offset is very close to 0 now I have reviewed my maps. Is that what you would expect to see? The difficulty here is that the differences in the mesh values is now so small, because of all the major improvements I've made, they are almost lost in the noise caused by thermal expansion and repeatability accuracy of the probe.

The first image shows a Bed map control taken a few days ago compared to one just done. with the Bed cold.

Legend Red is above 0, Yellow is below 0, Blue is 0
The image below shows the bed is very consistent between origonal scan and two days later (16/04/2026).
Bed mesh cold comparison

This image below shows the surface topology after the bed has heated and soaked for 15 minutes (The bed was trammed Hot nearly a week ago)
Bed mesh Hot

The Mesh visualizer is a spread sheet I created to display the data provided from the printer with M503.

@Maker-Paul

Maker-Paul commented Apr 28, 2026

Copy link
Copy Markdown

This is a comparison of the cold control from a couple of weeks ago and one done today 28/04/2026 with your fix applied to my local firmware build. note the square just above the center square which is the XY home coordinate is 0.008 and is the closest of any point on the mesh to the expected 0. I Since checked my Y offset to find it was a bit out which would account for this so I will rerun the test.

Bed Mesh cold comparison with #28380 applied

Bed Mesh cold comparison with #28380 applied

I still have an issue where all the values in the mesh seam to slowly increase if I don't reset the Eprom quite often, so there is still an accumulative error getting in to the calculations somewhere. Any thoughts?

@Maker-Paul

Maker-Paul commented Apr 28, 2026

Copy link
Copy Markdown

Now with my corrected X and Y offsets. Looks pretty good the center square and the one above are the closest to 0 either side of it so the actual zero must be between them. that looks to be pretty much the center of the bed. The actual values at 3 decimal places are getting into the repeatability tolerance of the sensor. The sensor is 17mm square at the bottom and although I assume it reads at the center of that 17 x 17mm there is no guarantee of that. But I think the pattern is pretty easy to see.

Bed Mesh cold comparison with #28380 applied   correct XY offsets

Thanks for your efforts I will be keeping your fix in the local firmware build that I use on my Tronxy's

@thinkyhead

thinkyhead commented Aug 24, 2026

Copy link
Copy Markdown
Member

Before I can merge this I will need to understand it better, and it is a bit confusing to refer to "probe space" when we don't really use such a thing. We don't select the probe as a tool by itself, but we subtract the probe offset from the target position when doing probing and move the tool to that position. So we have some functions to help us with "probe moves" in that sense.

When we probe a point at X100 Y100 we put the probe at that bed point, so the nozzle might be at, for example, X90 Y105 (for a probe offset of +10, -5). Then the Z offset that was recorded at X100 Y100 will apply to the leveling adjustment later, when the nozzle is at X100 Y100. The same print space is applied in all cases, no separate "probe space" required.

When another tool is set to be the active probing tool, the nozzle-to-probe offset in that case should be relative to that tool's position, not to the first tool's. Then when we want to move the probe to X100 Y100 while T1 is active, only the probe offset matters to that positioning. Later when we have T0 or T1 or Tn… selected and move it to X100 Y100 the Z offset recorded for X100 Y100 will apply.

So you want to apply the probe offset to abl.probe_position_lf but that is not a "probe space" coordinate, it's just regular bed space. There is no place where we store coordinates persistently in terms of "where the nozzle should go to get the probe to XY" but always in terms of "where in the shared coordinate space does the probe want to go." When we do probing moves, that is the moment at which we subtract the probe offset, to get it to the place it needs to probe.

Given these considerations it is unclear why one would want the values they supply in G29 L F to be modified when these are meant specifically to be shared space bed coordinates. Naturally these bounds are meant to be used responsibly, and the user must take care that a probe with X+20 offset will require a larger G29 L value if the nozzle can't reach X-20 to put the probe at X0.

Apologies if the variables named probe_position_* imply a probe-relative position of some kind. These are most definitely ordinary shared bed coordinates.

@thinkyhead thinkyhead added Bug: Potential ? Needs: Testing Testing is needed for this change labels Aug 24, 2026
@Maker-Paul

Maker-Paul commented Aug 24, 2026

Copy link
Copy Markdown

Hi Scott. What I can tell you is. That when I probe the center of the bed to automatically determin the probe Z offset. The centre position on the bed mesh map was never Zero and I couldn't understand why. untill I came accross #28380. I edited my version of bugfix 2.1.x to include it and the bed mapping then consistently showed the center Z value as close to zero. Well within the tolerance of the probe itself. Going back over previouse mesh maps I found that a value close to zero corresponded with the XY coordinate offset from the center by the X and Y probe offsets settings.

As most of my mapped Z values tend to be very small it was a pattern that wasn't easy to spot on the graphical map printouts. I just knew the center position, becouse that is where the auto Z offset is measured, should have mapped to zero or very close but never did untill I applied #28380.

You can look at the graphical maps above to see the whole mapped Z values.

@thinkyhead

thinkyhead commented Aug 25, 2026

Copy link
Copy Markdown
Member

It's not easy to tell from mapped values whether a fix is really a fix or just working around something else, such as a config adjustment that has been acting as a kludge.

Would it be possible for you to enable DEBUG_LEVELING_FEATURE and provide a full log of the probe procedure both before and after the patch? I would recommend using the patch from the point earlier, although the adjustments I made were only intended to apply some minimal correction.

I'd like to compare those logs to see in each case what coordinates are being used for the bounds of the ABL mesh, how the mesh is applied during movement once leveling is enabled, and perhaps sort out where this notion comes from that the given mesh coordinates need to be shifted over by the probe offset before measurements are taken.

The thing that confounds me —and I hope your logs will clear up— is that when we tell the probe to measure the bed at 100,100 we put the probe at 100,100 to take the measurement of the preheated bed there with a preheated probe. We trust the user to provide good bounds of where the mesh ought to be (when using LFRB parameters)….

One possibility that occurs to me is that for those using a different tool for probing, the default bounds of the ABL mesh may not be fully implemented to account for this, so experimenting with G29 L F R B could be a valuable part of the logging process.

The boilerplate for G29 logging is…

  • Download the source branch for this PR (link at top) to test with the latest code.
  • Enable DEBUG_LEVELING_FEATURE and M114_DETAIL and re-flash the firmware.
  • Connect to your printer from host software such as Cura, Printrun or Repetier Host.
  • Send M502 and M500 to ensure your Configurations are applied.
  • Issue the command M111 S247 to enable maximum logging.
  • Perform a G28 to do your standard homing procedure.
  • Do a G29 to probe the bed. This will also enable bed leveling.
  • Copy the log outputs into a .TXT and/or .ZIP file and attach it to your next reply.

Repeat this procedure, if needed, to demonstrate inconsistencies. From these logs we should hopefully get a better idea of what's going on with this issue.

I hope we can sort out what's truly going on. I can of course run some tests with and without this change to see if it makes proper sense and demonstrates some misunderstanding on my part. But I do know that our intent with the mesh bounds was to take the probe readings at the corner coordinates that are given to respect the user command. If that is the thing that is not happening, well clearly something is amiss, maybe at a deeper level, so I certainly appreciate all the data you can provide. 🤞🏻

@DerAndere1

DerAndere1 commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Are your issues with the displayed values in the mesh, or with incorrect positioning of the toolhead, when moving with G1?
I tested current bugfix-2.1.x without your changes and positioning was fine even with different values for hotend offsets and nozzle to probe offsets. The probing points were centered on my bed as expected. Here are the caveats:

  • to test this without being hit by other bugs, I had to apply the following changes:
    • PR 28456 to reliably deploy my solenoid probe
    • PR 26956 to make toolchanges possible on printers where the bed is at a fixed Z hight.
    • I had to fix an additional bug where the position is corrupted when performing a tool change while bed leveling is active. The fix is in the latest commit of PR 26956.
  • AFAIK, NOZZLE_TO_PROBE_OFFSET is always relative to tool 0. It was that way before PROBING_TOOL was introduced and it stayed that way for a good reason. Making it relative to the active tool is not a good idea: when PROBING_TOOL is disabled, you would have to adjust the nozzle to probe offset each time you perform G29/G30/G34/G35 with a different active tool.
  • Also, for testing do not use M851. There are reports that you need to use M500 and reboot the printer after you used it. Set the correct NOZZLE_TO_PROBE_OFFSET in the config. In addition there is this comment regarding Probing Margins in Configuration_adv.h:
 * This will allow all positions to match at compilation, however
 * should the probe position be modified with M851XY then the
 * probe points will follow. This prevents any change from causing
 * the probe to be unable to reach any points.
  • home offsets (M206 / M428) should not be used. It is not a well standardized feature and interactions with other features are undocumented, so we cannot rely on it during testing. Set correct Z_MIN_POS and Z_MAX_POS in your config.
  • Keep ENABLE_LEVELING_AFTER_G28 disabled. Send M420 S0 before homing and move to a central position before enabling bed leveling with M420 S1. Enabling bed leveling close to endstops might corrupt the position (not fully tested)
  • In general, pay attention not to trigger hardware endstops outside of G28 until PR 28388 is merged

Here is my log file. I added shims on the bed at some probing points to simulate an uneven bed.
G29_test.zip

Here my relevant config settings:

#define EXTRUDERS 3
#define HOTEND_OFFSET_X { 0.0, 00.00, -30 } // (mm) relative X-offset for each nozzle
#define HOTEND_OFFSET_Y { 0.0, 0.00, -30 }  // (mm) relative Y-offset for each nozzle
#define HOTEND_OFFSET_Z { 0.0, -10.00, -10 }  // (mm) relative Z-offset for each nozzle
#define AUTO_BED_LEVELING_BILINEAR
//#define USE_PROBE_FOR_Z_HOMING
#define SOLENOID_PROBE
#define NOZZLE_TO_PROBE_OFFSET { 10, -22, -2.2, 0, 0 }
#define PROBING_TOOL 2
//#define PROBE_TOOLCHANGE_NO_MOVE  // Suppress motion on probe tool-change
#define PROBING_MARGIN 50
#define Z_PROBE_LOW_POINT          -2 // (mm) Farthest distance below the trigger-point to go before stopping

#define X_HOME_DIR -1
#define Y_HOME_DIR -1
#define Z_HOME_DIR  1

#define X_BED_SIZE 220
#define Y_BED_SIZE 220

#define X_MIN_POS -180.135
#define Y_MIN_POS -212.575
#define Z_MIN_POS 0
#define X_MAX_POS X_BED_SIZE
#define Y_MAX_POS Y_BED_SIZE
#define Z_MAX_POS 255

#define RESTORE_LEVELING_AFTER_G28
//#define ENABLE_LEVELING_AFTER_G28

//#define ENABLE_LEVELING_FADE_HEIGHT

#define SEGMENT_LEVELED_MOVES
#define LEVELED_SEGMENT_LENGTH 5.0 // (mm) Length of all segments (except the last one)
#define GRID_MAX_POINTS_X 5
#define GRID_MAX_POINTS_Y GRID_MAX_POINTS_X
//#define EXTRAPOLATE_BEYOND_GRID
//#define ABL_BILINEAR_SUBDIVISION

#define BED_CENTER_AT_0_0
//#define MANUAL_X_HOME_POS 0
//#define MANUAL_Y_HOME_POS 0
//#define MANUAL_Z_HOME_POS 0

#define EEPROM_SETTINGS     // Persistent storage with M500 and M501

#define SWITCHING_TOOLHEAD

@Maker-Paul

Maker-Paul commented Aug 26, 2026

Copy link
Copy Markdown

Sorry Scott. I have been down this road before spent many many hours enabling debug and doing requested test. This PR #28380 is not mine. So I haven't delved deeply into what or how it does what it does..

But it did solve why I couldn't make sense of my xy home position (bed centre). Which is used to measure the probe Z offset. It did not mapp to Zero as expected. The position that did mapp to Zero was shifted by exactly the set x and y offsets and was not the centre of the bed.

That only became apparent after applying #28380 as you observed its not easy to see on the mapps as they are all small values but the closest to Zero was not in the centre untill I applied #28380. My G29 pull request (#28418 which uses your suggestion of just measuring the probe Z offset before G29) doesn't do anything other than measure probe trigger height at the xy home position (bed centre) and adds it to the user Z offset. It doesn't do anything to XY coordinates.

By placeing a physical marker on the bed I can observe the probe is possitioned correctly to carry out the Z offset measurement and all subsequent G29 probing points. After G29 has finished the nozzle is correctly positioned over the same point. I have not changed/set any mesh bounds, I don't even know where to set anything like that as I've never needed to. the only thing I have set is X and Y offsets from nozzle to probe.

My machines are all now working perfectly without having to do daily leveling. Perfect first layers every time. At the moment sorry I don't have the spare time to get involved with extensive testing and I'm very reluctant to mess with machines that are working so perfectly.

I hope this helps clarify.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug: Potential ? Needs: Testing Testing is needed for this change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants