Firmware retraction Z-HOP

Hi *,
to yesterday I implemented Material profiles for Klipper. They worked fine but was useless as some materials print fine with z-hop and other without, so I still needed to reslice.
So I implemented z-hop for firmware retraction. It is rarely tested until now, but a print that is known to print ugly without z-hop now looks fine. It has been tested for a while by multiply users, it should be safe to use on single extruder printers.

The strategy is to modify G0, G1, G2 and G3 to respect a positive Z-offset that is set while retract. All movements will then add this offset until the unretraction is done.
For not need to switch between modes and restore them the Z move will be made inside G10 (retract) and G11 (unretract) in the corresponding coordinate mode.

SET_ZHOP HEIGHT=0.2 VELOCITY=100
Will activate the zhop and save it to non-volatile memory (remains after restart/reboot…)

GET_ZHOP
Will print the current setting.

CLEAN_ZHOP
should be called in the start gcode, before any movement! It will set the z-offset to 0.

There is no setting for the Z movement speed you need to change it in the macro. Just find and replace F300

Ofc firmware retraction needs to be activated in the slicer for getting this work!

The methods also have a wait after retract setting. You can safe ignore it or remove it if you don’t need it. It was helpful for me in some situations.

[firmware_retraction]
retract_length: 1.5
#   The length of filament (in mm) to retract when G10 is activated,
#   and to unretract when G11 is activated (but see
#   unretract_extra_length below). The default is 0 mm.
retract_speed: 45
#   The speed of retraction, in mm/s. The default is 20 mm/s.
unretract_extra_length: 0.02
#   The length (in mm) of *additional* filament to add when
#   unretracting.
unretract_speed: 10
#   The speed of unretraction, in mm/s. The default is 10 mm/s.


[gcode_arcs]
#If you allready have this somewhere you will need to remove it here or there


[gcode_macro G0]
rename_existing: G0.1
gcode:
  G1 {rawparams}

[gcode_macro G1]
rename_existing: G1.1
variable_z_hop_offset = 0
gcode:
  {% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}
  {% if printer.gcode_move.absolute_coordinates and OFFSET > 0 and ('Z' in params) %}
    ; Rewrite paramters for adjust Z if offset is activ and we are absulute
    {% set new_params = ''|string %}
    {% if ('E' in params) %}
      {% set new_params = new_params ~ " E" ~ params.E %}
    {% endif %}
  
    {% if ('F' in params) %}
      {% set new_params = new_params ~ " F" ~ params.F %}
    {% endif %}
  
    {% if ('X' in params) %}
      {% set new_params = new_params ~ " X" ~ params.X %}
    {% endif %}
  
    {% if ('Y' in params) %}
      {% set new_params = new_params ~ " Y" ~ params.Y %}
    {% endif %}
  
    
    {% set new_params = new_params ~ (' Z%g' % (params.Z|float + OFFSET))|string %}
    
    ; Make the movement
    G1.1{new_params}
  {% else %}
    ; Don't care if relativ or no offset activ
    G1.1 {rawparams}
  {% endif %}


[gcode_macro G2]
rename_existing: G2.1
gcode:
  {% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}
  {% if printer.gcode_move.absolute_coordinates and OFFSET > 0 and ('Z' in params) %}
    ; Rewrite paramters for adjust Z if offset is activ and we are absulute

    {% set new_params = [] %}
    {% for key in params %}
      {% if key == "Z" %}
        {% set x = new_params.append('Z%g' % (params[key]|float + OFFSET)) %}
      {% else %}
        {% set x = new_params.append(key + params[key]) %}
      {% endif %}
    {% endfor %}

    {% set new_params=new_params|join(" ") %}

    ; Make the movement
    G2.1 {new_params}
  {% else %}
    ; Don't care if relativ or no offset activ
    G2.1 {rawparams}
  {% endif %}

