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

In this solution, are you setting a generic PID that works for the tool heads or how do you go about swapping that?

Nope !
And I moved on.
Please keep in mind I started with Klipper less than 1 month back, and it’s not easy.
Finally understood why i was unable to create a multi tool machine : syntax error.
I defined a multitool machine with multiple tools (yes !!!), all sharintg the same stepper. Just had to comment a sanity check in tmc_uart.py (lines 116 to 118). Now all tools can share the same stepper.
Each tool has its own PID and offset.

But for now I’m on the auto Z offset for bed slinger.

For now, it’s made of printed parts. Don’t like it. It’s a prototype, designed for 3 printing and conventional machining. Will be turned and milled. The prototype probe is based on the Voron Z probe.

Some pics.

Machining the plunger :

The probe, on the “Super Tornado” :

Probing the nozzle :

Probing the BLTouch :

A [probe] was created : it’s a knobprobe, a stripped down version of the Klipper Z probe. 2 probings, one for the BLTouch, a second one for the nozzle. There’s another offset, but the maths tells us that it will be cancelled later, and shouldn’t be taken in account.
(sorry for my bad english, hope the reader will understand)

See here : GitHub - TypQxQ/KnobProbe_Klipper: Using KnobProbe on Klipper

[EDIT] I’m not the author of knobprobe !!!

When the project is completed, I will upload the results on my Git, and shot a short (and crappy) video.

@bkahl
Definitely works. The machine becomes a multi head printer. The parameters are set using ACTIVATE_EXTRUDER command. Then, M104 defaults to the current tool head, and uses the PID parameters defined for the tool in use ; no need to add Txxx in the command line (no need for overriding the command - seems to me Klipper allows it ?). Solved (for now, could discover some quirks…).
About the nozzle offset, the device works fine. A “reference” tool is set with its own offset that goes into printer.cfg, and a reference offset is measured by the “knobprobe” and stored. Then, when using another tool, it is also measured, and the difference is applied using Z_OFFSET_APPLY_PROBE. Solved.

The only remaining problem is in the GUI. Don’t know how to hide the unused “virtual” tools from the interface. The current tool is easily identifiable, as it is the only one with a set temperature. But it’s a bit of a mess, in particular with KlipperScreen.

Also, when searching for updates, the hacked file is flagged “dirty”, and the lines have to be commented again and again if Klipper updates itself.

Doing this is much more straightforward than defining multiple printers in Cura ! Because in Cura, the machine related settings are lost when switching from one nozzle (machine) to another. Imagine having all your parameters optimized for a 0.5mm nozzle (machine…) with PETG, and making comparisons with ABS/0.8mm nozzle (machine…) while editing CAD files. Have to go through all settings. Hundreds of them.A real pain I’ve been living with for years. Should be over now. Thanks to Klipper.

How it works…
A toolhead / Z-probe (BLTouch for example) is calibrated the usual way.
Then it is calibrated using the blue thingy with the endstop, with the macro "REF_Z_OFFSET :

(“probe” is the BLTouch, "KnobProbe " is the endstop with the plunger, similar to the Voron Z probe)

11:53
REF_Z_OFFSET
11:54
KnobProbe triggered at z=-0.340000
11:54
KnobProbe triggered at z=-0.339687
11:54
KnobProbe triggered at z=-0.339687
11:54
Result is z=-0.339792
11:54
probe at 283.999,150.000 is z=1.381250
11:54
probe at 283.999,150.000 is z=1.383125
11:54
probe at 283.999,150.000 is z=1.380937
11:54
Result is z=1.381771
11:54
echo: Reference Tool Z Knob Offset : probe_z = 1.381771, knobprobe_z = -0.339792, ref_knob_z_offset = 1.721562

We get an offset, relative to the top surface of the blue device : ref_knob_z_offset. This value is stored in the file “variables.cfg”.

In the start gcode, a macro is called (“CUR_Z_OFFSET”). It does the same things, and will store the difference relative to the ref_knob_offset.

11:54
CUR_Z_OFFSET
11:54
KnobProbe triggered at z=-0.336562
11:54
KnobProbe triggered at z=-0.335937
11:55
KnobProbe triggered at z=-0.335937
11:55
Result is z=-0.336146
11:55
probe at 283.999,150.000 is z=1.386562
11:55
probe at 283.999,150.000 is z=1.385937
11:55
probe at 283.999,150.000 is z=1.385312
11:55
Result is z=1.385937
11:55
echo: Current Tool Knob Z Offset : probe_z = 1.385937, knobprobe_z = -0.336146, cur_knob_z_offset = 1.722083
11:55
echo: gcode_offset = -0.000521

The difference between ref_z_offset and cur_z_offset is the difference between our toolheads nozzle z position. It will be used in the start gcode to offset the gcode : it will be the parameter for SET_GCODE_OFFSET.

The machine has to be calibrated one time only. Then whatever is the tool/nozzle, the offset will be set automatically.

In the terminal output the difference is tiny (0.5 µm) because I didn’t swap toolheads. What we see is just a measurment fluctuation !

