Potential Issue with Bed levelling process

It appears that the Snapmaker Calibration process measures the bed level at points that are materially different than those used in the levelling process when printing. I have been looking at this in conjunction with the work i have been doing on a glass leveling probe. If I am correct this may go some way towards explaining why many of us have had great difficulty getting a good bed level and the high incidence of burying nozzles into beds.
I have made a note of why I think this to be the case, what is actually going on with bed leveling and what I did to try and test my hypotheses. It would be great if those with more knowledge than me could have a look at it and even try the test themselves including the folks at Snapmaker.

As a secondary issue, the process also ignores the values set in Configuration.h which detail the offset between the Nozzle and probe.


Nice work. It could explain a lot.

No comment form Snapmaker and I am now sure I am correct.
Put two small 5mm blocks on G1029 levelling points i1 j2 and i3 j2. Do a touch screen level. Then get the head to move from X0 to X320 along the line (Y160). The head moves up and down but the high and low points correspond with the leveling mesh points given by G42, not those actually probed in the Snapmaker leveling process. which are 19mm to the right. If you try this make sure the head is up slightly Z3 or it will crash into the bed on the right side.
As this is something wrong with the firmware I have raised it as an issue on GitHub - not response yet.

If you want to to a bed level at the same points that are used for leveling when you print you can turn off Workspace, level using G1029 and turn workspace back on.
I use the macro detailed below. You need to put a number in the “probe Z offset goes here”.
Calculate this by doing a touch screen calibration and when you do the manual bit where you bring the nozzle down to the levelling sheet make sure you record how far you bring it down and this is the “probe Z offset number”. This can be adjusted later very easily just dont make the number too big as the script moved the nozzle down to the bed.
So run the macro from something USB connected to the printer ( Luban or OctoPrint with Macro plugin) Once done print something small to check level. You can adjust the level up or down slightly using G1029 D x.xx If x.xx is a negative all printing will move towards the bed, move away if positive. No real idea why but I seem to need to move it all down 0.01mm with G1029 D -0.01. Change the M140/M190 values to change the temperature at which the bed is leveled or comment out to do it without heat.

;bed level script bed heated to 65c
;bed heat is left on as usually doing a print after this.
G21 ;set units to millimetre
G90 ;set absolute positioning
M425 X0.02 Y0.02 Z0.02 F1 S0 ; set backlash to 0.02mm
M140 S65 ; set bed temp for bed level, heat while homeing
G28 ;do this now, done in G1029 but takes ages
M190 S65 ; wait for bed temp
M206 x0 y0 ;remove worspace offset prior to leveling
M500 ;save change
;Auto Level
G1029 A ;start levelling
G91; relative positioning for Z offset move
G0 X0.5 Y-10 ;Adjust for difference in probe offset
G0 Z-x.xx ; Device specific Z offset, reduce to bring print closer to the bed (bigger minus number). (equivalent to M851)
G1029 S ;save data
G1029 D0 ;end levelling
G90; restore absolute positioning
M206 x-19 y-10 ;Put workspace offset back on
M500 ;save change
G28; home all for safety

1 Like

OK I dug into this just a bit now, and thar be dragons.

OK First M851: Snapmaker2-Controller/M851.cpp at main · Snapmaker/Snapmaker2-Controller · GitHub

