[SOLVED] Print Start Macro : accessing tool diameter Nozzle Offsets and PID params

I come from Marlin, and I’m missing M81x, M301, M851, and M500.

Until now, I had as many printers defined in Cura than I have print heads (indexed quick change tools, DIY until recently, now Stealthburner) ; in the start gcode, I was calling user gcode macros (M810-M817 for eight heads), and these gcode macros were setting PID parameters and nozzle Z-offsets. It’s been working great for years.

I now want to do something similar with Klpper. But these gcodes don’t exist ! And I don’t want to calibrate the machine everytime I swap the tools !

I defined a START_PRINT macro with 3 parameters (1st layer tool temperature, 1st layer bed temperature, and nozzle diameter) ; these parameters are from this list Settings and replacement patterns . Works fine for temperatures (preheat).

In Cura, {machine_nozzle_size} can be altered easily with the “Printer Setttings” plugin by fieldOfView (easier than the printer configuration dialogs)

On the printer side, I’m not sure how to alter the tool PID, probe z_offset, and nozzle diameter.

Found and used a macro (“SEARCH_VARS”), and got interesting variables ;

This is what I plan to add to my start gcode (there will be more later, this is just for one toolhead only).

Is this code correct ?

{% set nozzle_diam = params.NOZZLE_DIAM|float %}   # Cura {machine_nozzle_size}
{% if params.nozzle_diam  == 0.4 %}
  printer.configfile.settings.extruder.nozzle_diameter = 0.4
  printer.configfile.config.extruder.nozzle_diameter = 0.400
  printer.configfile.settings.extruder.pid_kp = 15.625
  printer.configfile.settings.extruder.pid_ki = 0.628
  printer.configfile.settings.extruder.pid_kd = 15.625
  printer.configfile.settings.bltouch.z_offset = 2.150
{% endif %}

The PID parameters are those computed by Klipper, not Marlin.

[EDIT] I’m not familiar with jinja. But I suspect I forgot some "

{% set ... %}

Is this better ?

  {% if params.nozzle_diam  == 0.4 %}
    {% set extruder.nozzle_diameter = 0.4 %}
    {% set extruder.nozzle_diameter = 0.400 %}
    {% set extruder.pid_kp = 15.625 %}
    {% set extruder.pid_ki = 15.625 %}
    {% set extruder.pid_kd = 15.625 %}
    {% set bltouch.z_offset = 2.150 %}
  {% endif %}

The PID values can’t be the same. Also not with Marlin.

For example, mine are this:

#*# control = pid
#*# pid_kp = 15.521
#*# pid_ki = 0.609
#*# pid_kd = 98.949

As of current these settings cannot be changed during runtime. They require a Klipper restart to become effective

Exactly what I didn’t want to hear ! This is a huge problem, as my whole workflow relies on easy and quick tool changes (5 years or so). Even had a dedicated custom menu in Marlin and buttons in Octoprint.

If I understand correctly, there’s an equivalent (more or less) to M851, it is SET_GCODE_OFFSET.

But nothing similar to M301. There is an old FR for M301 on Github, but we are probably very few needing such a feature : Request - Set / Adjust PID parameters via gcode · Issue #1167 · Klipper3d/klipper · GitHub

What do you think of running PID_CALIBRATE for each and every print ? After all, the auto PID takes 4 minutes, much less than the time I let the printer alone, heating up and stabilizing… [EDIT] and bed + chamber require longer than that.

There still is the ‘nozzle_diameter’. Not sure what it does exactly. I read this : " As such nozzle_diameter, filament_diameter and max_extrude_cross_section are used to determine a sane amount of extrusion that would be expected for typical printing. If this “sane amount” is exceeded, Klipper will error out and give you the notification that something unwanted / unexpected has happened."

Apart from sanity check, I don’t see why the printer, or even the slicer, has to know about the nozzle (Cra uses it to set default line width). Their job is to puch some volume of filament, according to filament diaameter, line width, layer height and instantaneous speed (not speaking of LA). So, setting insanely high values could trick Klipper ?

Looking for workarounds… and advice before I crash the bed and overheat the hotend !