[EDIT] now learning how to create a Klipper plugin repo and make it appear in the Update Manager.

I’m not sure whether you have fixed your PID-Problem with multiple extruders, but I modified my Klipper fork to allow multiple PID-Profiles.
I plan on making a PR for it once I come around to writing the doc, but as of now the code works perfectly fine, you can check it out at GitHub - Zeanon/klipper at pid_profiles
Be aware that my branch uses the improved PID-Tuning by Dans found here: https://github.com/Klipper3d/klipper/pull/5955
It also has a patch to read the internal temp sensor of an arduino uno and is patched to support the run_on_error feature found in the LED-Effect plugin
If you just want the Profiles(though I would recommend also going with the improved PID-Tuning) I can create another branch for you which only has those, otherwise I will make a PR into klipper as soon as the PID-Tuning PR is merged anyways.

For saving a custom PID Profile you can just add a PROFILE=<profile_name> to the PID_CALIBRATE command and for loading you would use PID_PROFILE_LOAD HEATER=<heater> PROFILE=<profile_name> [DEFAULT=<default_profile>] DEFAULT is optional and behaves like a getordefault method

Yes, I fixed the problem by commenting a sanity check so a multi tool printer can be created, that shares one stepper. Not the smartest, but it works. (found someone who did it in the Klipper Git issues section).

The printer is currently running, so I can’t test your PR. Currently browsing the sources… I doubt I will understand something (abolute beginner…), but cannot hurt !

It is weird that the Voron team didn’t implement software support for the Afterburner and the Stealthburner. These toolhead have been around for a long time…

Thanks ! But as a beginner, it’s better I use either the official branch, or limited mods I can understand or hacks I did myself. Learning from your repo is a much better thing. Asking for on purpose code is a lazy and unproductive solution. If someone had given the solution for the auto Z offset, I would have learned nothing about macros (most likely reinvented the wheel, but it’s my wheel !)

No problem, if you want some insight on the code managing the profiles, just hit me up

@LastZeanon

GREAT !!!

I didn’t install your fork, I just copied pid_calibrate.py, heaters.py and led.py (not a great fan of RGBs, but I put some in the Stealthburner and just received a mini 12864 with RGB… so why not !).

Amazing. A real solution to the problem, much more convenenient than my atrocious hack. Also opens doors to more possibilities like PIDs depending on higth temp, low temp, heated or open chamber.

Nice macros to be written !

I had a problem with the PID autotune (yours) : it didn’t want to stop. After 21 iterations, had to reboot. Maybe a dependency I didn’t “install” ?

I do it this way because doing this, I keep control over updates. Having the forked files by hand, I can “install” them again. And I don’t know how to tell Klipper what branch it should use.

Anyway, thanks a lot.

Solved² (squared)

Just fyi: the modified led.py won’t work without a modified firmware as well
The modified firmware file should be led.c I believe
You also need to redo your Pid tuning cause the modified Pid algorithm in there by dans uses other values

Ok, thanks for the update.

Not sure it is the right thing to do, but I did it :

  • forked klipper
  • added your “extra” files I want to experiment with (led and heaters)
  • suscribded to your git in order to get notifications
  • modified kiauh.sh after some search with grep : klipper repo now points to “my” branch (didn’t find anything related in the cfg files, I suspect it’s deeper in the OS)
  • now update using Kiauh, not Klipper

Seems to work fine, except Klipper is now flagged “Invalid”…
Is there a better way ?
(found nothing with Google…)

Now back to the configurations !

the klipper repo is hardcoded into moonraker, so as long as you dont fork moonraker, it’s gonna keep showing as invalid afaik
You can fork moonraker as well and change the hardcoded repo url for moonraker and klipper there, but then you’d most definitel have to tell people you are running a modified klipper when asking for support

This is done intentionally on two levels - hard coded in Moonraker and checked on every klipper start. Especially for the people supporting here, it would be very unfair to burn a lot of time just to find out way later that the errors are coming from modified sources and would not happen on pristine Klipper.

I understand.

Here’s what the start gcode now looks like using :

  • pid profiles : LastZeanon branch
  • knobprobe for a BLTouch friendly Z endstop : TypQxQ plugin
  • stealthburner_leds.cfg : voron-klipper-config macros library
  • the quick and dirty PLA 3D printed Z offset probe (DIY)
  • a pile of macros to get the auto Z offset (later on Github after more testing)
#######################################################################################
# START_PRINT : 
#
# ex. : START_PRINT NOZZLE_DIAM=0.4 BED_TEMP=60 TOOL_TEMP=200 CHAMBER_TEMP=0
#######################################################################################

[gcode_macro START_PRINT]

