# Copyright (C) 2022 Justin Schuh # # This file may be distributed under the terms of the GNU GPLv3 license. ################################################################################ # # Declare any of the below variables in your own [gcode_macro _km_options] to # to override the values here. # # DO NOT CHANGE ANYTHING IN THIS FILE!!! # # This file handles the initialization for all the macros, and difficult to # diagnose errors will result from unexpected values or code changes. # ################################################################################ [gcode_macro _km_globals] # Available bed surfaces for offset adjustments. variable_bed_surface_max_name_length: 10 # Available bed surfaces for offset adjustments. variable_bed_surfaces: ['default'] # Total length (in mm) of filament to load (bowden tubes will be much longer). variable_load_length: 90.0 # Global minimum extruder temp for loading (default: min_extrude_temp + 5). variable_load_min_temp: 0 # Length (in mm) of the extruder meltzone (feeds at priming speed). variable_load_priming_length: 20.0 # Filament priming speed (in mm/m). variable_load_priming_speed: 200 # Filament loading speed (in mm/m). variable_load_speed: 1200 # Set to False to hide the Octoprint LCD menus. variable_menu_show_octoprint: True # Set to False to hide the SD Card LCD menus. variable_menu_show_sdcard: True # List up to 10 pre-heat settings in order for the LCD "Temperature" menu variable_menu_temperature: [ {'name' : 'PLA', 'extruder' : 200, 'bed' : 60}, {'name' : 'PETG', 'extruder' : 230, 'bed' : 85}, {'name' : 'ABS', 'extruder' : 245, 'bed' : 110}] # X position to park toolhead (set "max" or "min" to infer from stepper config). variable_park_x: 0.0 # Y position to park toolhead (set "max" or "min" to infer from stepper config). variable_park_y: 0.0 # Z position to park toolhead (set "max" or "min" to infer from stepper config). variable_park_z: 20.0 # Minimum printable XY coordinate. Defaults to X and Y position_min. variable_print_min: () # example: (0, 0) # Maximum printable XY coordinate. Defaults to X and Y position_max. variable_print_max: () # example: (220, 220) # Scaling factor for M900 command (negative values make M900 a no-op). variable_pressure_advance_scale: -1.0 # Additional padding around the specified print area for a bed mesh. variable_probe_mesh_padding : 5.0 # Minimum number of probes per axis for partial probing of a bed mesh. variable_probe_min_count: 3 # Scaling factor to increase probe count for partial bed probes. variable_probe_count_scale: 1.0 # Additional delay (in ms) during bed heating, to allow the bed to stabilize. variable_start_bed_heat_delay: 2000 # Amount (in degrees C) to overshoot bed target temp before stabilizing. variable_start_bed_heat_overshoot: 2.0 # Set to clear adjustments (e.g. feedrate, extrusion, heater) at end of print. variable_start_clear_adjustments_at_end: True # PRINT_END Y park position (set "max" or "min" to infer from stepper config). variable_start_end_park_y: 0.0 # Defaults to print_max Y. # Extruder scale factor during pre-warmup in PRINT_START. variable_start_extruder_preheat_scale: 0.5 # If non-zero the extruder will stabilize at this temp before probing the bed. variable_start_extruder_probing_temp: 0 # Set to rehome Z in PRINT_START after bed temp stabilizes; False to disable. variable_start_home_z_at_temp: True # Set to level bed in PRINT_START after bed temp stabilizes; False to disable. variable_start_level_bed_at_temp: True # Distance (in millimeters) between the purge lines and the print area. variable_start_purge_clearance: 2.0 # Length of filament (in millimeters) to purge at print start. variable_start_purge_length: 0.0 # 30 is a good starting point. # Length of filament (in millimeters) to prime before drawing purge lines. variable_start_purge_prime_length: 12.0 # Level gantry in PRINT_START after bed temp stabilizes; False to disable. variable_start_quad_gantry_level_at_temp: True # Maximum distance to relocate print in random XY location to reduce bed wear. variable_start_random_placement_max: 0 # The relocation will be at least this far from the printable edge of the bed. variable_start_random_placement_padding: 10.0 # Skip level and load saved mesh for current sheet (if found); False to disable. variable_start_try_saved_surface_mesh: False # Adjust Z tilt in PRINT_START after bed temp stabilizes; False to disable. variable_start_z_tilt_adjust_at_temp: True # X and Y travel speed (in mm/m) for movement macros. variable_travel_speed_xy: 3000 # Z travel speed in (mm/m) for movement macros. variable_travel_speed_z: 600 ################################################################################ description: Initializes our globals, including any _km_options overrides. gcode: # Doing a shutdown here is a bit aggressive, but if we're missing required # sections then a lot of things could go very bad later. # To minimize the annoyance we try to identify all the fatal errors at once. # format is: # key = required config section # value[0] = required field in section # value[1] = required string in field # A "None" value means there's no required field {% set required_sections = {"heater_bed" : None, "extruder" : None, "gcode_macro _km_options" : None, "idle_timeout" : ("gcode", "_KM_IDLE_TIMEOUT"), "pause_resume" : None, "respond" : None, "save_variables" : None, "virtual_sdcard" : ("on_error_gcode", "CANCEL_PRINT") } %} {% set warn_removed = { "start_extruder_set_target_before_level" : "The start_extruder_set_target_before_level option has been removed. " "Use the start_extruder_probing_temp option instead to set a fixed " "probing temperature.", "start_gcode_before_print" : "The start_gcode_before_print option has been removed. The same effect " "can now be achieved by inserting custom gcode before the " "_PRINT_START_PHASE_PURGE line in your slicer's start gcode, or by " "wrapping the _PRINT_START_PHASE_PURGE macro. See the Slicer " "Configuration section in the readme for more information.", "velocity_decel_scale" : "The velocity_decel_scale option has been removed. Klipper's input " "shaping provides a superior mechanism for improving print quality " "at higher speeds.", } %} {% set output = [] %} {% for s in required_sections %} {% set f = required_sections[s][0] if required_sections[s] else None %} {% set v = required_sections[s][1] if required_sections[s] else None %} {% if s not in printer.configfile.config %} {% set dummy = output.append("Missing [%s] section.\n" | format(s)) %} {% elif f and (v not in printer.configfile.config[s][f]|default("")|upper) %} {% set dummy = output.append("Missing %s in %s for [%s] section.\n" | format(v, f, s)) %} {% endif %} {% endfor %} {% if output %} { action_emergency_stop(( "required printer.cfg section(s) missing:\n" ~ output | join("\n")) ~ "See readme: https://github.com/jschuh/klipper-macros\x23klipper-setup") } {% endif %} # These are all set to their defaults based on config options: SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=start_level_bed_at_temp VALUE="{ 1 if printer.bed_mesh is defined else 0}" SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=start_quad_gantry_level_at_temp VALUE="{ 1 if printer.quad_gantry_level is defined else 0}" SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=start_z_tilt_adjust_at_temp VALUE="{ 1 if printer.z_tilt is defined else 0}" {% set toolhead = printer.toolhead %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=print_min VALUE="{ (toolhead.axis_minimum.x, toolhead.axis_minimum.y)}" SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=print_max VALUE="{ (toolhead.axis_maximum.x, toolhead.axis_maximum.y)}" SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=start_end_park_y VALUE="{ toolhead.axis_maximum.y}" {% set settings = printer.configfile.settings %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE=start_home_z_at_temp VALUE="{ 1 if ("stepper_z" in settings and settings.stepper_z.endstop_pin.split()|join("")|lower == "probe:z_virtual_endstop") else 0}" {% set options = printer["gcode_macro _km_options"] %} {% set km = printer["gcode_macro _km_globals"] %} # Allow placeholders for parking positions. {% if options.park_x|default("")|string|lower == "min" %} {% set dummy = options.__setitem__("park_x", printer.configfile.settings.stepper_x.position_min) %} {% elif options.park_x|default("")|string|lower == "max" %} {% set dummy = options.__setitem__("park_x", printer.configfile.settings.stepper_x.position_max) %} {% endif %} {% if options.park_y|default("")|string|lower == "min" %} {% set dummy = options.__setitem__("park_y", printer.configfile.settings.stepper_y.position_min) %} {% elif options.park_y|default("")|string|lower == "max" %} {% set dummy = options.__setitem__("park_y", printer.configfile.settings.stepper_y.position_max) %} {% endif %} {% if options.start_end_park_y|default("")|string|lower == "min" %} {% set dummy = options.__setitem__("start_end_park_y", printer.configfile.settings.stepper_y.position_min) %} {% elif options.start_end_park_y|default("")|string|lower == "max" %} {% set dummy = options.__setitem__("start_end_park_y", printer.configfile.settings.stepper_y.position_max) %} {% endif %} {% if options.park_z|default("")|string|lower == "min" %} {% set dummy = options.__setitem__("park_z", printer.configfile.settings.stepper_z.position_min) %} {% elif options.park_z|default("")|string|lower == "max" %} {% set dummy = options.__setitem__("park_z", printer.configfile.settings.stepper_z.position_max) %} {% endif %} # Force overrides to use the original types in _KM_GLOBALS. {% for k in options %} {% if k not in km %} {% if k in warn_removed %} {action_respond_info(warn_removed[k])} {% else %} {% set dummy = output.append("%s is not valid for _KM_OPTIONS." | format(k)) %} {% endif %} {% elif km[k] is string %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE={k } VALUE="'{options[k]|replace('\\','\\\\')|replace('\'','\\\'') |replace('\"','\\\"')}'" {% elif km[k] is float %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE={k } VALUE="{options[k]|float}" {% elif km[k] is integer or km[k] is boolean %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE={k} VALUE="{options[k]|int}" {% elif km[k] is mapping %} {% if options[k] is not mapping %} {% set dummy = output.append("%s requires a mapping type." | format(k)) %} {% endif %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE={k } VALUE="{options[k]|replace('\"','\\\"')}" {% elif km[k] is sequence %} {% if options[k] is not sequence %} {% set dummy = output.append("%s requires a sequence type." | format(k)) %} {% endif %} SET_GCODE_VARIABLE MACRO=_km_globals VARIABLE={k } VALUE="{options[k]|replace('\"','\\\"')}" {% else %} {% set dummy = output.append("%s is not a valid type for _KM_OPTIONS." | format(k)) %} {% endif %} {% endfor %} {% if "homing_override" in printer.configfile.config %} {% for l in printer.configfile.config.homing_override.gcode.split("\n") %} {% if " g28 " in (" " ~ l.split("\x23")[0].split(";")[0]|lower ~ " ") %} {% set dummy = output.append( "G28 in [homing_override] gcode. Replace with G28.6245197 to " "fix recursive macro call.\n" "See readme: https://github.com/jschuh/klipper-macros\x23g28") %} {% endif %} {% endfor %} {% endif %} SET_GCODE_VARIABLE MACRO=check_km_config VARIABLE=load_errors VALUE="{ output|replace('\"','\\\"')}" M400 [delayed_gcode INIT_GLOBALS] # This runs once at startup and initializes all macros. initial_duration: 1 gcode: LIST_MACROS SILENT=1 # Build the macro cache. _KM_GLOBALS # This needs to be its own macro so it gets evaluated after _KM_GLOBALS runs. CHECK_KM_CONFIG _INIT_SURFACES BED_MESH_CHECK # Sets the default drawing parameters. SET_DRAW_PARAMS WIDTH="{printer.configfile.settings.extruder.nozzle_diameter}" # This is any end-user gcode that need to run after macro initialization. _KM_OPTIONS _KM_PRINT_STATUS ACTION=CHANGE STATUS=ready [gcode_macro check_km_config] variable_load_errors: [] description: Checks global variables and throws an error on any invalid values. Does nothing if the config has no errors. gcode: {% set km = printer["gcode_macro _km_globals"] %} {% set toolhead = printer.toolhead %} {% set output = load_errors %} {% if km.park_x > toolhead.axis_maximum.x or km.park_x < toolhead.axis_minimum.x %} {% set dummy = output.append("park_x is invalid.") %} {% endif %} {% if km.park_y > toolhead.axis_maximum.y or km.park_y < toolhead.axis_minimum.y %} {% set dummy = output.append("park_y is invalid.") %} {% endif %} {% if km.park_z > toolhead.axis_maximum.z or km.park_z < toolhead.axis_minimum.z %} {% set dummy = output.append("park_z is invalid.") %} {% endif %} {% if km.print_max[0] > toolhead.axis_maximum.x or km.print_max[1] > toolhead.axis_maximum.y %} {% set dummy = output.append("print_max is invalid.") %} {% endif %} {% if km.print_min[0] < toolhead.axis_minimum.x or km.print_min[1] < toolhead.axis_minimum.y %} {% set dummy = output.append("print_min is invalid.") %} {% endif %} {% if km.start_extruder_preheat_scale > 1.0 or km.start_extruder_preheat_scale < 0.0 %} {% set dummy = output.append("extruder_preheat_scale is invalid.") %} {% endif %} {% if km.load_length > printer.configfile.settings["extruder"].max_extrude_only_distance %} {% set dummy = output.append( "load_length exceeds max_extrude_only_distance.") %} {% endif %} {% if km.load_length < km.load_priming_length %} {% set dummy = output.append( "load_length is shorter than load_priming_length.") %} {% endif %} {% if km.load_length < 0.0 %} {% set dummy = output.append("load_length is negative.") %} {% endif %} {% if km.load_priming_length < 0.0 %} {% set dummy = output.append("load_priming_length is negative.") %} {% endif %} # Emit all the config errors. {% if output %} { action_raise_error(output|sort|join('\nError: ')) } {% endif %} M400 [gcode_macro kmvars] description: Lists global variables used by klipper-macros. Usage: KMVARS [SEARCH=] gcode: {% set SEARCH = params.SEARCH|default(params.S|default(""))|lower %} {% set km = printer["gcode_macro _km_globals"] %} {% set output = [] %} {% for k in km %} {% if SEARCH in k %} {% set dummy = output.append(k ~ ": " ~ km[k]) %} {% endif %} {% endfor %} { action_respond_info(output|sort|join('\n')) } [gcode_macro check_macro_docs] description: Lists macros lacking proper documentation. Usage: CHECK_MACRO_DOCS [USAGE=<0|1>] [HIDDEN=<1|0>] [RENAMED=<1|0>] gcode: {% set USAGE = params.USAGE|default(0)|int %} {% set HIDDEN = params.HIDDEN|default(0)|int %} {% set RENAMED = params.RENAMED|default(0)|int %} {% set output = [] %} {%set config = printer.configfile.config %} {% for k in config|sort %} {% if k.startswith("gcode_macro") %} {% set name = k.split()[1] %} {% set desc = config[k].description|default("") %} {% set is_renamed = config[k].rename_existing|default("") %} {% if (not desc or (USAGE and not "Usage: "~name.upper() in desc)) and (HIDDEN or not name.startswith('_')) and (RENAMED or is_renamed) %} {% set dummy = output.append("%s %s: missing %s." | format("*" if is_renamed else " ", name, "description" if not desc else "usage")) %} {% endif %} {% endif %} {% endfor %} {action_respond_info(output|join("\n"))} # The below macro is a lightly edited version of the one found here: # https://klipper.discourse.group/t/example-search-printer-objects/164 [gcode_macro listvars] description: Lists per-macro variables with a name containing SEARCH. This is useful for debugging macros by allowing you to probe printer state. Be very careful, however, as an overly broad SEARCH parameter can take a long time to process and potentially hang or crash klipper. Usage: LISTVARS SEARCH= gcode: {% if 'SEARCH' not in params and 'S' not in params %} { action_raise_error("Must provide a SEARCH parameter.") } {% endif %} {% set SEARCH = params.SEARCH|default(params.S)|lower %} {% set ns = namespace() %} {% set output = [] %} {% for item in printer %} {% if item is not string %} {% set ns.path = ['printer', "[%s]" % (item|string), ''] %} {% elif ' ' in item %} {% set ns.path = ['printer', "['%s']" % (item), ''] %} {% else %} {% set ns.path = ['printer.', item, ''] %} {% endif %} {% if SEARCH in ns.path|lower %} {% set dummy = output.append(ns.path|join) %} {% endif %} {% if printer[item].items() %} {% for childkey, child in printer[item].items() recursive %} {% set ns.path = ns.path[:loop.depth|int + 1] %} {% if childkey is not string %} {% set null = ns.path.append("[%s]" % (childkey|string)) %} {% elif ' ' in childkey %} {% set null = ns.path.append("['%s']" % (childkey)) %} {% else %} {% set null = ns.path.append(".%s" % (childkey)) %} {% endif %} {% if child is mapping %} {loop(child.items())} {% else %} {% if SEARCH in ns.path|lower %} {% set dummy = output.append("%s : %s" % (ns.path|join, child)) %} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %} { action_respond_info(output|join("\n")) } [gcode_macro list_macros] description: Lists registered macros (and optionally show variable state). Usage: LISTVARS SEARCH= VARS=<0|1> SILENT=<0|1> variable_macros: {} gcode: # Load the config state once and save it. {% if not macros %} {% set renames = {} %} {% for k in printer.configfile.config %} {% if k.lower().startswith("gcode_macro") and printer.configfile.config[k].rename_existing %} {% set name = k.split(None, 1)[1] | lower %} {% set dummy = renames.__setitem__(name, [printer.configfile.config[k].rename_existing|lower] + renames[name]|default([])) %} {% endif %} {% endfor %} {% for k in printer %} {% if k.lower().startswith("gcode_macro") %} {% set name = k.split(None, 1)[1] | lower %} {% set dummy = macros.__setitem__(name, renames[name]|default([])) %} {% endif %} {% endfor %} {% endif %} {% if params.SILENT|default(0)|int == 0 %} _LIST_MACROS_DISPLAY {rawparams} {% endif %} [gcode_macro _list_macros_display] gcode: {% set SEARCH = params.SEARCH | default(params.S) | default("") | lower %} {% set VARS = params.VARS | default(params.V) | default(0) | int != 0 %} {% set macros = printer["gcode_macro list_macros"].macros %} {% set output = [] %} {% for k in macros %} {% if SEARCH in k | lower %} {% set line = k ~ ((" (renames: %s)" % (macros[k]|join(","))) if macros[k] else "") %} {% set outvars = [] %} {% if VARS %} {% for v in printer["gcode_macro " ~ k] | sort %} {% set dummy = outvars.append("\n* %s: %s" % (v, printer["gcode_macro " ~ k][v]|string)) %} {% endfor %} {% endif %} {% set dummy = output.append(line + outvars | join("")) %} {% endif %} {% endfor %} { action_respond_info(output | sort | join("\n")) }