In the printer.cfg
, one can specify the nozzle size and filament diameter. The values are not exposed in gcode macros.
Having them available, can be useful to calculate how much filament should be extruded or to error if a macro (that contains pre-sliced gcode) is called with the wrong nozzle size.
This feature has been requested in [FR] SET_NOZZLE_SIZE · Issue #797 · Klipper3d/klipper · GitHub, but rejected.
To enable this functionality, create the file ~/klipper/klippy/extras/extra_data.py
with the following content:
import sys
import math
import logging
import types
# ensure that the script is run with a relatively modern python version
if sys.version_info.major != 3:
raise EnvironmentError("python 3 is required for execution of %s" % sys.argv[0])
# In python it is possible to change functions of objects.
#
# This function modifies the object's get_status function, so that
# after calling it, the get_extra_values function is called and all
# values are appended to the result.
def override_get_status(object, get_extra_values):
# keep a reference to the original get_status function (to call it)
original_get_status = object.get_status
def new_get_status(self, eventtime):
# call the original function
res = dict(original_get_status(eventtime))
res.update(get_extra_values(eventtime))
return res
# replace the old get_status function with the new one
object.get_status = types.MethodType(new_get_status, object)
class ExtraData:
def __init__(self, config):
self.printer = config.get_printer()
self.config = config
is_read_only = self.config.getboolean("is_read_only", default=True)
# extruder is not available in __init__ which is required to override the get_status function
# therefore it is overridden when klipper connects to the printer.
self.has_updated = False
self.printer.register_event_handler("klippy:connect", self._handle_connect)
# get values from config:
extruder_section = self.config.getsection("extruder")
self.nozzle_diameter = extruder_section.getfloat('nozzle_diameter', 0.4, above=0.)
self.filament_diameter = extruder_section.getfloat('filament_diameter', 1.75, minval=self.nozzle_diameter)
# register commands:
self.gcode = self.printer.lookup_object('gcode')
self.gcode.register_command('DEBUG_EXTRUDER_VALUES', self.cmd_DEBUG_EXTRUDER_VALUES, desc=self.cmd_SET_DEBUG_EXTRUDER_VALUES_help)
if not is_read_only:
self.gcode.register_command('SET_NOZZLE_DIAMETER', self.cmd_SET_NOZZLE_DIAMETER, desc=self.cmd_SET_NOZZLE_DIAMETER_help)
self.gcode.register_command('SET_FILAMENT_DIAMETER', self.cmd_SET_FILAMENT_DIAMETER, desc=self.cmd_SET_FILAMENT_DIAMETER_help)
def _handle_connect(self):
if not self.has_updated:
# the extruder object is not available in __init__, therefore the get_status method is overridden here
extruder = self.printer.lookup_object("extruder")
override_get_status(extruder, lambda eventtime: { "nozzle_diameter": self.nozzle_diameter, "filament_diameter": self.filament_diameter })
self.has_updated = True
# Update the extruder values based on the new nozzle_diameter/filament_diameter:
def _update_extruder_values(self, extruder, nozzle_diameter=None, filament_diameter=None):
# update nozzle_diameter:
if nozzle_diameter is not None:
extruder.nozzle_diameter = nozzle_diameter
extruder_section = self.config.getsection(extruder.name)
# fetch the value for the filament diameter:
if filament_diameter is None:
filament_diameter = self.filament_diameter
extruder.filament_area = math.pi * (filament_diameter * .5)**2
def_max_cross_section = 4. * extruder.nozzle_diameter**2
def_max_extrude_ratio = def_max_cross_section / extruder.filament_area
max_cross_section = extruder_section.getfloat(
'max_extrude_cross_section', def_max_cross_section, above=0.)
extruder.max_extrude_ratio = max_cross_section / extruder.filament_area
logging.info("Extruder max_extrude_ratio=%.6f", extruder.max_extrude_ratio)
toolhead = self.printer.lookup_object('toolhead')
max_velocity, max_accel = toolhead.get_max_velocity()
extruder.max_e_velocity = extruder_section.getfloat(
'max_extrude_only_velocity', max_velocity * def_max_extrude_ratio
, above=0.)
extruder.max_e_accel = extruder_section.getfloat(
'max_extrude_only_accel', max_accel * def_max_extrude_ratio
, above=0.)
cmd_SET_DEBUG_EXTRUDER_VALUES_help = ("Prints the extruder values that depend on nozzle/filament diameter, useful for debugging.")
def cmd_DEBUG_EXTRUDER_VALUES(self, gcmd):
extruder = self.printer.lookup_object("extruder")
gcmd.respond_info("""
[extruder]
nozzle_diameter: {}
filament_diameter: {}
filament_area: {}
max_extrude_ratio: {}
max_e_velocity: {}
max_e_accel: {}
""".format(
extruder.nozzle_diameter,
self.filament_diameter,
extruder.filament_area,
extruder.max_extrude_ratio,
extruder.max_e_velocity,
extruder.max_e_accel,
))
cmd_SET_NOZZLE_DIAMETER_help = ("Sets the nozzle diameter to the specified value")
# SET_NOZZLE_DIAMETER DIAMETER=0.6
def cmd_SET_NOZZLE_DIAMETER(self, gcmd):
new_nozzle_diameter = gcmd.get_float('DIAMETER', None, above=0.)
# do nothing if no diameter has been specified
if new_nozzle_diameter is None:
return
configfile = self.printer.lookup_object('configfile')
extruder = self.printer.lookup_object("extruder")
self._update_extruder_values(extruder, nozzle_diameter=new_nozzle_diameter)
self.nozzle_diameter = new_nozzle_diameter
# update config value:
gcmd.respond_info("extruder: nozzle_diameter: {:.3f}\nThe SAVE_CONFIG command will update the printer config file\nwith the above and restart the printer.".format(new_nozzle_diameter))
configfile.set("extruder", "nozzle_diameter", "{:.3f}".format(new_nozzle_diameter))
cmd_SET_FILAMENT_DIAMETER_help = ("Sets the filament diameter to the specified value")
# SET_FILAMENT_DIAMETER DIAMETER=1.76
def cmd_SET_FILAMENT_DIAMETER(self, gcmd):
new_diameter = gcmd.get_float('DIAMETER', None, minval=self.nozzle_diameter)
# do nothing if no diameter has been specified
if new_diameter is None:
return
configfile = self.printer.lookup_object('configfile')
extruder = self.printer.lookup_object("extruder")
self._update_extruder_values(extruder, filament_diameter=new_diameter)
self.filament_diameter = new_diameter
# update config value:
gcmd.respond_info("extruder: filament_diameter: {:.3f}\nThe SAVE_CONFIG command will update the printer config file\nwith the above and restart the printer.".format(new_diameter))
configfile.set("extruder", "filament_diameter", "{:.3f}".format(new_diameter))
def load_config(config):
return ExtraData(config)
Then in the printer.cfg
add this section:
[extra_data]
# is_read_only = True
# If set to True, the SET_NOZZLE_SIZE and SET_FILAMENT_DIAMETER commands
# will not be exposed. Some klipper safety features rely on sensible values for the nozzle size
# and with those commands available, it would be possible for foreign gcode files to potentially
# harm your 3d printer.
The following commands will become available (if is_read_only is False):
DEBUG_EXTRUDER_VALUES
SET_NOZZLE_DIAMETER DIAMETER=0.4
SET_FILAMENT_DIAMETER DIAMETER=1.75
and one can access the values in a gcode macro:
[gcode_macro TEST_EXTRA_DATA]
gcode:
{ action_respond_info("NOZZLE_DIAMETER={}".format(printer.extruder.nozzle_diameter)) }
{ action_respond_info("FILAMENT_DIAMETER={}".format(printer.extruder.filament_diameter)) }
Related discussion: