I’m working on a macro that implements G10 (Retract) and G11 (Unretract) for finer control of the unretract length. My goal is to have a variable length of unretraction that depends on the time that has passed since the retraction. This is to help with very oozy nozzles like the Volcano.
The principle is inspired from the Scalable Extra Prime Cura plugin, that varies the extra length of unretraction based on how much the nozzle has traveled since the retraction. My intent is to turn this into a macro, so that it can be used regardless of slicer (so no longer limited to Cura, as long as the slicer supports firmware retractions), and to use travel time rather than travel distance, as time spent traveling is a much more accurate predictor of the amount of oozing.
I have most of the logic pinned down, but I’m having a hard time figuring out how to capture the retraction/unretraction timing precisely.
My first attempt was this (simplified to focus on timing):
[gcode_macro G10]
variable_last_retract_time: -1
gcode:
SET_GCODE_VARIABLE MACRO=G10 VARIABLE=last_retract_time VALUE={printer.idle_timeout.printing_time}
; Retraction logic ...
[gcode_macro G11]
gcode:
{% set last_retract_time = printer["gcode_macro G10"].last_retract_time %}
{% set TIME_SINCE_LAST_RETRACT = printer.idle_timeout.printing_time - last_retract_time %}
; Unretraction logic ...
This basic implementation doesn’t work, because when G10 and G11 are called during a print, the commands get buffered, and the jinja tags get evaluated at buffer-time rather than execution-time. For instance, if the following gcode is executed:
; Retract
G10
; Travel
G1 X30 Y10 F800
; Unretract
G11
…both G10 and G11 will be evaluated before the intermediate travel has even begun. So the printer.idle_timeout.printing_time
variable is not representative of the instant at which the retraction and unretractions happen, and TIME_SINCE_LAST_RETRACT
is incorrect. Same story with other timing variables like printer.system_stats.cputime
and printer.toolhead.estimated_print_time
.
To address this issue, I attempted to use M400 (Finish moves) in two encapsulating macros, which forces Klipper to wait for any toolhead move to complete before evaluating the macros that capture timing information:
[gcode_macro _RETRACT]
variable_last_retract_time: -1
gcode:
SET_GCODE_VARIABLE MACRO=_RETRACT VARIABLE=last_retract_time VALUE={printer.idle_timeout.printing_time}
; Retraction logic ...
[gcode_macro _UNRETRACT]
gcode:
{% set last_retract_time = printer["gcode_macro _RETRACT"].last_retract_time %}
{% set TIME_SINCE_LAST_RETRACT = printer.idle_timeout.printing_time - last_retract_time %}
; Unretraction logic ...
[gcode_macro G10]
gcode:
M400
_RETRACT
[gcode_macro G11]
gcode:
M400
_UNRETRACT
This approach fixes the issue, but M400 introduces a ~1s delay to execution, so for instance if the following gcode is executed:
; Extrude
G1 X30 Y10 E10 F800
; Retract
G10
… the toolhead will perform its move-extrude motion, pause for a second, then execute the retraction. This pause is enough for the pressure in the nozzle to push an excessive amount of molten plastic at the end of the extrusion. Without M400, there is no pause, but we’re back to the previous time-capturing issues.
Does anyone know if there’s another way to compare the real time that has passed between two macro executions? Is there any other way to force a gcode macro not to be buffered, without introducing any processing delay? Is there another way to capture timing in gcode, outside of Jinja templates? Would implementing this as an ‘extra’ module would make more sense and would provide access to real-time timing information?
Plan B is to calculate the unretraction amount from travel distance, but this would be less than ideal for multiple reasons.
As a side-note, I’m aware of [firmware_unretraction], but the commands it implements don’t allow the adjustment of unretract_extra_length between the G10 and G11 calls. Retractions are reset as soon as SET_RETRACTION is called and any subsequent call to G11 is ignored.