The smart solution would be coding a custom command or Gcode. I can easily code such things for Marlin, definitely not for Klipper in Python/Jinja. Too bad I didn’t take the new tools/languages train !

Sorry for flooding !

Not sure why you would do this. If you have known PID values for each tool, then you would just create config files for each one with those values and use an include. When you want to change tools, you simply change a couple lines in your config, save and restart. It would take maybe 15 seconds to comment out one tool config and uncomment a different one.

The topic is about setting everything from the slicer side, and not touching anything on the Klipper side ! This is what I’ve been doing for years using Cura/OctoPrint/Marlin.
Until now, I didn’t use the nozzle diameter in the slicer : I was using multiple printer definitions with various start gcode. I now want the slicer nozzle size parameter to set everything on the printer.
Editing the printer configuration file is not an option : my previous setup didn’t need any Marlin recompilation, nor any action from the user, except selecting the “virtual” printer in Cura…

I think the problem is now solved. Here’s the start gcode :

# Cura         : http://files.fieldofview.com/cura/Replacement_Patterns.html
# Prusa Slicer : https://help.prusa3d.com/article/list-of-placeholders_205643
# Super Slicer : https://github.com/supermerill/SuperSlicer/wiki/Macro-&-Variable-list
# Cura Start G-code :
# START_PRINT NOZZLE_DIAM={machine_nozzle_size} BED_TEMP={material_bed_temperature_layer_0} TOOL_TEMP={material_print_temperature_layer_0}

[gcode_macro PRINT_START]

  START_PRINT {rawparams}

[gcode_macro START_PRINT]


  {% set bed_temp = params.BED_TEMP|float %}         # Cura : {material_bed_temperature_layer_0}
  {% set tool_temp = params.TOOL_TEMP|float %}       # Cura : {material_print_temperature_layer_0}
  {% set nozzle_diam = params.NOZZLE_DIAM|float %}   # Cura : {machine_nozzle_size}


  {% if nozzle_diam==0.2 %} # TODO :
    RESPOND MSG="0.2mm toolhead selected"
    RESPOND TYPE=error MSG="0.2mm toolhead not defined, aborting"

  {% elif nozzle_diam==0.3 %}
    RESPOND MSG="0.3mm toolhead selected"

  {% elif nozzle_diam  == 0.4 %}
    RESPOND MSG="0.4mm toolhead selected"

  {% elif nozzle_diam  == 0.5 %}
    RESPOND MSG="0.5mm toolhead selected"

  {% elif nozzle_diam  == 0.6 %}
    RESPOND MSG="0.6mm toolhead selected"

  {% elif nozzle_diam  == 0.8 %}
    RESPOND MSG="0.8mm toolhead selected"

  {% elif nozzle_diam  == 1.0 %}
    RESPOND MSG="1.0mm toolhead selected"

  {% elif nozzle_diam  == 1.2 %}
    RESPOND MSG="1.2mm toolhead selected"

  {% else %}
    RESPOND TYPE=error MSG="unknown toolhead, resetting>"
  {% endif %}

  M104 S{tool_temp}              # heat tool, don't wait
  M140 S{bed_temp}               # heat bed, don't wait
  M109 S{tool_temp}              # wait for tool temp

  # while the bed is heating up :
  PID_CALIBRATE HEATER = extruder TARGET = {tool_temp} 

  M117 Homing...
  G21                            # metric
  G90                            # absolute coordinates
  M82                            # absolute extrusion mode
  G92 E0                         # set extruder to zero
  G28                            # auto home
  G0 X10 Y10 Z5 F20000           # park front left while heating for cleaning : mm/mn

  # M117 Heating...               # LCD = Heating
  M190 S{bed_temp}               # wait for bed temp

  #M117 Leveling...              # LCD = Bed Leveling
  # Marlin G29 macro
  # M117 Cleaning...             # LCD = Cleaning
  G1 X0 Y0 Z5 F20000
  G0 X5 Y10 Z0.2 F9000           # move in 5x10mm from edge and up 0.2mm
  G1 Y100 E12 F500               # priming : extrude 12mm filament 100mm long
  G0 Y180 F4000                  # quick wipe 80mm long
  G0 Z2 F3000                    # move up 2mm to prevent scratching

  # M117 Printing...             # LCD = Printing...


