[gcode_macro TEST]
variable_total : 0
gcode:
{% set points = range(1,4) %}
{% for point in points %}
{ action_respond_info("Calling probe on: %i" % (point)) }
TEST_PROBE POINT={point}
{% endfor %}
{ action_respond_info("Total is: %s" % (printer['gcode_macro TEST'].total)) }
[gcode_macro TEST_PROBE]
gcode:
{ action_respond_info("Probing: %i" % (params.POINT | int)) }
SET_GCODE_VARIABLE MACRO=TEST VARIABLE=total VALUE={printer['gcode_macro TEST'].total + 1}
will output:
$ TEST
// Calling probe on: 1
// Calling probe on: 2
// Calling probe on: 3
// Total is: 0
// Probing: 1
// Probing: 2
// Probing: 3
Sad times. Normal intuition about how program flow works does not work in Jinja/klipper. Macros execute completely before the sub-routines called from inside the macro are invoked. Then those are invoked in-order and so on. But where there is a will there is a way⦠a deep dark way
We can pause the print and use delayed_gcode to monitor the probing until its completed, then resume the print to make use of the result:
$ TEST
// Calling probe on: 1
// Calling probe on: 2
// Calling probe on: 3
// Probing: 1
// Probing: 2
// Probing: 3
// action:paused
// Total is: 3
// action:resumed
This has pros and cons. One of the major cons is that PAUSE doesnāt stop macro execution, it only stops execution of the GCode file being printed. So you would have to call TEST directly from a line in the gcode file for this to work as expected during a print. The other major downside is it gets very complicated to reason about when coding.
Thanks so much for replying to my question about if the SET_GCODE_VARIABLE and SAVE_VARIABLE commands in your example above could be used as global scope variables. I deleted my question shortly after writing it (before you responded), because I found another possible way to do this from another of your macro examples using the save_variables command. I will try out that command to see how well it would work. As you say, it is a shame that a macro is not able to do a bed probe and pick up the result in the same macro. That would have made my life a lot simpler.
You just cant get around the execution order problem. Templates are evaluated completely before any sub-routines are run. If a macro is going to provide data you are basically out of luck. You might be better off doing it in Python as a Klippy extension.
Your gcode macro test above is fantastic! It contains all the tricky stuff that I would not have been able to figure out on my own from this tutorial. Using it as a template (since the syntax is greek to me), I was able to create a set of macros that, combined with a gcode print file, will do what I need to create a bed mesh generator with backlash compensation averaging. I average 4 probes, approaching the probe point from 4 directions. Then I will assemble a 15 x 15 point output to the console to cut and past into the printer.cfg. There is still a bit of hand work to process it, but it is manageable. The proof of principle is now done. I will share the code (clunky as it is) once I have it running perfectly. I only have one error that I can not figure out and have yet to find an example. I am trying to concatenate a series of strings to a single string of 15 probe results to print to the console. You can see what I am doing just from the example gcode of one probe point:
G28 Home
G90 ;absolute
G1 Z2 F3000 ;get close to bed
;
GOTO_PROBE_POINT X_Pos=0.001 Y_Pos=134.959
;Probe sequence
PROBE_BED X_Dir=1 Y_Dir=1
SAVE_Z
PROBE_BED X_Dir=-1 Y_Dir=-1
SUM_Z
PROBE_BED X_Dir=1 Y_Dir=-1
SUM_Z
PROBE_BED X_Dir=-1 Y_Dir=1
AVG_Z Probe_Count=4
SAVE_TO_LINE Repeat=15
PRINT_LINE
I get this error: !! Error evaluating āgcode_macro SAVE_TO_LINE:gcodeā: TypeError: āstrā object cannot be interpreted as an integer
Here are all the relative macros that make it work. The error is just in the SAVE_TO_LINE macro. Everything else is working.
Thank you. That was a stupid error on my part. I was looking for the error on the wrong line. I took a more systematic debug process now. After dozens of attempts at still trying to append text to a string variable, I have made no progress in getting past this error message:
The same command can save numbers, but seems to choke on strings. Perhaps I do not understand some syntax difference for strings. It is the only thing now keeping me from printing.
Here is the stripped down and expanded debug code for the macros in question:
[gcode_macro CLR_LINE]
variable_row_line : ""
gcode:
#SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE="" # this blows up with internal error unexpected EOF
[gcode_macro SAVE_TO_LINE]
gcode:
{% set TEMPA = printer['gcode_macro CLR_LINE'].row_line %}
{% set TEMPB = printer['gcode_macro SAVE_Z'].zavg|round(5)|string %}
{% set TEMPC = TEMPA + ", " + TEMPB %}
M118 { TEMPC } #debug -- good to here
SET_GCODE_VARIABLE MACRO=CLR_LINE VARIABLE=row_line VALUE={ TEMPC } # this errors with malformed command
I really appreciate your help with learning how to do this.
and the output is: āOUTPUT: {āXYZā: ā1ā}ā ⦠the array⦠again⦠not what im looking for.
I suppose one way you could look at it as I want to pass a parameter name only to TEST and not a value along with it⦠so in sudo code something like this:
Thanks so much for taking the time to create this resourceā¦looking forward to more.
Ps. I found Chat gpt to be somewhat knowledgeable about klipper macros, though often uses python rather than jinja. You might find it useful in developing new sections quickly.
Itās funny you mention that. I did that too out of pure curiosity. My impression was that ChatGPT sounds like itās knowledgeable about Klipper macros, but it is actually not. I could not get it to produce even a simple functional macro even after multiple attempts to coach it. Iāve found that with tentpole programming languages like Python and Perl, ChatGPT is capable of producing working code, though will often have bugs and errors. As long as you know the language well enough to recognize them, or at least to formulate prompts that will get ChatGPT to recognize them, you can usually run its generated code through multiple iterations to end up with something functional that does what you intended. Not so with Klipper macros, at least not in my admittedly superficial experience. In addition to mixing python and jinja like you mentioned, it will also create āpseudo-jinjaā by recasting python expressions in a jinja-like construction, even though jinja has no corresponding syntax or function. On top of that, Klipper uses a somewhat non-standard implementation of jinja and ChatGPT seems completely unaware of that.
So, all that to say, for all the lurkers out there, please do not try to generate Klipper macros with ChatGPT and then come in here wondering why they donāt work.
Last week I had questions from somebody who wanted to make a 15 foot (4.5m) high statue that consisted of interlocking 3D printed PLA segments that had a poured concrete core (which would mass 6 tonnes). He worked it all out with ChatGPT but he wanted peopleās opinions about his assumptions. He got angry when told to talk to somebody who made concrete statues/columns for a living and argued that what came out of ChatGPT seemed right.
I havenāt used ChatGPT myself but it seems to produce very confidence inspiring answers.
Ha, yeah interesting to play with. I did the same, showed it some proper macros and it seamed to get its logic together at least partially. As you said, some parts might be right but you need to know what is. It helped me get a randomizing function going. Above all, as you said, it sounds very confident. Yes, good point, donāt go trying to pull fully working macros out of Chat-GPT!!! Maybe next iteration.
Macros in Klipper are based on a template generation language. So they are fed a frozen state of the printer and then evaluated against that state. The evaluation of the entire macro ends before the first GCode instruction executes.
So in your case, last_query is empty on the first run. On the second run it contains the results from the first run. Or put another way: the if block runs before QUERY_PROBE.
You could likely resolve this by putting the if blocks in a separate macro and calling that. That new macro call would execute after the QUERY_PROBE call and get the state of the printer after the probe completes.
[gcode_macro START_PRINT]
description: Gcode ran at the start of each print. This is called by the slicer to make per machine config easier and cross_slicer compatible.
gcode:
{% set BED_TEMP = params.BED_TEMP|default(0)|float %}
{% set EXTRUDER_TEMP = params.EXTRUDER_TEMP|default(0)|float %}
{% if EXTRUDER_TEMP == 0 %}
CANCEL_PRINT
{action_raise_error("Your extruder temperature was not received from your slicer or was incorrectly set. Check that your code and parameters are accurate and restart the print. It may be helpful to look at the start gcode section of the slicer generated gcode file.")}
{% else %}
{% if BED_TEMP == 0 %}
{action_respond_info("Your bed temperature was not received or is set to 0. If this is not intended, cancel the print and check that your code and parameters are accurate and restart the print.")}
{% endif %}
CLEAR_PAUSE
M117 Preparing to print
{% if 'bed_mesh' not in printer %}
{action_respond_info("[bed_mesh] is not enabled. Skipping load profile")}
{% else %}
BED_MESH_PROFILE LOAD=default
{% if bed_mesh.profile_name is none %}
{action_respond_info("Default mesh profile does not seem to be calibrated and will not effect this print. Please calibrate the mesh to apply it for next time")}
{% else %}
{action_respond_info("Loaded default mesh")}
{% endif %}
{% endif %}
M140 S{BED_TEMP} ; set bed temp and dont wait
G90 ; use absolute coordinates
M83 ; extruder relative mode
M220 S100 ; reset speed
M221 S95 ; THIS GCODE LOWERS EXTRUSION RATE TO .95 ON PRINT START. IF THIS CAUSES ISSUE3S CHANGE IT.
G28 ; home all with default mesh bed level
M117 Heating - Careful
M190 S{BED_TEMP} ; wait for bed final temp
M104 S{EXTRUDER_TEMP} ; set extruder final temp and dont wait
M109 S{EXTRUDER_TEMP} ; wait for extruder final temp
M117 Priming nozzle
G92 E0.0; reset extrusion distance
G1 X-80 Y-100 Z0.3 F4000; go to prime arc start
G3 X80 Y-100 I80 J100 E15 F1500
G92 E0.0
G2 X-80 Y-100 I-80 J100 E10 F1000
G92 E0.0 ; reset extrusion distance
G10;
G92 E0.0 ; reset extrusion distance
G1 Z10 F1000;
M117 Print started
{% endif %}
Can anyone help explain why this macro only returns:
Error evaluating 'gcode_macro START_PRINT:gcode': jinja2.exceptions.UndefinedError: 'bed_mesh' is undefined
Iām not really too sure what the issue is. Spent quite a while reading documentation, and still confused what is going wrong.
Thanks for any help.
edit: to clarify, Itās hanging on the if statement that tests if bed_mesh is not in the printer object. The purpose of this is to detect whether or not bed_mesh has been configured and skip the mesh load if it hasnāt. However, It just keeps saying that bed_mesh is undefined. Which is weird because Iām not testing the value of bed_mesh but rather whether or not the string ābed_meshā appears in the printer object array.
Sorry I will start a new thread next time. And yes I do see that now. I was tired while trying to fix it so it didnāt occur to me. Why would bed_mesh be undefined in this scenario? If I was reading moonraker docs correctly, the profile_name should return the name of the profile loaded in this case ādefaultā or null if there is no saved profile. Am I missing something else?