Bug: bed_mesh and z_thermal_adjust applies z changes even if z isn't homed

I think i have located a bug, at least the behavior doesn’t seem intended.

Simplest steps to reproduce
Make sure bed_mesh fading is disabled

[bed_mesh]
fade_end: 0

Now load a mesh (or calibrate a new one and run M84) home X:

BED_MESH_LOAD PROFILE=default
G28 X

If we move X now, we’ll get an error

G0 X90

Result:

Must home axis first: 200.000 0.000 24.864 [0.000]

This seems unintended. The reason this happens is that both bed_mesh and z_thermal_adjust both apply their changes regardless of whether Z is homed or not. I propose to add a check to skip adjustment if Z isn’t homed.

Here’s a crude “Fix” for bed_mesh that solves that issue. Do note that i have not verified that the mesh is applied correctly after this change, and i’m also not sure if the check needs to be in both get_position() and move().

diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py
index 92c3ac54..2cc8c273 100644
--- a/klippy/extras/bed_mesh.py
+++ b/klippy/extras/bed_mesh.py
@@ -173,7 +173,9 @@ class BedMesh:
             return 1.
     def get_position(self):
         # Return last, non-transformed position
-        if self.z_mesh is None:
+        curtime = self.printer.get_reactor().monotonic()
+        kin_status = self.toolhead.get_kinematics().get_status(curtime)
+        if self.z_mesh is None or 'z' not in kin_status['homed_axes']:
             # No mesh calibrated, so send toolhead position
             self.last_position[:] = self.toolhead.get_position()
             self.last_position[2] -= self.fade_target
@@ -198,9 +200,13 @@ class BedMesh:
         return list(self.last_position)
     def move(self, newpos, speed):
         factor = self.get_z_factor(newpos[2])
-        if self.z_mesh is None or not factor:
-            # No mesh calibrated, or mesh leveling phased out.
+        curtime = self.printer.get_reactor().monotonic()
+        kin_status = self.toolhead.get_kinematics().get_status(curtime)
+        if self.z_mesh is None or not factor or 'z' not in kin_status['homed_axes']:
+            # No mesh calibrated, mesh leveling phased out or z not homed.
             x, y, z, e = newpos