[gcode_macro PRINT_END] # alias

    END_PRINT {rawparams}

[gcode_macro END_PRINT]


  G27           # park
  M84           # disable motors
  M104 S0       # hotend off, continue
  M140 S0       # bed off, continue

On the slicer side, there’s just :

START_PRINT NOZZLE_DIAM={machine_nozzle_size} BED_TEMP={material_bed_temperature_layer_0} TOOL_TEMP={material_print_temperature_layer_0}

nothing more. Nozzle diameter is set with the “Nozzle Diameter” field, in the Printer Settings (Plugin)


M109 S{tool_temp}



probably is not good. Chances are it could trigger some thermal runaway ! Will reverse the order.

I’m still not clear on why you would do a PID calibration in the start gcode. I don’t believe the calibrated values are applied until they’re saved in your config and the printer is restarted.

Please read the answer by @Sineos (3rd post).

There’s no problem doing this, no toolhead will be harmed and no time will be lost, the calibration being done while the bed and the chamber are heating up, and it 100% solves my problem. Not as smart than with Marlin, but in the end it does the exact same job.

In Marlin, I had this (from a Configuration_adv.h" where not all settings were up to date) :

#define STARTUP_COMMANDS  "M810 M301 P00.00 I0.00 D00.00 | M851 Z-0.00 | M500 | M117 E3D V6 0.2mm\n"      \
                          "M811 M301 P20.45 I1.65 D63.24 | M851 Z-0.00 | M500 | M117 E3D V6 0.3mm\n"      \
                          "M812 M301 P16.25 I1.08 D60.95 | M851 Z-1.75 | M500 | M117 E3D V6 0.4mm\n"      \
                          "M813 M301 P18.22 I1.23 D67.52 | M851 Z-0.00 | M500 | M117 E3D V6 0.5mm\n"      \
                          "M814 M301 P12.85 I0.67 D61.80 | M851 Z-1.97 | M500 | M117 E3D V6 0.6mm\n"      \
                          "M815 M301 P11.48 I0.46 D71.47 | M851 Z-0.00 | M500 | M117 E3D Volcano 0.8mm\n" \
                          "M816 M301 P15.25 I0.86 D67.77 | M851 Z-0.00 | M500 | M117 E3D Volcano 1.0mm\n" \
                          "M817 M301 P12.90 I0.67 D62.06 | M851 Z-0.00 | M500 | M117 E3D Volcano 1.2mm\n"

Also not sure values will be in use for the current cession as they are for the bed mesh.
If they are not, I’ll have to go back to Marlin + OctoPrint.

(BTW, the start gcode is a bit different, refactorization, reordering, and fixed a few typos…)

1 - in printer.cfg, PID that generates ±5°C oscillations
2 - launch start gcode : the PID autotune algorithm does its thing
3 - nothing happens, as the temperature never stabilizes (in Marlin it would be a KILL after some time) ; hitting the Cooldown button has no effect (it waits in an infinite loop I guess…) ; have to E-Stop

@jakep_82 you are right, it means that Klipper does not use the PID parameters it just computed, unlike Marlin. PID_CALIBRATE is not M303 with the U flag :frowning:

Not being able to select a tool is a severe limitation. Last resort : selecting heads that can live with the same PID values, without too much oscillation.

It’s not clear to me exactly how you have your hardware set up, but you can define multiple extruders in Klipper and activate them with macros. See the link for a sample.

One extruder, multiple swapable hotends. The pins for the motors, heater, fans and thermistor allways remain the same. Well known ones are the Voron Stealthburner and the Voron Afterburner. Now using the Stealthburner after years with my own design.

Defining multiple extruders with the same pins definitions is not allowed. All my attempts failed. step_pin, dir_pin, enable_pin cannot remain the same accross extruder definitions.

It seems Klipper is made for Idex or tool changers, not for Voron SB/AB style hotends. And it is what I’m using.

How it works (Afterburner), except the guy does tell nothing about the dark side of this story : changing the tool head means changing the z offset and the PID settings in printer.cfg (I will not do that : quick change means changing quickly) :

A pain with Klipper because of the slim gcode set, can be automated with other firmwares with standard REPRAP gcodes.

For me the most simple solution would be to work with include statements:

[include hotend1.cfg]
#[include hotend2.cfg]
#[include hotend3.cfg]

Put the hotend relevant stuff in there and the only action would be to comment one and uncomment the other then restart Klipper.

1 Like

Yes, thanks for the suggestion : makes things easier. But still would have to do some editing, and I want to avoid that…

On Github, there are some FR for extruder sharing, some from 3 or 4 years back. Shared motor for extruders · Issue #2171 · Klipper3d/klipper · GitHub

@Sineos : as a Klipper Collaborator, you could answer this. You wrote “these settings cannot be changed during runtime”. Does this mean that there is just no access for the end user, or that the object model does not allow it ? In other words could a user coded G-Code or extended G-Code command do it at runtime ? (coding a custom G-Code in Marlin is pretty easy…) ; not being able to share a stepper looks more like a sanity check, and as long as there’s no concurrent access to the ports, I don’t see any problem (it’s not an IDEX or some dual head)…

Worth learning “a bit” more Python and Klipper internals ?

At worst, I could create macros that issue shell commands, alter the cfg, and restart Klipper, and have access to these macros as buttons in KlipperScreen. Found somewhere a plugin or something that can issue shell commands, but cannot have root access. I just tested : printer.cfg can be edited using nano without sudo-ing. Could sed be my friend ? Then the workflow would be : 1-replace tool cardridge 2-tap the macro on KlipperScreen 3-start printing?

For easier editing, you could create a wrapper config file which just loads the correct tool config.


[include tool-loader.cfg]

other config


[include tool1.cfg]

Then your custom command can just overwrite the “tool-loader.cfg” file with the current tool to load and do a restart.


Of course !!!

But why do it simple when it can be complicated…
(was thinking regexes because never been comfortable with, and it could be some training ; sed artists are amazing and their scripts looks like ASCII art)

The link to the shell command extension :

(will definitely start with your idea ASAP on the test RasPi ; for now the chamber thermistor decided to die, and a 4-way accelerometer project to complete, test, and upload to GitHub)

Could likely be done. So far changing such items during runtime has not found the maintainer’s approval

Thanks for your answer. So it’s definitely worth browsing the sources and learning Klipper more in depth.

Also been dreaming of tool cardridges with RFID recognition and auto settings for a long time.

Follow up…
Had an idea : why couldn’t we measure the delta offset relative to a properly tuned toolhead ?
Google → good news : already done !

Testing rig (will be machined later). Hopefully I’ll be able to adapt the code to my needs…

(the pictures are self explanatory)

1 Like

If you want it to do that way, the nozzle always have to be perfectly clean.

Try the microswitch without the steel sheet lever. It works way more accurate then.

Have it that way for my two x axes and the two z axes.

Of course, it has to be clean.
At some point was thinking of direct contact between the nozzle and a “sensor”. Bad idea because of oozing.
Don’t get me wrong ! This is just for testing. The endstop will be replaced with a all metal industrial adjustable one, with no lever (made for machine tools). Have a bunch of, but can’t remember where they are !!! Spent 2 hours browsing through contactors, relays, VFDs and other stuff with no success. Reason why there’s this crappy REPRAP module. Crappy but good enough for learning.
Just for testing : most difficult part is code. Been a C/C++ fanboy since Turbo C. What a mistake ! A line of Python can do the same than 1000 lines of C/C++. Currently banging my head against the walls between two facepalmings.

(but still, we wouldn’t need more and more powerfull computers if we were still using '80s languages and processors :slight_smile: )

1 Like

Finally figured how to do exactly what I wanted in the first place.

Offsets are stored in a file.

in printer.cfg :


filename: ~/printer_data/config/variables.cfg

in variables.cfg :


and so on...

in the print_start gcode macro, the values are read using :


and so on...

Then some conditionals on the nozzle diameter sent as parameter by the slicer, with basic math in the start_gcode macro (offset relative to the offset stored in printer.cfg for the “reference” toolhead), and finally a call to SET_GCODE_OFFSET with the results

Initial problem 100% solved.

Now struggling with code for automatic offset the way it’s done for the Klicky probe, but using a BLTouch.