[gcode_macro G3]
rename_existing: G3.1
gcode:
  {% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}
  {% if printer.gcode_move.absolute_coordinates and OFFSET > 0 and ('Z' in params) %}
    ; Rewrite paramters for adjust Z if offset is activ and we are absulute

    {% set new_params = [] %}
    {% for key in params %}
      {% if key == "Z" %}
        {% set x = new_params.append('Z%g' % (params[key]|float + OFFSET)) %}
      {% else %}
        {% set x = new_params.append(key + params[key]) %}
      {% endif %}
    {% endfor %}

    {% set new_params=new_params|join(" ") %}

    ; Make the movement
    G3.1 {new_params}
  {% else %}
    ; Don't care if relativ or no offset activ
    G3.1 {rawparams}
  {% endif %}

# Hacked G11 (unretract) for have a short delay after unretract, remove it or set wait to 0, if this is not needed.
[gcode_macro G11]
rename_existing: G11.1
gcode:
  {% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}
  ; Z-UNHOP
  {% if OFFSET > 0 %}
    {% set VELOCITY = printer.save_variables.variables.current_zhop_velocity|default(300)|float %}
    SET_GCODE_VARIABLE MACRO=G1 VARIABLE=z_hop_offset VALUE=0
    {% set speed = printer.gcode_move.speed %}
    ; Make a different movement coresponting to current coordinate mode
    {% if printer.gcode_move.absolute_coordinates %}
      #G1.1 F{VELOCITY} Z{(printer.gcode_move.position.z - OFFSET)}
      G91
      G1.1 Z-{OFFSET} F{VELOCITY}
      G90
    {% else %}
      G1.1 Z-{OFFSET} F{VELOCITY}
    {% endif %}
    G1.1 F{speed}
  {% endif %}

  G11.1
  ; Have an addional parameter for wait after unretract
  {% set wait = printer.save_variables.variables.current_unretract_wait|default(0)|float %}
  {% if wait > 0 %}
    G4 P{'%d' %wait}
  {% endif %}

# Hacked G10 (retract) with ZHOP
[gcode_macro G10]
rename_existing: G10.1
gcode:
  {% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}
  ; Make the retract
  G10.1

  ; Make the ZHOP
  {% set HEIGHT = printer.save_variables.variables.current_zhop_height|default(0)|float %}

  {% if HEIGHT > 0 %}
    {% if OFFSET > 0 %}
      SHOW_MSG MSG="ZHOP allready activ"
    {% else %}
      {% set VELOCITY = printer.save_variables.variables.current_zhop_velocity|default(300)|float %}
      {% set speed = printer.gcode_move.speed %}
      SET_GCODE_VARIABLE MACRO=G1 VARIABLE=z_hop_offset VALUE={HEIGHT}
      ; Make a different movement coresponting to current coordinate mode
      {% if printer.gcode_move.absolute_coordinates %}
        #SHOW_MSG MSG="G10 abs up: {(printer.gcode_move.position.z + HEIGHT)}"
        #G1.1 Z{(printer.gcode_move.position.z + HEIGHT)} F{VELOCITY}
        G91
        G1.1 Z{HEIGHT} F{VELOCITY}
        G90
      {% else %}
        G1.1 Z{HEIGHT} F{VELOCITY}
      {% endif %}
      G1.1 F{speed}
    {% endif %}
  {% endif %}


# Set the wished ZHOP height/distance. Set to 0 will disable it!
[gcode_macro SET_ZHOP]
gcode:
  {% set HEIGHT = params.HEIGHT|default(0)|float %}
  {% set VELOCITY = params.VELOCITY|default(300)|float %}
  {% if VELOCITY > 0 %}
    SAVE_VARIABLE VARIABLE='current_zhop_velocity' VALUE={VELOCITY}
  {% endif %}
  {% if HEIGHT >= 0 %}
    SAVE_VARIABLE VARIABLE='current_zhop_height' VALUE={HEIGHT}
    SHOW_MSG MSG="ZHOP: Z{HEIGHT}/F{VELOCITY}"
  {% endif %}