+            logging.info("bed_mesh no mesh adjustment: Current Z: %.4f fade_target: %.4f "
+                    % (z, self.fade_target))
             if self.log_fade_complete:
                 self.log_fade_complete = False
                 logging.info(

A use case where this causes a lot of problems is with [homing_override] such as for sensorless homing where consistency on a CoreXY is vastly improved by centering the axes after homing. Another is in the case of [safe_z_home] if you have a bed_mesh loaded, such as after finishing a print.

UPDATE:
To clarify, you cannot complete a G28 if you have a homing_override that centers each axis after homing, or if you use safe_z_home, if you load a bed_mesh first or have z_thermal_adjust defined in your config without setting z_adjust_off_above to something lower than your homing z_hop (which is not the default).

This is a verifiable, reproducible bug, only using klipper as intended.

You could argue that you “just shouldn’t have a bed mesh loaded outside of a print”
But that is inconsistent with the default behavior of BED_MESH_CALIBRATE which sets the calibrated mesh as active upon completion. Any re-homing after this will break. It also doesn’t solve the problem with z_thermal_adjust at all.

This is actually mentioned in the docs, but it’s a bit hidden. It’s recommended to include bed_mesh_clear in your end gcode for this very reason.

https://www.klipper3d.org/G-Codes.html#bed_mesh_clear

That doesn’t really cover all cases though. What if the user calibrates a bed mesh and shuts off the motors (could be idle_timeout) before printing? And then there’s the whole z_thermal_adjust thing.

Can you point me to where it is explained in the docs?

It’s not fully explained, just mentioned at the link I posted. I personally think it would be better to leave the behavior as is and better document it, but it’s not my decision to make.

How come? What are the downsides of not making adjustments to Z when z isn’t homed?

I’ll flip the question around. What is gained by allowing X and Y movement when Z isn’t homed and a mesh is loaded?

I think i’ve pointed out quite a few situations already. Also this isn’t just about bed_mesh. z_thermal_adjust does it too, and that’s enabled by default if configured.

Doing X/Y movements without homing Z is quite common. If you coincidentally load a mesh, or have z_thermal_adjust configured, klipper breaks. Currently you can’t combine homing_override that centers axes or [safe_z_home] with z_thermal_adjust. You don’t think that’s a problem?

All I can say is I clear my mesh in my print end and cancel macros, and I’ve never run into this problem in 5 years using Klipper on 4 different printers. That said it would likely be up to Kevin to decide if this is something that should be changed.

Those load correction values for future Z moves but do not move the Z axes at the moment of loading.

I know where you’re coming from, i was in that same boat until recently. I manage a rather large project, and i have had this bug reported multiple times over the years, i’ve brushed it off till now with “you must be doing something wrong”, although i could find nothing in their configs or logs. People couldn’t tell how or why it happened but they kept getting sporadic Must home axis first errors. Until today were 2 users were persistent enough to make a reproducible report, where this keeps happening. Configuring z_thermal_adjust basically bricks your printer, and loading a bed_mesh before homing bricks it too. That’s pretty bad.

Those load correction values for future Z moves but do not move the Z axes at the moment of loading.

I’m not sure why you’re pointing this out, the bug is not “loading a bed_mesh throws an error” or “configuring z_thermal_adjust throws an error”. But maybe i’m just not understanding you right.

These commands give no errors because they do not need homing to be executed. They just load values. In the moment of execution they do not change anything with the Z position.

For every movement like G0 X90 you need a complete homing.

I think you misunderstood my report. Check the steps i posted to reproduce it.

To clarify, you cannot complete a G28 if you have a homing_override that centers each axis after homing, or if you use safe_z_home, if you load a bed_mesh first or have z_thermal_adjust defined in your config without setting z_adjust_off_above to something lower than your homing z_hop.

It’s a combination of factors here. This is a verifiable, reproducible bug, only using klipper as intended.

You could argue that you “just shouldn’t have a bed mesh loaded outside of a print”
But that is inconsistent with the default behavior of BED_MESH_CALIBRATE which sets the calibrated mesh as active upon completion. Any re-homing after this will break. It also doesn’t solve the problem with z_thermal_adjust at all.

I was one of the people that helped find this issue with this issue. I don’t load a bed mesh before homing and have z thermal adjust on with no fade and I cannot home the printer with the ratos homing overrides unless I disable z thermal adjust before homing. I get a must home axis error if I don’t. While I can work around it, a loaded mesh or z thermal adjust shouldn’t stop homing with a homing override should it?

1 Like

One thing that is gained in particular instance.

Klipper’s normal homing operation is to home X and leave X in the home position to home Y. If you want or need to move X away from home before homing Y, and you have z thermal adjust enabled or a bed mesh loaded WITHOUT a fade, you cannot complete a G28 without clearing the mesh or disabling z thermal adjust before G28.

1 Like

For what it is worth, I’d say the code works as intended. A request was made to move to a new X and Z location and Z wasn’t homed so the code correctly reported an error.

I understand what you are reporting. To wit, “I wanted the printer to move to X60 so Klipper should have done whatever was necessary to do something similar to that”. However, that’s not how Klipper is designed and it’s not how I’d want my 3d printer to behave. I much prefer software that takes specific actions upon specific requests. In this case there was a request to move X and Z, and the software could not do that.

As for getting this to work on the impacted printers, there are a few alternatives. The homing_override config section can be used - it will turn off Z homing checks for the duration of the macro. As mentioned, it’s also possible to only enable bed_mesh during a print.

Finally, if the z_thermal_adjust behavior is confusing we can make the associated software changes so that it requires explicit enablement instead of defaulting on (as was recently done for bed_mesh).

Cheers,
-Kevin

Also, the initial report says this impacts homing_override and safe_z_home. However, homing_override should disable homing checks, and safe_z_home does not apply any g-code transforms. So, if either of those modules are impacted then please provide the klipper log file from the event (run M112 immediately after the event so the log has more debugging info).

-Kevin

A request was made to move to a new X location. Then implicitly, because of behavior that is opaque to the user, a transform was made internally to that move to transform Z - regardless if z could be moved or not. It is not evident to the user that this will happen, and the error that results, doesn’t point them in any useful direction either. While i think i agree with you on how i’d want my 3dprinter to behave (meaning i understand and it is logical why it’s doing what it’s doing), i think there’s a discrepancy between the intention of that behavior and the result or at least the perception of that behavior. What may seem logical, knowing the internal workings of klipper, is perceived as illogical to users who don’t. “Why would it move Z without explicit instruction, when Z can’t be moved yet”.

Granted, i know our goals are different, i’m taking klipper to users who were never the intended end user in the first place. I understand that UX is relative - it’s very much contingent on the users experiences and underlying understanding of the systems they’re interacting with. This is probably why our perception of “a bug” in this case i different.

Anyway…

Wouldn’t that only happen if set_position_z was defined ( klipper/homing_override.py line 50 at master · Klipper3d/klipper (github.com))? It seems to me that homing_override overrides homing state rather than disabling homing checks. Furthermore, if an error happens within homing_override with set_position_* set, the printer can be left in an invalid state (if it didn’t complete G28 for all axes), which can cause crashes if you issue movement commands afterwards. But that’s besides the point.

You’re right about safe_z_home though, toolhead.manual_move() doesn’t apply transforms.

Finally, if the z_thermal_adjust behavior is confusing we can make the associated software changes so that it requires explicit enablement instead of defaulting on (as was recently done for bed_mesh).

I think this would be appropriate for consistency if nothing else, and it would alleviate the issue somewhat. That way you would have to explicitly enable all movement transforms for them to take place. I like that, that’s much better.

So, if either of those modules are impacted then please provide the klipper log file from the event (run M112 immediately after the event so the log has more debugging info).

Provided you think homing_override should disable homing checks for Z if set_position_z isn’t set, i will set up a test machine to provide this.

From a users perspective there hasn’t been a request to move X and Z (where z is not homed), there is only a request to move X only. From the software perspective I can see where it’s requesting Z to move because of the bed mesh and thermal adjust when moving X. I think this is the issue. From a users perspective I don’t see why any Z adjustments from bed mesh or z thermal adjust needs to be made during any homing operation.