Despite the documentation saying it supports X and Y, it does not because that feature was added 2 years ago, before Snapmaker diverged (Add M851 X Y probe offsets (#15202) · MarlinFirmware/Marlin@df1e512 · GitHub)

Next: The firmware probe offset in X and Y is hardcoded in the configuration file, and is used here: Snapmaker2-Controller/probe.cpp at c09a114cfef9892fe7d2b4aa5b56531ffc828a8d · Snapmaker/Snapmaker2-Controller · GitHub

I don’t see anything obviously wrong with this. However, a lot has changed in how up-to-date marlin implements this function, many possible bug fixes in here: Marlin/probe.cpp at 755adb8973aa69ca6f0832e606060eaca065b88c · MarlinFirmware/Marlin · GitHub

Snapmaker ignores M851 completely, tried changing it long ago and it did nothing to the Nozzle height so I have been using G1029 D to change the whole leveling mesh (which it does nicely).
The probe offset is used correctly and taken from configuration.h. I didn’t think it was but I do now apart from the Z entry. As you say the X & Y parts of M851 have not been implemented yet so you need to change configuration.h to change the probe offset.
The issue is Snapmaker use their own G1029 leveling and this takes place under a workspace offset of X-19 Y-10 and this is what I am saying is incorrect as the probed Z vales are saved in the mesh at their logical, not their workspace coordinates. This has been verified from actual nozzle positions on the bed - not reported coordinates. I have also run a leveling with workspace set to X0 Y0 and the probed heights then agree with the nozzle heights during printing.
What I can’t work out is how they have set the bed size and probe limits that result in the logical mesh being shifted left and forward so that the G1029 workspace probe happens in the middle of the bed. I certainly cant find anything where the entries usually are in configuration.h.
The only place I have seen bed size specified is directly in linier.cpp at line 397 but that doesn’t contain anything about probe boundaries. The answer might be that they have defined the bed in relation to the travel limits of the rails and their endstops and these do not actually line up with the bed so have used a workspace to compensate rather than specifying the boundaries in configuration.h and mirroring the relevant leveling coding in G29 when they wrote G1029. If this is the case, they might need to do quite a bit of change to G1029, hopefully they can copy more chunks out of G29 and make it all work.
Also cant find where the M206 offset value is set. It’s not reset by a machine re-boot or by firmware change so it must be in the flash memory from somewhere.
Lets hope I get a sensible reply to the GitHub issue

That’s new to me. If you’ve been looking at it recently would you mind permalinking where that is in the codebase.

My understanding is there is no special workspace offset, and G1029 calls auto_probing from abl.cpp: https://github.com/Snapmaker/Snapmaker2-Controller/blob/c09a114cfef9892fe7d2b4aa5b56531ffc828a8d/Marlin/src/gcode/bedlevel/abl/G1029.cpp#L119

Is this what you’re looking for? https://github.com/Snapmaker/Snapmaker2-Controller/blob/c09a114cfef9892fe7d2b4aa5b56531ffc828a8d/Marlin/src/feature/bedlevel/bedlevel.cpp#L139

I think I have it now, sorted out what a permalink is and how to do it!
Also know a bit more about what is going on with this firmware.
All printing is done under an M206 home offset, M503 shows it as X-19 Y-10 or you can use the Snapmaker command M2000 that shows all workspace.
It’s set in configuration.h line 1025 as #define L_HOME_OFFSET_DEFAULT {-19, -10, 0, 0}.

L is for Large (A350), this is used in Marlin.cpp line 252 to set l_home_offset which is used to set home_offset in linear.cpp line 408, which is used in M206_M428.cpp to set home_offset. Seems like a bit of a loop but probably a reason.
Linear.cpp defines the X and Y Max positions -

This is how far you can travel on a rail from home and also bed sizes that are smaller than those X & Y max positions. X is 345 but the bed size is 320 so 45 less. The Nozzle travel on the rail is also offset from the center of the rail by 19mm so a home offset of -19 brings the rail travel to the edge of the bed marked 0 on the print surface. This is all great for printing but not for levelling.
Levelling is done outside any work offsets, where probing should take place is normally set in configuration.h.

The entries here look wrong and mean that the bed mesh is towards the front left.
The problem is these entries will be different for each machine but I cant find that they are set anywhere else. I would have expected something that prefixed the probe limits by the version of the printer but cant find any of that.
I assume most of the leveling stuff is done by existing code as there isn’t much code in G1029. So sorting this just needs the bed probe area to be set for the various machines, workspace offset to be disabled and it should work.
I am afraid I dont know how to code and I have got here just using logic and observation and I am at my limit. So very happy to be told I am completely wrong as long as anybody who does can explain why leveling done with measurable highs and lows gives those highs and lows at the G42, non workspace points on the bed and not those actually probed by G1029 or the touch screen process.


You’ve done well. Nice work.

One possible solution would be to continue troubleshooting, but in more detail with the tools in VSCode for tracing calls and coming up with a detailed plan. Or, just update the github issue with this info and hope the developers look into it more.

Will have a look at VSCode. It’s all quite interesting. I do hope Snapmaker know all the stuff I have said so far. The missing bit is how a G29 level ignores workspaces and G1029 doesn’t. Alssociated with where G1029 gets its probing coordinates and whether it passes them to the probing routines. Didn’t know about tools to trace stuff so hopefully VSCode will help.
This all does explain why Tone’s spreadsheet gives gives much better levering - it uses G42 and probes the correct points.
Thanks for the advice.

I have to clarify, every time previously you mentioned ‘workspace offset’ I was interpreting that to mean equivalent to a G54 workspace coordinate offset. I see not in the documentation M206 is referred to as a workspace offset, a confusing choice of terminology. I previously referred to that exclusively as the home offset.

Regardless, it’s worth being precise with the terminology.

Is this referring to a G54 workspace or the M206 home offset. If M206, I don’t agree. G29 calls the same probe function G1029 does, they should behave identically. Are you referring to G42 instead?

If you are referring to G42, does it probe the correct point? If you are at a nozzle position of X100 Y100 and issue G42 I believe it moves straight down, not in X or Y - I think I would expect it to based on the above discussion?

OK the language is confusing. The Snapmaker M2000 command uses
active coordinate: -1
coordinate 1: X: 0.000, Y: 0.000, Z: 0.000, B: 0.000
position_shift: X: 0.000000, Y:0.000000, Z:0.000000, B:0.000000
home_offset: X: -19.000000, Y:-10.000000, Z:0.000000, B:0.000000
workspace_offset: X: -19.000000, Y:-10.000000, Z:0.000000, B:0.000000

It’s both home_offset and workspace_offset that are not used in the coordinates used for the leveling mesh - and I know that language is not clear. I think the leveling mesh uses “raw coordinates” but again I have found the langage confusing and I have not been able to find a definition.
I think the active_coordinate system is used in CNC.

G42 moves the nozzle to the i & j mesh leveling points that match the probe position for that point taking into account the Probe to nozzle offset. It doesn’t move down at all. Snapmaker G1029 W is the equivalent but for no apparent reason it moves down to a set Z13mm.
G30 probes straight down from the current position.
G42 i1 j2 moves the nozzle to X72 Y160. G1029 W i1 j2 moves the nozzle to X91 Y170.
Put a 5mm block on X91 Y170 and do a bed level with a 1mm clearance to the bed on the final point. When you move the nozzle with G0 to Z91 Y170 the nozzle is 4.2mm above the bed. When you move the nozzle to X72 Y160 the Nozzle is 5.9mm above the bed. The Snapmaker probe at i1 J2 has been saved and is being used for leveling as if it was at X72 Y160, the G42 i1 J2 position.
Do we really have to show them exactly where their code is wrong? You would think it should be enough to prove that it is so in a way that anybody can repeat.

I think you know at least some or all of this, but here’s what my understanding is.

active coordinate: -1: this returns gcode.active_coordinate_system and when -1 means that the workspace_offset is not enabled, and the machine is in native machine coordinate space. This is consistent with the default startup behavior in 3DP mode, using native space. The laser module starts up in workspace 1, G54. 3DP boots into G53. This comment is illuminating:
* System index -1 is used to specify machine-native.

coordinate 1 returns gcode.coordinate_system[0][X_AXIS], etc. for Y and Z. This is the what the workspace coordinate offset would be, as coordinate_system[] is an array that stores each XYZ offset for each workspace offset. In this case the offset is 0.

position_shift: this is returning position_shift[X_AXIS], etc. for Y and Z. position_shift is calculated as the difference between a new and old offset when changing coordinate systems. As native machine space has an offset of 0, and the stored workspace 1 offset in this case is also 0, the difference is 0.

home_offset returns home_offset[X_AXIS], etc., and is related to the M206 home offset. It is used when homing as the known native space coordinates when touching the homing limit switches.

workspace_offset returns workspace_offset[X_AXIS], etc. It is calculated as follows:
workspace_offset[axis] = home_offset[axis] + position_shift[axis]. It is referred to through a macro WORKSPACE_OFFSET(AXIS) which is solely used by NATIVE_TO_LOGICAL and LOGICAL_TO_NATIVE to add or subtract the total offset from the current position. The documentation indicates workspace_offset is precomputed to save on computes.

None of the above are the probe offset referenced in probe_pt when probing relative (the default, and only ever overridden for delta autocalibration). Relative probing subtracts X_PROBE_OFFSET_FROM_EXTRUDER and similar for Y. Those values are the ones defined in configuration.h as X13 Y19.15 Z1.

So far I think everything looks like it should be working as intended.

G42 has a P parameter as follows:

    if (parser.boolval('P')) {
      if (hasI) destination[X_AXIS] -= X_PROBE_OFFSET_FROM_EXTRUDER;
      if (hasJ) destination[Y_AXIS] -= Y_PROBE_OFFSET_FROM_EXTRUDER;

This does not show up in any documentation and could be an interesting troubleshooting tool.

This is true for a weird reason: see the following snippet from G30.

  const float xpos = parser.linearval('X', current_position[X_AXIS] + X_PROBE_OFFSET_FROM_EXTRUDER),
              ypos = parser.linearval('Y', current_position[Y_AXIS] + Y_PROBE_OFFSET_FROM_EXTRUDER);

parser.linearval returns either the parameter value specified (‘X’ for example) or the supplied default. So a G30 command with no ‘X’ parameter will return the current X position + the X probe offset.

The calculated or supplied X and Y position is passed to probe_pt, the same function discussed above. probe_pt, which is called with the relative flag in its default state, then subtracts off the X and Y probe offsets previously added in the G30 method.

Something about this behavior seems off to me, but it matches the current Marlin branch’s behavior so it likely is intended.

For more consistent behavior, it’s likely that G1029 should follow more closely the behavior in probe_pt. I somewhat have run out of time tracing the _GET_MESH_X macro, perhaps there’s a quirk in there yet to be discovered.


Nice work guys. Lots of good info there. e.g. I’d spent a bit of time a while ago working out that M851 is ignored but hadn’t worked out that G1029 was another way to adjust the Z offset.


@Edwin Could somebody from Snapmaker have a look at this. I have also posted an issue on GitHub but nobody has responded at all. It would be good to know if I am right and somebody is looking at it or you have checked and for some reason my measurements and understanding of the process are incorrect.
This is the GitHub issue, happy for somebody to reply there.

Our firmware developer @scotthuang is looking into this issue. I ask him to answer this question in 3 days.

I will update the information here and also post it on GitHub Page.


Thanks Edwin. I can see they are looking at it on GitHub.

@stewl do you think it’s been like this since the beginning?

I went back 3 Firmware versions and it was the same. Didn’t dare go back further as early firmware had some bed destroying bugs.

I am sorry that I am not an expert in coding, and not completely understand what you asked in several posts and the issue on the GitHub page, but I have tried to make it clear.

Please do not hesitate to ask me what questions you have.


Hi @Edwin
Unfortunately the document didn’t answer any of the questions. I have had another go to get my point across in the linked document. I have set permissions so you can leave comments within the document if that works for you.
The first point re probe offset should hopefully just need a “yes” answer.
The main point is that practical tests prove the Snapmaker levelling process does not match print levelling and needs to be fixed. This doesn’t just impact print quality. If you bed is not perfectly level or is on a slope (or the X axis is on a slope) it can easily lead to the nozzle burying itself in the bed.
I will also refer to the attached document on GitHub.