# This should allways be part of the start gcode. Worestcase you will print with an positive offset.
[gcode_macro CLEAR_ZHOP]
gcode:
  SET_GCODE_VARIABLE MACRO=G1 VARIABLE=z_hop_offset VALUE=0

# Print ZHOP settings
[gcode_macro GET_ZHOP]
gcode:
  {% set HEIGHT = printer.save_variables.variables.current_zhop_height|default(0)|float %}
  {% set VELOCITY = printer.save_variables.variables.current_zhop_velocity|default(300)|float %}
  {% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}
  SHOW_MSG MSG="ZHOP height:    {HEIGHT}"
  SHOW_MSG MSG="ZHOP velocity:  {VELOCITY}"
  SHOW_MSG MSG="ZHOP retracted: {'yes' if (OFFSET > 0) else 'no'}"

[gcode_macro SHOW_MSG]
gcode:
  {% set MSG = params.MSG|default("No msg")|string %}
  M117 {MSG}
  RESPOND MSG={'"%s"' % MSG}

I’m not very happy with casting the deltas to float in relative mode. Rounding error could add up if no coordinates come in absolute mode. But not sure if float is really a float here and if this could be a real world problem.

Additionally, I’m not very sure my order is good:

  1. retract
  2. zhop
  3. movement
  4. unzhop
  5. unretract

I think the best thing would be:

  1. unretract 2/3
  2. start zhop and parallel unretract 1/3
  3. movement
  4. unzhop
  5. unretract

As this is difficult maybe it could be an idea to make zhop and retract parallel. It would be easy to implement, but I’m not sure, that this is an advantage.

If you see any issues please write!
Please also write if I forgot to supply some macros that are used by this, just the standard problem if some finds macros on the net ^^

If you want to test this code, don’t forget to add this or something similar to your config (thanks @Sitoxic and @dredivan):

[save_variables]
filename: ~/klipper-variables.cfg

#For SHOW_MSG macro:
[respond]

Thanks for reading :slight_smile:

Edit:

  1. removed velocity bug
  2. added z velocity to the ZHOP settings.
  3. Repaired Typos in set function
  4. Changed the movement from absolute to relative (what fixed the issue we had while using CURA 5, but the root cause is sadly still unknown)
  5. Added SHOW_MSG macro
  6. Did some test prints
  7. Added G2 and G3
  8. Removed a bug inside G2 and G3 caused variable by scope inside for loops
  9. Fixed problem with G3
3 Likes

You might want to check the discussion here:

Nice thanks,
I think I did implement the option 2 that Kevin mentioned, at least something very close to it. (Conditional G0, G1 instead of swapped handlers).
If I understand the issue correctly at least the original issue will not appear in my code, as the z containing move will use the corresponding z-offset.

Ok, possibly there is a bug, in some prints the printer gets really slow. I will learn more about it tomorrow and tell you if I could track down the bug… :confused:

Good morning from Germany,
the bug is fixed! The code in my first post has been patched. It is now restoring the speed.

Hi, thanks for these super useful macros, but I got some troubles after putting them to my config.

First when I run GET_ZHOP, I get

Error evaluating 'gcode_macro GET_ZHOP:gcode': UndefinedError: 'instance object' has no attribute 'save_variables' 

I ignored it and went ahead started a print with firmware retraction enabled, then I get

Error evaluating 'gcode_macro G10:gcode': UndefinedError: 'instance object' has no attribute 'save_variables'

and print stopped.

after that I thought I needed to set the zhop height first, ran SET_ZHOP HEIGHT=0.2, then I get

Unknown command:"SHOW_MSG"
Unknown command:"SAVE_VARIABLE"
Unknown command:"SAVE_VARIABLE"

How do I make this work, did I missed somthing?

Edit:
Did a little research, I guess what I need is a simple

[save_variables]
filename:

Have not test it yet, because I am still in a middle of a print, will report back later.

1 Like

Thank you for the reminder!
I added the needed configuration to the first post!
I would be happy if you would tell me your experience with that macro.

