# Copyright (C) 2022 Justin Schuh # # This file may be distributed under the terms of the GNU GPLv3 license. [gcode_macro before_layer_change] description: Add this to the "before layer change" input box in the slicer. Usage: BEFORE_LAYER_CHANGE HEIGHT= LAYER= gcode: {% set height = params.HEIGHT|default(printer.toolhead.position.z)|float %} {% set layer = params.LAYER|default(-1)|int + 1 %} {% if height >= 0.0 and layer >= 0 %} SET_PRINT_STATS_INFO CURRENT_LAYER="{layer}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=cur_height VALUE="{height}" {% if printer["gcode_macro _km_layer_run"].clearance_z < height %} SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=clearance_z VALUE="{ height}" {% endif %} {% endif %} [gcode_macro after_layer_change] description: Add this to the "after layer change" input box in the slicer. Usage: AFTER_LAYER_CHANGE gcode: _KM_LAYER_RUN [gcode_macro gcode_at_layer] description: Schedules the specified g-code command to be run at the specified layer. LAYER=next will cause the command to run at the next layer change. Usage: GCODE_AT_LAYER { HEIGHT= | LAYER= } COMMAND= [CANCEL=<0|1>] gcode: {% set tot_layers = printer.print_stats.info.total_layer %} {% if params|length > 0 %} _KM_CHECK_IS_PRINTING {% set CANCEL = params.CANCEL|default(0)|int != 0 %} {% set COMMAND = params.COMMAND|default(None) %} {% if COMMAND %} # This makes it easier to match commands for cancellation. {% set COMMAND = COMMAND.lstrip().split(None, 1) %} {% set COMMAND = " ".join([COMMAND[0]|upper] + COMMAND[1:]) %} {% endif %} {% if "LAYER" in params %} {% set cmd_container = "commands_layer" %} {% set cur_layer = printer.print_stats.info.current_layer|int %} {% if "HEIGHT" in params %} {action_raise_error("Conflicting HEIGHT and LAYER arguments provided.")} {% elif params.LAYER|string|lower == "next" %} {% set LAYER = cur_layer + 1 %} {% else %} {% set LAYER = params.LAYER|int %} {% endif %} {% if LAYER <= cur_layer %} {action_raise_error("LAYER[%i] must be above current print layer[%i]." | format(LAYER, cur_layer))} {% elif tot_layers and LAYER > tot_layers %} {action_raise_error("LAYER[%i] must not be above top layer[%i]." | format(LAYER, tot_layers))} {% endif %} {% set key = LAYER %} {% elif "HEIGHT" in params %} {% set cmd_container = "commands_height" %} {% set HEIGHT = params.HEIGHT|float %} {% set cur_height = printer["gcode_macro _km_layer_run"].cur_height %} {% if HEIGHT <= cur_height %} {action_raise_error( "HEIGHT[%.3f] must be above current print height[%.3f].") | format(HEIGHT, cur_height)} {% elif HEIGHT >= printer.toolhead.axis_maximum.z %} {action_raise_error( "HEIGHT[%.3f] must be below maximum Z height[%.3f].") | format(HEIGHT, printer.toolhead.axis_maximum.z)} {% endif %} {% set key = HEIGHT %} {% else %} {action_raise_error("No HEIGHT or LAYER argument provided.")} {% endif %} {% set commands = printer["gcode_macro _km_layer_run"][cmd_container] %} {% if key not in commands and not CANCEL %} {% set dummy = commands.__setitem__(key, []) %} {% endif %} {% if CANCEL %} {% if key in commands %} {% set pos = ("%i"|format(key)) if key is integer else ("%.3fmm"|format(key)) %} {% if COMMAND %} {% set dummy = commands[key].remove(COMMAND) %} {% if commands[key]|length == 0 %} {% set dummy = commands.__delitem__(key) %} {% endif %} {action_respond_info("Cancelled %s %s:\n* %s" | format("layer" if k is integer else "height", pos, COMMAND))} {% else %} {% set dummy = commands.__delitem__(key) %} {action_respond_info("Cancelled all commands at %s %s." | format("layer" if k is integer else "height", pos))} {% endif %} {% endif %} {% elif not COMMAND %} {action_raise_error("No COMMAND argument provided.")} {% elif COMMAND in commands[key] %} {action_raise_error("Duplicate command previously scheduled.")} {% else %} {% set dummy = commands[key].append(COMMAND) %} {% set pos = ("%i"|format(key)) if key is integer else ("%.3fmm"|format(key)) %} {action_respond_info("%s %s:\n* %s" | format("layer" if key is integer else "height", pos, COMMAND))} {% endif %} SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE={cmd_container} VALUE="{ commands|replace('\"','\\\"')}" _UPDATE_LAYER_COMPAT # No arguments means just list all the triggers. {% else %} {% set output = [] %} {% set commands = printer["gcode_macro _km_layer_run"].commands_layer %} {% for key in commands|list|sort %} {% set dummy = output.append("layer %i:" | format(key)) %} {% for c in commands[key] %} {% set dummy = output.append("* %s" | format(c)) %} {% endfor %} {% endfor %} {% set commands = printer["gcode_macro _km_layer_run"].commands_height %} {% for key in commands|list|sort %} {% set dummy = output.append("height %.3fmm:" | format(key)) %} {% for c in commands[key] %} {% set dummy = output.append("* %s" | format(c)) %} {% endfor %} {% endfor %} {action_respond_info(output|join('\n'))} {% endif %} [gcode_macro _km_layer_run] description: Runs pending commands for the current layer change. Usage: _KM_LAYER_RUN variable_cur_height: 0.0 variable_clearance_z: 0.0 variable_commands_layer: {} variable_commands_height: {} gcode: {% set cur_layer = printer.print_stats.info.current_layer %} {% for key in commands_layer | select("<=", cur_layer) | sort %} {action_respond_info("Executing scheduled commands at layer %d:\n%s" | format(key, commands_layer[key]|join('\n')))} {% for c in commands_layer[key] %} {c} {% endfor %} {% set dummy = commands_layer.__delitem__(key) %} {% endfor %} SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_layer VALUE="{ commands_layer|replace('\"','\\\"')}" {% for key in commands_height | select("<=", cur_height) | sort %} {action_respond_info("Executing scheduled commands at height %.3f:\n%s" | format(key, commands_height[key]|join('\n')))} {% for c in commands_height[key] %} {c} {% endfor %} {% set dummy = commands_height.__delitem__(key) %} {% endfor %} SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_height VALUE="{ commands_height|replace('\"','\\\"')}" _UPDATE_LAYER_COMPAT [gcode_macro init_layer_gcode] description: Clears scheduled gcode commands and state for all layers. Usage: INIT_LAYER_GCODE LAYERS= gcode: SET_PRINT_STATS_INFO TOTAL_LAYER="{params.LAYERS|int + 1}" CURRENT_LAYER="{0}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=cur_height VALUE="{0.0}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=clearance_z VALUE="{0.0}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_layer VALUE="{{}}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_height VALUE="{{}}" _UPDATE_LAYER_COMPAT [gcode_macro _reset_layer_gcode] description: Clears scheduled gcode commands and state for all layers. Usage: _RESET_LAYER_GCODE gcode: SET_PRINT_STATS_INFO TOTAL_LAYER="{0}" CURRENT_LAYER="{0}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=cur_height VALUE="{0.0}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=clearance_z VALUE="{0.0}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_layer VALUE="{{}}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_height VALUE="{{}}" _UPDATE_LAYER_COMPAT [gcode_macro cancel_all_layer_gcode] description: Clears all scheduled gcode commands. Usage: CANCEL_ALL_LAYER_GCODE gcode: SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_layer VALUE="{{}}" SET_GCODE_VARIABLE MACRO=_km_layer_run VARIABLE=commands_height VALUE="{{}}" _UPDATE_LAYER_COMPAT # # Begin Mainsail/Fluidd compat # [gcode_macro SET_PAUSE_NEXT_LAYER] description: Enable a pause if the next layer is reached gcode: _KM_CHECK_IS_PRINTING {% set pause_next_layer = printer['gcode_macro SET_PRINT_STATS_INFO'].pause_next_layer %} {% set ENABLE = params.ENABLE | default(1) | int != 0 %} {% set MACRO = params.MACRO | default(pause_next_layer.call, True) %} SET_GCODE_VARIABLE MACRO=SET_PRINT_STATS_INFO VALUE="{ { 'enable': False, 'call': MACRO }}" VARIABLE=pause_next_layer GCODE_AT_LAYER COMMAND="{MACRO|replace('\"','\\\"') }" CANCEL="{0 if ENABLE else 1}" LAYER="next" [gcode_macro SET_PAUSE_AT_LAYER] description: Enable/disable a pause if a given layer number is reached gcode: _KM_CHECK_IS_PRINTING {% set pause_at_layer = printer['gcode_macro SET_PRINT_STATS_INFO'].pause_at_layer %} # This enable logic is copied directly from Fluidd/Mainsail. {% set ENABLE = params.ENABLE | int != 0 if params.ENABLE is defined else params.LAYER is defined %} {% set LAYER = params.LAYER | default((pause_at_layer.layer|int, printer.print_stats.info.current_layer|int + 1)|max)%} {% set MACRO = params.MACRO | default(pause_at_layer.call, True) %} SET_GCODE_VARIABLE MACRO=SET_PRINT_STATS_INFO VARIABLE=pause_at_layer VALUE="{ { 'enable': False, 'layer': 0, 'call': MACRO }}" {% if ENABLE and pause_at_layer.enable %} # Remove the previously scheduled command if we're replacing it. GCODE_AT_LAYER COMMAND="{pause_at_layer.call|replace('\"','\\\"') }" CANCEL="{1}" LAYER="{pause_at_layer.layer}" {% endif %} # Add the new command. GCODE_AT_LAYER COMMAND="{MACRO|replace('\"','\\\"') }" CANCEL="{0 if ENABLE else 1}" LAYER="{LAYER}" [gcode_macro SET_PRINT_STATS_INFO] rename_existing: _KM_SET_PRINT_STATS_INFO description: Overwrite, to get pause_next_layer and pause_at_layer feature variable_pause_next_layer: { 'enable': False, 'call': "PAUSE" } variable_pause_at_layer : { 'enable': False, 'layer': 0, 'call': "PAUSE" } gcode: _KM_SET_PRINT_STATS_INFO {rawparams} [gcode_macro _update_layer_compat] gcode: {% set next_layer = printer.print_stats.info.current_layer|int + 1 %} {% set commands_layer = printer["gcode_macro _km_layer_run"].commands_layer %} {% set keys = commands_layer | sort(reverse=True) %} {% set pause_next_layer = {'enable': False, 'call': printer['gcode_macro SET_PRINT_STATS_INFO'].pause_next_layer.call} %} {% if pause_next_layer.call in commands_layer[next_layer] | default([]) %} {% set dummy = pause_next_layer.__setitem__('enable', True) %} {% endif %} SET_GCODE_VARIABLE MACRO=SET_PRINT_STATS_INFO VARIABLE=pause_next_layer VALUE="{ pause_next_layer|replace('\"','\\\"') }" # Don't just make pause_at_layer a copy of pause_next_layer. {% set pause_at_layer = {'enable': False, 'layer': 0, 'call': printer['gcode_macro SET_PRINT_STATS_INFO'].pause_at_layer.call} %} {% if pause_next_layer.enable and pause_next_layer.call == pause_at_layer.call %} {% set keys = keys | reject("==", next_layer) %} {% endif %} # We iterate through the entire reverse sorted key list because Jinja2 doesn't # have a filter for dict values. It's ugly, but there should be only a few # items in the dict. {% for key in keys %} {% if pause_at_layer.call in commands_layer[key] %} {% set dummy = pause_at_layer.__setitem__('enable', True) %} {% set dummy = pause_at_layer.__setitem__('layer', key) %} {% endif %} {% endfor %} SET_GCODE_VARIABLE MACRO=SET_PRINT_STATS_INFO VARIABLE=pause_at_layer VALUE="{ pause_at_layer|replace('\"','\\\"') }" # # End Mainsail/Fluidd compat # [gcode_macro pause_next_layer] description: Convenience macro to schedule the current print to pause at the next layer change. See PAUSE for additional arguments. Usage: PAUSE_NEXT_LAYER ... gcode: _CHECK_KINEMATIC_LIMITS{% for k in params%}{' ' ~k~ '=' ~ params[k] }{% endfor %} GCODE_AT_LAYER LAYER=NEXT COMMAND="PAUSE{% for k in params %}{ ' ' ~ k ~ '=' ~ params[k]}{% endfor %}" [gcode_macro pause_at_layer] description: Convenience macro to schedule the current print to pause at the specified layer change. LAYER=next will cause the command to run at the next layer change. See PAUSE for additional arguments. Usage: PAUSE_AT_LAYER { HEIGHT= | LAYER= } ... gcode: # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.LAYER|default(layer number)|float %} {% set dummy = params.HEIGHT|default(Z height)|int %} " %} # End argument block for Mainsail {% set filtered_params = params|reject('in',['HEIGHT','LAYER'])|list|sort %} _CHECK_KINEMATIC_LIMITS{% for k in filtered_params%}{' ' ~k~ '=' ~ params[k] }{% endfor %} GCODE_AT_LAYER {% for k in params|select('in',['HEIGHT','LAYER'])|list %}{ ' ' ~ k ~ '=' ~ params[k] }{% endfor %} COMMAND="PAUSE{% for k in filtered_params %}{ ' ' ~ k ~ '=' ~ params[k]}{% endfor %}" [gcode_macro speed_at_layer] description: Convenience macro to schedule a feedrate adjustment at the specified layer change. LAYER=next will cause the command to run at the next layer change. (SPEED parameter behaves the same as the M220 S parameter.) Usage: SPEED_AT_LAYER { HEIGHT= | LAYER= } SPEED= gcode: {% set SPEED = params.SPEED|default(0)|int %} {% if SPEED < 1 or SPEED > 500 %} {action_raise_error("SPEED[%i] parameter between 1 and 500 is required." % SPEED)} {% endif %} GCODE_AT_LAYER {% for k in params|select('in',['HEIGHT','LAYER'])|list %}{ ' ' ~ k ~ '=' ~ params[k] }{% endfor %} COMMAND="M220 S{SPEED|int}" # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.LAYER|default(layer number)|float %} {% set dummy = params.HEIGHT|default(Z height)|int %} {% set dummy = params.SPEED|default(percentage)|int %} " %} # End argument block for Mainsail [gcode_macro flow_at_layer] description: Convenience macro to schedule a flow percentage adjustment at the specified layer change. LAYER=next will cause the command to run at the next layer change. (FLOW parameter behaves the same as the M221 S parameter.) Usage: FLOW_AT_LAYER { HEIGHT= | LAYER= } FLOW= gcode: {% set FLOW = params.FLOW|default(0)|int %} {% if FLOW < 1 or FLOW > 500 %} {action_raise_error("FLOW[%i] parameter between 1 and 500 is required." % FLOW)} {% endif %} GCODE_AT_LAYER {% for k in params|select('in',['HEIGHT','LAYER'])|list %}{ ' ' ~ k ~ '=' ~ params[k] }{% endfor %} COMMAND="M221 S{FLOW|int}" # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.LAYER|default(layer number)|float %} {% set dummy = params.HEIGHT|default(Z height)|int %} {% set dummy = params.FLOW|default(percentage)|int %} " %} # End argument block for Mainsail [gcode_macro fan_at_layer] description: Convenience macro to schedule a fan adjustment at the specified layer change. LAYER=next will cause the command to run at the next layer change. See SET_FAN_SCALING for additional arguments. Usage: FAN_AT_LAYER { HEIGHT= | LAYER= } ... gcode: # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.LAYER|default(layer number)|float %} {% set dummy = params.HEIGHT|default(Z height)|int %} {% set dummy = params.SCALE|default(1.0)|float %} {% set dummy = params.BUMP|default(0)|int %} {% set dummy = params.MAXIMUM|default(0)|int %} {% set dummy = params.MINIMUM|default(255)|int %} {% set dummy = params.SPEED|default(current speed)|int %} " %} # End argument block for Mainsail {% set filtered_params = params|reject('in',['HEIGHT','LAYER'])|list|sort %} {% if filtered_params|length == 0 %} {action_raise_error("No fan parameters provided.")} {% endif %} _CHECK_FAN_PARAMS{% for k in filtered_params %}{' '~k~'='~params[k] }{% endfor %} GCODE_AT_LAYER {% for k in params|select('in',['HEIGHT','LAYER'])|list %}{ ' ' ~ k ~ '=' ~ params[k] }{% endfor %} COMMAND="SET_FAN_SCALING{% for k in filtered_params %}{ ' ' ~ k ~ '=' ~ params[k]}{% endfor %}" [gcode_macro heater_at_layer] description: Convenience macro to schedule a heater adjustment at the specified layer change. LAYER=next will cause the command to run at the next layer change. See SET_HEATER_SCALING for additional arguments. Usage: HEATER_AT_LAYER { HEIGHT= | LAYER= } ... gcode: # Dummy argument block for Mainsail {% set dummy = None if True else " {% set dummy = params.LAYER|default(layer number)|float %} {% set dummy = params.HEIGHT|default(Z height)|int %} {% set dummy = params.HEATER|default(e.g. extruder) %} {% set dummy = params.SCALE|default(1.0)|float %} {% set dummy = params.BUMP|default(0.0)|float %} {% set dummy = params.MAXIMUM|default(max_temp)|float %} {% set dummy = params.MINIMUM|default(min_temp)|float %} {% set dummy = params.TARGET|default(current target)|float %} " %} # End argument block for Mainsail {% set filtered_params = params|reject('in',['HEIGHT','LAYER'])|list|sort %} _CHECK_HEATER_PARAMS{% for k in filtered_params%}{' ' ~ k ~ '=' ~ params[k] }{% endfor %} GCODE_AT_LAYER{% for k in params|select('in',['HEIGHT','LAYER'])|list %}{ ' ' ~ k ~ '=' ~ params[k] }{% endfor %} COMMAND="SET_HEATER_SCALING{% for k in filtered_params %}{ ' ' ~ k ~ '=\\\"' ~ params[k]|replace('\\','\\\\')|replace('\'','\\\'') |replace('\"','\\\"') ~ '\\\"' }{% endfor %}"