I’m currently diving into macro writing and need some advice.
My goal is to get a working kind of a loop that reacts to the extruder temperature.
I know that Klipper/Jinja2 evaluates the macros once at the beginning and prevents kind of live updates. It requires additional macros to counteract this.
However I did not yet find a way to pass the updated variable/value back to the calling macro.
Here is an example but I tested different ways already:
[gcode_macro _MINOR]
variable_temp1: 0
gcode:
{% set temp1 = printer.extruder.temperature | float %} # read live extruder temperature
M118 _Minor Call {temp1} C. # Echo temp to show what macro has what value
SET_GCODE_VARIABLE MACRO=MAIN VARIABLE=temp2 VALUE={temp1} # Update temp2 in macro MAIN
[gcode_macro MAIN]
variable_temp2: 0
gcode:
{% for count in range(1, 10) %}
_MINOR # Call macro _MINOR to get live temperature
M118 {count} - Temperature is {temp2} C. # Echo temp in console
G4 P1000 # Wait 1 second
{% endfor %}
M118 End of test.
I can do what I want, the macro MAIN does not get the updated extruder temp from macro _MINOR.
Why is there a SET_GCODE_VARIABLE command if it does not work or what do I miss here?
I even tried SET_GCODE_VARIABLE from within the loop to save temp1 to temp2.
And even printer[“gcode_macro _MINOR”].temp1 does not present an updated value.
The results in the console always look like this:
10:32 End of test.
10:32 9 - Temperature is 0 C.
10:32 _Minor Call 23.01 C.
10:32 8 - Temperature is 0 C.
10:32 _Minor Call 23.01 C.
10:32 7 - Temperature is 0 C.
10:32 _Minor Call 23.02 C.
10:32 6 - Temperature is 0 C.
10:32 _Minor Call 23.01 C.
10:32 5 - Temperature is 0 C.
10:32 _Minor Call 23.01 C.
10:32 4 - Temperature is 0 C.
10:32 _Minor Call 23.02 C.
10:32 3 - Temperature is 0 C.
10:32 _Minor Call 23.02 C.
10:32 2 - Temperature is 0 C.
10:32 _Minor Call 23.02 C.
10:32 1 - Temperature is 0 C.
10:32 _Minor Call 23.01 C.
My first test with delayed_gcode macros neither worked as expected, so I hope there is a (better) solution available at all.
[gcode_macro MAIN]
variable_temp2: 0
gcode:
_MINOR
M118 0 - Temperature is 0 C.
G4 P1000
_MINOR
M118 1 - Temperature is 0 C.
G4 P1000
_MINOR
M118 2 - Temperature is 0 C.
G4 P1000
_MINOR
M118 3 - Temperature is 0 C.
G4 P1000
M118 End of test.
(It evaluates as a template to the text upon call, and then this text will be executed, so it can’t show any changes inside the template).
(Any updates will be shown in the next call to MAIN).
The next idea would sound like: “I can do a loop by the recursion!”
[gcode_macro _MINOR]
variable_temp1: 0
gcode:
{% set COUNT = params.COUNT|default(0)|int %}
{% set temp1 = printer.extruder.temperature | float %} # read live extruder temperature
M118 _Minor Call {temp1} C. # Echo temp to show what macro has what value
SET_GCODE_VARIABLE MACRO=MAIN VARIABLE=temp2 VALUE={temp1} # Update temp2 in macro MAIN
{% if COUNT < 10 %}
MAIN COUNT={COUNT}
{% endif %}
[gcode_macro MAIN]
variable_temp2: 0
gcode:
{% set COUNT = params.COUNT|default(0)|int %}
_MINOR COUNT={COUNT + 1}
M118 {count} - Temperature is {temp2} C. # Echo temp in console
G4 P1000 # Wait 1 second
M118 End of test.
Alas, it is also invalid, because Klipper forbids recursions.
So, the closest that you can get Jinja2 + G-code is to define 10 macros and call them conditionally, I guess.
The loop in my MAIN macro is only an example and not the actual goal.
The M118 commands and their respective outputs in the console are just for debugging reasons and to show how Klipper is processing this whole procedure.
With a [delayed_gcode] macro I received ten consecutive outputs from MAIN and then afterwards only two from MINOR although MINOR was called at least 9 times from within the loop and should consecutively follow eacht MAIN output line.
I wanted to get it working without [delayed_gcode] to not block additional commands after the loop in MAIN while MINOR might still be running in the background.
I’m currently testing this however…
The main goal is to optimize my nozzle cleaning macro before touch probing on my printer.
I don’t know how.
However I wanted it in my START_PRINT macro so it is automated.
But although the main cleaning macro is working in an isolated point of view it seems to end there as I can’t proceed with the main macro to finish the printing requirements.
Looks like I need another macro for passing some parameters as you can’t hand over parameters to the delayed_gcode macro.
Each macro that needs updates, create a toplevel macro for the user, and then split the actual implementation into “blocks” sub-macros that need the current state of the printer and toplevel macro threads them together.
to pass by value between blocks use parameters to sub-macros.
to return values or propagate computed values use gcode variables.
@3dcoded I installed DynamicMacros and tested it but it looks like it won’t help me on my topic.
Updating variables does not work in loops and even if I call a separate macro to save back a variable to the main macro it does not reflect the updated value.
Maybe I overlooked something but that is my current state.
So my only way now seems to run a more or less static (maybe with a variable counter dependant on used filament/printing temperature) loop to perform cleaning operations and hope it is sufficient enough.
Unfortunately loops are the main limitation of receiving variable updates. You could try recursion as suggested here or go with the static loop approach.
It would help if you provided an exact demonstration of what you want to accomplish, instead of an artificial example that is very unclear about its goal.
The problem with your example implementation is that you don’t have enough macros. The output needs to be separate so that it will be templated at output time, instead of when the loop is constructed.
[gcode_macro _MINOR]
variable_temp1: 0
gcode:
{% set temp1 = printer.extruder.temperature | float %} # read live extruder temperature
M118 _Minor Call {temp1} C. # Echo temp to show what macro has what value
SET_GCODE_VARIABLE MACRO=MAIN VARIABLE=temp2 VALUE={temp1} # Update temp2 in macro MAIN
[gcode_macro _OUTPUT]
gcode:
M118 {params.COUNT} - Temperature is {printer['gcode_macro MAIN'].temp2} C.
[gcode_macro MAIN]
variable_temp2: 0
gcode:
{% for count in range(1, 10) %}
_MINOR # Call macro _MINOR to get live temperature
_OUTPUT COUNT={count} # Output current temperature
G4 P1000 # Wait 1 second
{% endfor %}
M118 End of test.
Example output:
2:04 PM MAIN
2:04 PM _Minor Call 18.75 C.
2:04 PM 1 - Temperature is 18.75 C.
2:04 PM _Minor Call 18.67 C.
2:04 PM 2 - Temperature is 18.67 C.
2:04 PM _Minor Call 18.62 C.
2:04 PM 3 - Temperature is 18.62 C.
2:04 PM _Minor Call 18.71 C.
2:04 PM 4 - Temperature is 18.71 C.
2:04 PM _Minor Call 18.68 C.
2:04 PM 5 - Temperature is 18.68 C.
2:04 PM _Minor Call 18.64 C.
2:04 PM 6 - Temperature is 18.64 C.
2:04 PM _Minor Call 18.68 C.
2:04 PM 7 - Temperature is 18.68 C.
2:04 PM _Minor Call 18.73 C.
2:04 PM 8 - Temperature is 18.73 C.
2:04 PM _Minor Call 18.75 C.
2:04 PM 9 - Temperature is 18.75 C.
2:04 PM End of test.
Yeah you are right.
The topic is about a nozzle cleaning macro.
The crucial point however is a temperature dependant nozzle swipe routine.
Lets say you heat the hotend to 200 °C and then turn off the hotend and swipe the nozzle over a brush until to a particular temperature.
Under normal circumstances I’d use a loop that performs a swipe sequence repeatedly until the temperature meets the exit condition.
This is not possible in Klipper/Jinja2.
I now rebuilt it with a static macro and a loop with filament dependant counters.
Of course I can create a loop that always runs a separate macro the checks temperature and if this is still too high performs a single swipe sequence.
But how to exit the loop in the calling macro if I can’t send back a exit trigger?
Hopefully this somehow gives you a better understanding.
Ah I see. As long as the exit condition is not met in the called macro it “consumes” time by either waiting as in your example or by doing real work (moving nozzle back and forth over a brush).
If the exit condition is met the called macro just consumes milliseconds and the remaining loop ends in quite no time.
This is really clever using the given boundaries/limitations.
Will try to test this tomorrow.
Today I calculated that a swipe sequence takes about 0,3 seconds or less if the speed is higher.
So I can determine the maximum of needed time for the worst case the loop has to cover.
Rather than passing variables you can store them and retrieve them from any macro
Here’s my variable storage for my custom Idex printer. I can get the current state from any macro.