And thank you again :slight_smile:

There is a bug with Cura 5 and firmware retractions with this z-hop macro. Most likely the problem is caused by the macro.
I’m working on a solution, but until I found something please pause use the macro…
Sorry

Changelog:

  • The problem is fixed now
  • A fatal problem with SET_ZHOP is fixed. The function was unused by my system, so the issue was hided well :confused:
  • added the SHOW_MSG.
  • added G2 and G3 to respect the zhop offset.

The macro is still rarely tested, but possible changed from mega alpha to alpha now. ^^
Usage can bring risks for your printer! As there is at least one more person that is using it, I will report back issues and update the code with the corresponding fixes (if there still can be found issues).

As mentioned before, if you’re going to test it please report back any issue you have. Good testing is the half of the way! Thanks!

Best regards and have a nice Print :slight_smile:

Getting error with code copy and pasted from your post. Would Updating fix this?

They also have all this same line, are they supposed to be different?

{% set OFFSET = printer["gcode_macro G1"].z_hop_offset %}

Possibly. Also remember that G2 and G3 is only available if you have activated arcs:

If you don’t want to use arc functions (G2 and G3) you can just remove this:

This is correct, it accesses a “global” variable. It needs to be shared through many functions.

Sorry for late reply, I need to change the discord notification settings. I get to many messages and these, that’s require an action are not getting attention… :confused:

Best regards

Just to update you:
I’m using this retraction macro for some month now. I had no problems.
But it takes time to make all that z-hop with that low speed, so I added a second z screw to my Ender 5 to avoid vibrations on fast z-hop speed. This made my prints with high z-hop speed more clean and was a real upgrade.
Sadly I can not support you with information for the optimum z-hop speed on your printer.
Higher speeds have these advantages:

  • Lower print time
  • Less oozing on Z-Hop

and possibly these disadvantages

  • more vibrations (acceleration)
  • possible to lose steps (acceleration+speed)

If you want to find a good speed/acceleration on your printer you will need to run some test. If you try higher speeds stay close to you printer… Lost steps can result in wrong printing height and can damage your bed.
Specially if your Z axis is still stock/original on your printer it could be worth to share your results here.

Hint:
You also need to change your klipper settings to allows higher Z speeds.

I hope this helps!

@masterq
Thank you very much for creating these macros! It still boggles me that Klipper hasn’t figured out how to do this, yet. Anyway…

I have been using your macros for several weeks now. They work great, and I have had no problems with them at all.

If I do see any problems, I’ll be sure to report back here.

Thanks again!

1 Like

Hello tfjield,
thank you very much for reporting and share your experience!

hello? can you help please
SHOW_MSG don`t work

chrome_1AMoNTrwMd

UPD:
need

[respond]

in general config

1 Like

Thank you for the hint, I added the information to the first post!

here is M207 gcode macros with Z hope:

[gcode_macro M207]
gcode:
    {% if params.S is not defined %}
        {% set S = printer.configfile.settings.firmware_retraction.retract_length|float %}
    {% endif %}
    {% if params.F is not defined %}
        {% set F = printer.configfile.settings.firmware_retraction.retract_speed|float %}
    {% endif %}
    {% if params.Z is not defined %}
        {% set Z = printer.save_variables.variables.current_zhop_height|float %}	 
    {% endif %}

    SET_RETRACTION RETRACT_LENGTH={S} RETRACT_SPEED={F}
    SET_ZHOP HEIGHT={Z}

May be You know how add this?

chrome_g9KuzKdalW

Sadly no,
but I tried to get information about this too. My research is some month old, but as long I remember I started to read a lot of code and the result was that it is necessary to patch mainsailos or moonrakers code. But decided not to implement it and accept to use the macro dropdown menu.
image
If you hide unneeded macros the view will be more clean.

If you find a way to do it please tell us :slight_smile:

Best regards
MasterQ

works great for one of my extruders but when i print with second extuder nothing works…not even firmware retract