gcode:

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

  RESPOND MSG="START_PRINT"
  RESPOND MSG="Selected : Nozzle : "{nozzle_diam} #"mm ; Tool : "{tool_temp}"°C, "Bed : "{bed_temp}"°C, "Chamber :"{chamber_temp}"°C"

  # M117 Heating...              # LCD = Heating
  STATUS_HEATING				 # Neopixel	Stealthburner
  
  M140 S{bed_temp}               # heat bed, don't wait
  M104 S{tool_temp}              # heat tool, don't wait
  #M141 S{chamber_temp}           # heat chamber, don't wait  
  
  M190 S{bed_temp}               # wait for bed temp
  M109 S{tool_temp}              # wait for tool temp
  #M191 S{chamber_temp}           # wait for chamber temp

  # https://github.com/Zeanon/klipper/tree/pid_profiles
  # heaters.py led.py pid_calibrate.py

  {% if nozzle_diam == 0.2 %}
    PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_02 #DEFAULT=V6_04
  {% elif nozzle_diam == 0.3 %}
    PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_03 #DEFAULT=V6_04
  {% elif nozzle_diam  == 0.4 %}
    PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_04 #DEFAULT=V6_04
  {% elif nozzle_diam  == 0.5 %}
    PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_05 #DEFAULT=V6_04
  {% elif nozzle_diam  == 0.6 %}
    PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_06 #DEFAULT=V6_04
  {% elif nozzle_diam  == 0.8 %}
	PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_08 #DEFAULT=V6_04
  {% elif nozzle_diam  == 1.0 %}
    PID_PROFILE_LOAD HEATER=extruder PROFILE=V6_10 #DEFAULT=V6_04
  {% else %}
    RESPOND TYPE=error MSG={nozzle_diam}"mm nozzle ? Are you kidding me ???"
    _print_abort
  {% endif %}

  M117 Homing...
  STATUS_HOMING                  # Neopixel	Stealthburner
  G21                            # metric
  G90                            # absolute coordinates
  M82                            # absolute extrusion mode
  G92 E0                         # set extruder to zero
  G28                            # auto home
  
  # auto Z offset
  CUR_Z_OFFSET                   # get nozzle offset
  SET_GCODE_OFFSET_ADJUST {printer.save_variables.variables.gcode_offset}
  
  G0 X10 Y10 Z5 F20000           # park front left while heating for cleaning : mm/mn

  # M117 Leveling...             # LCD = Bed Leveling
  STATUS_MESHING                 # Neopixel	Stealthburner
  BED_MESH_CALIBRATE             # Marlin G29 macro
  #BED_MESH_PROFILE SAVE = p1

  # M117 Cleaning...             # LCD = Cleaning
  STATUS_CLEANING                # Neopixel	Stealthburner
  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...
  STATUS_PRINTING                # Neopixel	Stealthburner

Looks cool
If you have any problems with the PID-Profiles, pls hit me up so I can fix it.
I have updated the command a bit(basically changed from PID_PROFILE_LOAD HEATER PROFILE to PID_PROFILE LOAD= HEATER DEFAULT=<default profile to load if the wanted profile does not exist)
Mainly to keep the syntax similar to bed mesh and also add a PID_PROFILE REMOVE= HEATER
It’s not nescessary for you to update your klipper with the changes as they didnt fix any bugs, just changed the command syntax

I will add everything to the doc soon

Thanks !

Yes, I’ve seen a lot of work on your Git. No activity for 2 days, I just updated heater.py
Seems to be working fine, but I’m not a specialist…

An idea : when giving PROFILE=<profile_name> to the command, couldn’t the command write to printer.cfg on its own (without SAVE_CONFIG and restarting ?) Having to issue the command by hand is a bit counter intuitive because providing a profile name means that we want to save it. Couldn’t this allow the user to load the profile and use it immediately ? Could be usefull. Similar to bed leveling. Could live in the start gcode, in particular when printing in a heated chamber (does not take that long).

Attempted to understand the sources about the profiles, but really not comfortable with Python.

Of course, we can create profiles for various situations, and add conditionals in the start gcode.

I tried to be as close to the default PID_Tune behaviour as possible
Only keeping the PID values in memory isn’t as easy though due to how the code is designed
It would be possible but would require a lot more code to be rewritten and I tried to keep the changes as minimal as possible
One thing you could do(which is what I did) is to just write a macro which calls the pid tune and then saves and use a LUT in your start code to load whichever profile you want
Also I dont think doing a PID tune before each print is the way to go, as tuning a bed takes quite some time(about 45min, at least for me)
Maybe I can rewrite the code to work more like bed meshes, but that will take one or two days

Of course, a PID before printing could only be somewhat usefull for hotends. Definitely not for beds.

I have a problem with your last commit. PID_PROFILE_LOAD was replced with PID_PROFILE, and reading sources, I can’t find the syntax ; there seems to be a LOAD in the command line, but didn’t figure how to. Had to revert to previous one.

Oh, and there’s an error in the code I posted. Strings are malformed. Should be ‘“profile_name”’, not profile_name. Just begining to learn all these subtilities !

Anyway, I get much much more stable temperatures than those I had using Marlin. Night and day !

as said, I changed the syntax a bit to allow for a remove command
as soon as I finished redoing the logic to allow to keep a new profile in memory I will add a doc as well

Oops :person_facepalming: