Files
klipper-macros/macros/blobifier/blobifier.cfg
T
mangavalkandClaude Opus 5.5 eaf9c82617 Group macros by topic, add Blobifier, fix first tool change
- Split the macro files into folders by topic (print, heating, afc,
  pressure_advance, spoolman, leds, sensors); klipper-macros.cfg is the
  one file printer.cfg includes. Macros moved unchanged; the effective
  config is identical apart from the paths of the PA shell scripts.
- Add the modified Blobifier (Turtleblobifier for AFC) and define
  [save_variables] once in save_variables.cfg instead of in both
  blobifier.cfg and the PA macros (same file, bd_pa_variables.cfg).
- PRINT_WARMUP: heat to the loaded lane's temperature before the first
  tool change. AFC skips heating when the loaded lane's tool number is
  not in the print file's temperature list, and the unload then failed
  below min_extrude_temp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:24:37 +02:00

920 lines
41 KiB
INI

# Include servo hardware definition separately to allow for automatic upgrade
[include ./blobifier_hw.cfg]
##########################################################################################
# Sample config to be used in conjunction with Blobifier Purge Tray, Bucket & Nozzle
# Scrubber mod. Created by Dendrowen (dendrowen on Discord). The Macro is based on a
# version, and Nozzle Scrubber is made by Hernsl (hernsl#8860 on Discord). The device is
# designed around a Voron V2.4 300mm, but should work for 250mm and 350mm too. This
# version only supports the assembly on the rear-left of the bed. If you decide to change
# that, please consider contributing to the project by creating a pull request with the
# needed changes.
# IMPORTANT: The rear-left part of your bed becomes unusable by this mod because the
# toolhead needs to lower down to 0. Be sure not to use the left-rear 130x35mm.
# The goals of this combination of devices is to dispose of purged filament during a
# multicolored print without the need of a purge block and without the flurries of
# filament poops consuming your entire 3D printer room. The Blobifier achieves that by
# purging onto a retractable tray which causes the filament to turn into a tiny blob
# rather then a large spiral. This keeps the waste relativly small. The bucket should be
# able to account for up to 200 filament swaps (for the 300mm V2).
# The Blobifier uses some room at the back-left side of your printer, depending on your
# printer limits and positions. (usually max_pos.y - toolhead_y and brush_start +
# brush_width + toolhead_x). If you do place objects within this region, Blobifier will
# skip purging automatically. It does this by extending the EXCLUDE_OBJECT_* macro's, so
# make sure you have exclude objects enabled in your slicer.
# If your using Blobifier in conjunction with the filament cutter on the stealthburner
# toolhead, you can place the pin at max_pos.y - 7 (e.g., max pos y is 307, place it at
# 300). The pin will then poke through the cavity in your toolhead. (Be careful with
# manually moving the toolhead. I have broken many filament cutter pins)
# It is advised to use the start_gcode from Happy Hare. Then you will be able to fully
# and efficiently use this mod. Check the Happy Hare document at gcode_preprocessing.md
# in the Happy Hare github for more details.
###################################### DISCLAIMER ########################################
# You, and you alone, are responsible for the correct execution of these macros and
# gcodes. Any damage that may occur to your machine remains your responsibility.
# Especially when executing this macro for the first few times, keep an eye on your
# printer and the
# emergency stop.
##########################################################################################
##########################################################################################
# Main macro. Usually you should only need to call this one or place it in the Happy Hare
# _MMU_POST_LOAD macro using the variable_user_post_load_extension:
#
# variable_user_post_load_extension : `BLOBIFIER`
#
# Notes on parameters:
# PURGE_LENGTH=[float] (optional) The length to purge. If ommited (default) it will check
# the purge_volumes matrix or variable_purge_length. This can be used
# to override and for testing.
#
[gcode_macro BLOBIFIER]
# These parameters define your filament purging.
# Note that the control of retraction is set in 'mmu_macro_vars.cfg' which can be increased
# if you experience excessive oozing.
# variable_purge_spd: 250 #300 # Speed, in mm/min, of the purge.
variable_purge_spd: 150 # Speed, in mm/min, of the purge.
variable_purge_temp_min: 200 # Minimum nozzle purge temperature.
variable_toolhead_x: 70 # From the nozzle to the left of your toolhead
variable_toolhead_y: 50 # From the nozzle to the front of your toolhead
variable_retract_before_park: 3.0 # amount in mm. 0 to disable
variable_retract_speed: 2000 # mm/m
##custom
variable_retracted_length: 0.4 # Amount of current retraction
# This macro will prevent a gcode movement downward while 'blobbing' if there might be a
# print in the way (e.g. You print something large and need the area where Blobifier does
# its... 'business'). However, at low heights (or at print start) this might not be
# desireable. You can force a 'safe descend' with this variable. Keep in mind that the
# height of the print is an estimation based on previous heights and certain assumptions
# so it might be wise to include a safety margin of 0.2mm
variable_force_safe_descend_height_until: 2.0
# Adjust this so that your nozzle scrubs within the brush. Be careful not to go too low!
# Start out with a high value (like, 6) and go
# down from there.
variable_brush_top: -0.5
# These parameters define your scrubbing, travel speeds, safe z clearance and how many
# times you want to wipe. Update as necessary.
variable_clearance_z: 5 # When traveling, but not cleaning, the
# clearance along the z-axis between nozzle
# and brush.
variable_wipe_qty: 4 # Number of complete (A complete wipe: left,
# right, left OR right, left, right) wipes.
variable_travel_spd_xy: 25000 # Travel (not cleaning) speed along x and
# y-axis in mm/min.
variable_travel_spd_z: 2000 # Travel (not cleaning) speed along z axis
# in mm/min.
variable_wipe_spd_xy: 30000 # Nozzle wipe speed in mm/min.
# The acceleration to use when using the brush action. If set to 0, it uses the already
# set acceleration. However, in some cases this is not desirable for the last motion
# could be an 'outer contour' acceleration which is usually lower.
variable_brush_accel: 10000
# Blobifier sends the toolhead to the maximum y position during purge oeprations and
# minimum x position during shake operations. This can cause issues when skew correction
# is set up. If you have skew correction enabled and get 'move out of range' errors
# regarding blobifier while skew is enabled, try increasing this value. Keep the
# adjustments small though! (0.1mm - 0.5mm) and increase it until it works.
variable_skew_correction: 0.1
# These parameters define the size of the brush. Update as necessary. A visual reference
# is provided below.
#
# ← brush_width →
# _________________
# | | ↑ Y position is acquired from your
# brush_start (x) | | brush_depth stepper_y position_max. Adjust
# |_________________| ↓ your brush physically in Y so
# (y) that the nozzle scrubs within the
# brush_front brush.
# __________________________________________________________
# PRINTER FRONT
#
#
# Start location of the brush. Defaults for 250, 300 and 350mm are provided below.
# Uncomment as necessary
#variable_brush_start: 34 # For 250mm build
#variable_brush_start: 52 # For 300mm build
# variable_brush_start: 84 # for 350mm build
variable_brush_start: 48 # for 350mm build
# width of the brush
variable_brush_width: 45
# Location of where to purge. The tray is 15mm in length, so if you assemble it against
# the side of the bed (default), 10mm is a good location
variable_purge_x: 2
# Height of the tray. If it's below your bed, give this a negative number equal to the
# difference. If it's above your bed, give it a positive number. You can find this number
# by homing, optional QGL or equivalent, and moving you toolhead above the tray, and
# lowering it with the paper method.
variable_tray_top: 0
# Servo angles for tray positions
variable_tray_angle_out: 0
variable_tray_angle_in: 180
# ========================================================================================
# ==================== BLOB TUNING =======================================================
# ========================================================================================
# The following section defines how the purging sequence is executed. This is where you
# tune the purging to create pretty blobs. Refer to the visual reference for a better
# understanding. The visual is populated with example values. Below are some guides
# provided to help with tuning.
#
# \_____________/
# |___|___|
# \_/ ______________ < End of third iteration.
# / \ HEIGHT: 3 x iteration_z_raise - (2 + 1) x iteration_z_change (3 x 5 - 2 x 1.2 = 11.4)
# | | EXTRUDED: 3 x max_iteration_length (3 x 50 = 150)
# / \ ______________ < End of second iteration.
# | \ HEIGHT: 2 x iteration_z_raise - 1 x iteration_z_change (2 x 5 - 1 x 1.2 = 8.8)
# / | EXTRUDED: 2 x max_iteration_length (2 x 50 = 100)
# | \ ______________ < End of first iteration.
# / \ HEIGHT: 1 x iteration_z_raise (1 x 5 = 5)
# | | EXTRUDED: 1 x max_iteration_length (1 x 50 = 50)
#___________ \ / ______________ < Start height of the nozzle. default value: 1.5mm
# |_______________\___________/_ ______________ < Bottom of the tray
# |_____________________________|
# |
#
########################### BLOB TUNING ##############################
# +-------------------------------------+----------------------------+
# | Filament sticks to the nozzle at | Incr. purge start |
# | initial purge (first few mm) | |
# +-------------------------------------+----------------------------+
# | Filament scoots out from under | Incr. temperature |
# | the nozzle at the first iteration | Decr. z_raise |
# | | Incr. purge_length_maximum |
# +-------------------------------------+----------------------------+
# | Filament scoots out from under the | Decr. purge_spd |
# | the nozzle at later iterations | Decr. z_raise_exp |
# | | Decr. z_raise |
# | | Incr. purge_length_maximum |
# +-------------------------------------+----------------------------+
# | Filament sticks to the nozzle at | Incr. z_raise_exp |
# | later iterations | (Not above 1) |
# +-------------------------------------+----------------------------+
#
# The height to raise the nozzle above the tray before purging. This allows any built up
# pressure to escape before the purge.
variable_purge_start: -0.8 #0.2
# The amount to raise Z
variable_z_raise: 12 #12
# As the nozzle gets higher and the blob wider, the Z raise needs to be reduced, this
# follows the following formula:
# (extruded_amount/max_purge_length)^z_raise_exp * z_raise
# 1 is linear, below 1 will cause z to raise less quickly over time, above 1 will make it
# raise quicker over time. 0.85 is a good starting point and you should not have it above 1
variable_z_raise_exp: 0.85 #0.85
# Lift the nozzle slightly after creating the blob te release pressure on the tray.
variable_eject_hop: 6.0
# Dwell time (ms) after purging and before cleaning to relieve pressure from the nozzle.
variable_pressure_release_time: 3000 #1000
# Set the part cooling fan speed. Disabling can help prevent the nozzle from cooling down
# and stimulate flow, Enabling it can prevent blobs from sticking together. Values range
# from 0 .. 1, or -1 if you don't want it changed.
#variable_part_cooling_fan: -1 # Leave it unchanged
#variable_part_cooling_fan: 0 # Disable the fan
variable_part_cooling_fan: 1 # Run it at full speed
# ========================================================================================
# ==================== PURGE LENGTH TUNING ===============================================
# ========================================================================================
# The absolute minimum to purge, even if you don't changed tools. This is to prime the
# nozzle before printing
variable_purge_length_minimum: 35
# The maximum amount of filament (in mm¹) to purge in a single blob. Blobifier will
# automatically purge multiple blobs if the purge amount exeeds this.
variable_purge_length_maximum: 80 #120
# Default purge length to fall back on when neither the tool map purge_volumes or
# parameter PURGE_LENGTH is set.
variable_purge_length: 150
# The slicer values often are a bit too wastefull. Tune it here to get optimal values.
# 0.6 (60%) is a good starting point.
variable_purge_length_modifier: 0.5
# Fixed length of filament to add after the purge volume calculation. Happy Hare already
# shares info on the extra amount of filament to purge based on known residual filament,
# tip cutting fragment and initial retraction setting. However this setting can add a fixed
# amount on top on that if necessary although it is recommended to start with 0 and tune
# slicer purge matrix first.
# When should you alter this value:
# INCREASE: When the dark to light swaps are good, but light to dark aren't.
# DECREASE: When the light to dark swaps are good, but dark to light aren't. Don't
# forget to increase the purge_length_modifier
variable_purge_length_addition: 20 # 30
# ========================================================================================
# ==================== BUCKET ============================================================
# ========================================================================================
# Maximum number of blobs that fit in the bucket. Pauses the print if it exceeds this
# number.
variable_max_blobs: 1300
# Enable the bucket shaker. You need to have the shaker.stl installed
variable_enable_shaker: 1
# The number of back-and-forth motions of one shake
variable_bucket_shakes: 10
# During shaking acceleration can often be higher because you don't neeed to keep print
# quality in mind. Higher acceleration helps better with dispersing the blobs.
variable_shake_accel: 10000
# The frequency at which to shake the bucket. A decimal value ranging from 0 to 1, where 0
# is never, and 1 is every time. This way the shaking occurs more often as the bucket
# fills up. Sensible values range from 0.75 to 0.95
variable_bucket_shake_frequency: 0.95
# Height of the shaker arm. If your hotend hits your tray during shaking, increase.
variable_shaker_arm_z: 4
gcode:
# ======================================================================================
# ==================== RECORD STATE (INCL. FANS, SPEEDS, ETC...) =======================
# ======================================================================================
# General state
SAVE_GCODE_STATE NAME=BLOBIFIER_state
# ======================================================================================
# ==================== CHECK HOMING STATUS =============================================
# ======================================================================================
{% if "xyz" not in printer.toolhead.homed_axes %}
RESPOND MSG="BLOBIFIER: Not homed! Home xyz before blobbing"
{% elif printer.quad_gantry_level and printer.quad_gantry_level.applied == False %}
RESPOND MSG="BLOBIFIER: QGL not applied! run quad_gantry_level before blobbing"
{% else %}
# Part cooling fan
{% if part_cooling_fan >= 0 %}
# Save the part cooling fan speed to be enabled again later
{% set backup_fan_speed = printer.fan.speed %}
# Set part cooling fan speed
M106 S{part_cooling_fan * 255}
{% endif %}
# Set feedrate to 100% for correct speed purging
{% set backup_feedrate = printer.gcode_move.speed_factor %}
M220 S100
# ======================================================================================
# ==================== DEFINE BASIC VARIABLES ==========================================
# ======================================================================================
{% set park_vars = printer['gcode_macro AFC_PARK'] %}
{% set filament_diameter = printer.configfile.config.extruder.filament_diameter|float %}
{% set filament_cross_section = (filament_diameter/2) ** 2 * 3.1415 %}
{% set bl_count = printer['gcode_macro _BLOBIFIER_COUNT'] %}
{% set pos = printer.gcode_move.gcode_position %}
{% set safe = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'] %}
{% set ignore_safe = safe.print_height < force_safe_descend_height_until %}
{% set restore_z = [printer['gcode_macro BLOBIFIER_PARK'].restore_z,pos.z]|max %}
{% set pos_max = printer.toolhead.axis_maximum %}
{% set position_y = pos_max.y - skew_correction %}
{% set bl = printer['gcode_macro BLOBIFIER'] %}
# Get purge volumes from the slicer (if set up right. see
# https://github.com/moggieuk/Happy-Hare/wiki/Gcode-Preprocessing)
# {% set pv = printer.mmu.slicer_tool_map.purge_volumes %}
# ======================================================================================
# ==================== DETERMINE PURGE LENGTH ==========================================
# ======================================================================================
{% if params.PURGE_LENGTH %} # =============== PARAM PURGE LENGTH ======================
{action_respond_info("BLOBIFIER: param PURGE_LENGTH provided")}
{% set purge_len = params.PURGE_LENGTH|float %}
# {% elif from_tool == to_tool and to_tool >= 0 %} # ==== TOOL DIDN'T CHANGE =============
# {action_respond_info("BLOBIFIER: Tool didn't change (T%s > T%s), %s" % (from_tool, to_tool, "priming" if purge_length_minimum else "skipping"))}
# {% set purge_len = 0 %}
# {% elif pv %} # ============== FETCH FROM HAPPY HARE (LIKELY FROM SLICER) ==============
# {% if from_tool < 0 and to_tool >= 0%}
# {action_respond_info("BLOBIFIER: from tool unknown. Finding largest value for T? > T%d" % to_tool)}
# {% set purge_vol = pv|map(attribute=to_tool)|max %}
# {% elif to_tool < 0 %}
# {action_respond_info("BLOBIFIER: tool(s) unknown. Finding largest value")}
# {% set purge_vol = pv|map('max')|max %}
# {% else %}
# {% set purge_vol = pv[from_tool][to_tool]|float * purge_length_modifier %}
# {action_respond_info("BLOBIFIER: Swapped T%s > T%s" % (from_tool, to_tool))}
# {% endif %}
# {% set purge_len = purge_vol / filament_cross_section %}
# {% set purge_len = purge_len + printer.mmu.extruder_filament_remaining + park_vars.retracted_length + purge_length_addition %}
{% else %} # ========================= USE CONFIG VARIABLE =============================
{action_respond_info("BLOBIFIER: No toolmap or PURGE_LENGTH. Using default")}
{% set purge_len = purge_length|float + bl.retracted_length %}
{% endif %}
# # ==================================== APPLY PURGE MINIMUM =============================
{% set purge_len = [purge_len,purge_length_minimum]|max|round(0, 'ceil')|int %}
{action_respond_info("BLOBIFIER: Purging %dmm of filament" % (purge_len))}
{% if params.PURGE_LENGTH %}
{% set purge_len = params.PURGE_LENGTH|float %}
{% else %}
{% set purge_len = purge_length %}
{% endif %}
# Apply purge minimum
#{% set purge_len = [purge_len, purge_length_minimum]|max %}
{% set purge_len = [purge_len,purge_length_minimum]|max|round(0, 'ceil')|int %}
{action_respond_info("BLOBIFIER: Purging %dmm of filament" % (purge_len))}
# ======================================================================================
# ==================== PURGING SEQUENCE ================================================
# ======================================================================================
# Set to absolute positioning.
G90
# Check for purge length and purge if necessary.
{% if purge_len|float > 0 %}
# ====================================================================================
# ==================== POSITIONING ===================================================
# ====================================================================================
# Move to the assembly, first a bit more to the right (brush_start) to avoid a
# potential filametrix pin if it's not already on the same Y coordinate.
{% if printer.toolhead.position.y != position_y %}
G1 X{brush_start} Y{position_y} F{travel_spd_xy}
{% endif %}
# Move over to the tray
G1 X{purge_x} F{travel_spd_xy}
# ====================================================================================
# ==================== BUCKET SHAKE ==================================================
# ====================================================================================
{% if enable_shaker and (safe.shake or ignore_safe) %}
{% if (bl_count.current_blobs + 1) >= bl_count.next_shake %}
BLOBIFIER_SHAKE_BUCKET SHAKES={bucket_shakes}
_BLOBIFIER_CALCULATE_NEXT_SHAKE
{% endif %}
{% endif %}
# ====================================================================================
# ==================== POSITIONING ON TRAY ===========================================
# ====================================================================================
{% if safe.tray or ignore_safe %}
G1 Z{tray_top + purge_start} F{travel_spd_z}
# Extend the blobifier
BLOBIFIER_SERVO POS=out
{% endif %}
# ====================================================================================
# ==================== HEAT HOTEND ===================================================
# ====================================================================================
{% if printer.extruder.temperature < purge_temp_min %}
{% if printer.extruder.target < purge_temp_min %}
M109 S{purge_temp_min}
{% else %}
TEMPERATURE_WAIT SENSOR=extruder MINIMUM={purge_temp_min}
{% endif %}
{% endif %}
# ====================================================================================
# ==================== START ITERATING ===============================================
# ====================================================================================
# Calculate total number of iterations based on the purge length and the max_iteration
# length.
{% set blobs = (purge_len / purge_length_maximum)|round(0, 'ceil')|int %}
{% set purge_per_blob = purge_len|float / blobs %}
{% set retracts_per_blob = (purge_per_blob / 40)|round(0, 'ceil')|int %}
{% set purge_per_retract = (purge_per_blob / retracts_per_blob)|int %}
{% set pulses_per_retract = (purge_per_blob / retracts_per_blob / 5)|round(0, 'ceil')|int %}
{% set pulses_per_blob = (purge_per_blob / 5)|round(0, 'ceil')|int %}
{% set purge_per_pulse = purge_per_blob / pulses_per_blob %}
{% set pulse_time_constant = purge_per_pulse * 0.95 / purge_spd / (purge_per_pulse * 0.95 / purge_spd + purge_per_pulse * 0.05 / 50) %}
{% set pulse_duration = purge_per_pulse / purge_spd %}
# Repeat the process until purge_len is reached
{% for blob in range(blobs) %}
RESPOND MSG={"'BLOBIFIER: Blob %d of %d (%.1fmm)'" % (blob + 1, blobs, purge_per_blob)}
{% if safe.tray or ignore_safe %}
G1 Z{tray_top + purge_start} F{travel_spd_z}
{% endif %}
# relative positioning
G91
# relative extrusion
M83
# Purge filament in a pulsating motion to purge the filament quicker and better
{% for pulse in range(pulses_per_blob) %}
# Calculations to determine z-speed
{% set purged_this_blob = pulse * purge_per_pulse %}
{% set z_last_pos = purge_start + ((purged_this_blob)/purge_length_maximum)**z_raise_exp * z_raise %}
{% set z_pos = purge_start + ((purged_this_blob + purge_per_pulse)/purge_length_maximum)**z_raise_exp * z_raise %}
{% set z_up = z_pos - z_last_pos %}
{% set speed = z_up / pulse_duration %}
# Purge quickly
G1 Z{z_up * pulse_time_constant} E{purge_per_pulse * 0.95} F{speed}
# Purge a tiny bit slowly
G1 Z{z_up * (1 - pulse_time_constant)} E{purge_per_pulse * 0.05} F{speed}
# retract and unretract filament every now and then for thourough cleaning
{% if pulse % pulses_per_retract == 0 and pulse > 0 %}
G1 E-2 F1800
G1 E2 F800
{% endif %}
{% endfor %}
# Retract to match what Happy Hare is expecting
G1 E-{bl.retracted_length} F{bl.retract_speed * 60}
# ==================================================================================
# ==================== DEPOSIT BLOB ================================================
# ==================================================================================
{% if safe.tray or ignore_safe %}
# Raise z a bit to relieve pressure on the blob preventing it to go sideways
G1 Z{eject_hop} F{travel_spd_z}
# Retract the tray
BLOBIFIER_SERVO POS=in
# Move the toolhead down to purge_start height lowering the blob below the tray
G90 # absolute positioning
G1 Z{tray_top} F{travel_spd_z}
# Extend the tray to 'cut off' the blob and prepare for the next blob
BLOBIFIER_SERVO POS=out
BLOBIFIER_SERVO POS=in
BLOBIFIER_SERVO POS=out
# Keep track of the # of blobs
_BLOBIFIER_COUNT
{% endif %}
{% endfor %}
{% endif %}
{% if safe.tray or ignore_safe %}
G1 Z{tray_top + 1} F{travel_spd_z}
G4 P{pressure_release_time}
{% endif %}
{% if safe.brush or ignore_safe %}
BLOBIFIER_CLEAN
{% else %}
G1 X{brush_start} F{travel_spd_xy}
{% endif %}
# ======================================================================================
# ==================== RESTORE STATE ===================================================
# ======================================================================================
G90 # absolute positioning
G1 Z{restore_z} F{travel_spd_z}
{% if part_cooling_fan >= 0 %}
# Reset part cooling fan if it was changed
M106 S{(backup_fan_speed * 255)|int}
{% endif %}
M220 S{(backup_feedrate * 100)|int}
{% endif %}
RESTORE_GCODE_STATE NAME=BLOBIFIER_state
##########################################################################################
# Wipes the nozzle on the brass brush
#
[gcode_macro BLOBIFIER_CLEAN]
gcode:
{% set bb = printer['gcode_macro BLOBIFIER'] %}
{% set position_y = printer.configfile.config["stepper_y"]["position_max"]|float %}
{% set original_accel = printer.toolhead.max_accel %}
{% set original_minimum_cruise_ratio = printer.toolhead.minimum_cruise_ratio %}
{% set pos = printer.gcode_move.gcode_position %}
SAVE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state
G90
{% if bb.brush_accel > 0 %}
SET_VELOCITY_LIMIT ACCEL={bb.brush_accel} MINIMUM_CRUISE_RATIO=0.1
{% endif %}
{% if pos.z < bb.brush_top + bb.clearance_z %}
G1 Z{bb.brush_top + bb.clearance_z} F{bb.travel_spd_z}
{% endif %}
G1 X{bb.brush_start} F{bb.travel_spd_xy}
G1 Y{position_y}
G1 Z{bb.brush_top + bb.clearance_z} F{bb.travel_spd_z}
# Move nozzle down into brush.
G1 Z{bb.brush_top} F{bb.travel_spd_z}
SET_VELOCITY_LIMIT ACCEL={original_accel} MINIMUM_CRUISE_RATIO={original_minimum_cruise_ratio}
# Perform wipe. Wipe direction based off bucket_pos for cool random scrubby routine.
{% for wipes in range(1, (bb.wipe_qty + 1)) %}
G1 X{bb.brush_start + bb.brush_width} F{bb.wipe_spd_xy}
G1 X{bb.brush_start} F{bb.wipe_spd_xy}
{% endfor %}
{% if pos.z < 10 %}
G1 Z10
{% else %}
G1 Z{pos.z}
{% endif %}
RESTORE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state
##########################################################################################
# Park the nozzle on the tray to prevent oozing during filament swaps. Place this
# extension in the post_form_tip extension in mmu_macro_vars.cfg:
# variable_user_post_form_tip_extension: "BLOBIFIER_PARK"
#
[gcode_macro BLOBIFIER_PARK]
variable_restore_z: 0
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
{% set pos = printer.gcode_move.gcode_position %}
{% set safe = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'] %}
{% set pos_max = printer.toolhead.axis_maximum %}
{% set position_y = pos_max.y - bl.skew_correction %}
SET_GCODE_VARIABLE MACRO=BLOBIFIER_PARK VARIABLE=restore_z VALUE={pos.z}
SAVE_GCODE_STATE NAME=blobifier_park_state
{% if "xyz" in printer.toolhead.homed_axes and printer.quad_gantry_level and printer.quad_gantry_level.applied %}
G90
G1 X{bl.brush_start} Y{position_y} F{bl.travel_spd_xy}
G1 X{bl.purge_x}
{% if safe.tray or ignore_safe %}
G1 Z{bl.tray_top} F{bl.travel_spd_z}
{% endif %}
{% else %}
RESPOND MSG="Please home (and QGL) before parking"
{% endif %}
RESTORE_GCODE_STATE NAME=blobifier_park_state
##########################################################################################
# Retract or extend the tray
# POS=[in|out] Retractor extend the tray
#
[gcode_macro BLOBIFIER_SERVO]
# Increase this value if the servo doesn't have enough time to fully retract or extend
# variable_dwell_time: 200
variable_dwell_time: 1000
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
{% set pos = params.POS %}
{% if pos == "in" %}
SET_SERVO SERVO=blobifier ANGLE={bl.tray_angle_in}
G4 P{dwell_time}
{% elif pos == "out" %}
SET_SERVO SERVO=blobifier ANGLE={bl.tray_angle_out}
G4 P{dwell_time}
{% else %}
{action_respond_info("BLOBIFIER: provide POS=[in|out]")}
{% endif %}
SET_SERVO SERVO=blobifier WIDTH=0
##########################################################################################
# Define exclude objects for those who haven't already
#
[exclude_object]
##########################################################################################
# Overwrite the existing EXCLUDE_OBJECT_DEFINE to also check for safe descend.
#
[gcode_macro EXCLUDE_OBJECT_DEFINE]
rename_existing: _EXCLUDE_OBJECT_DEFINE
gcode:
# only reset on the first object at the beginning of a print
{% if printer.exclude_object.objects|length < 1 %}
_BLOBIFIER_RESET_SAFE_DESCEND
{% endif %}
_EXCLUDE_OBJECT_DEFINE {rawparams}
_BLOBIFIER_SAFE_DESCEND
UPDATE_DELAYED_GCODE ID=BLOBIFIER_SHOW_SAFE_DESCEND DURATION=1
[delayed_gcode BLOBIFIER_SHOW_SAFE_DESCEND]
gcode:
{% set safe = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'] %}
{action_respond_info(
"BLOBIIER: Safe descend possible:\n - tray: %s\n - brush: %s\n - shake: %s" %
(
"yes" if safe.tray else "no",
"yes" if safe.brush else "no",
"yes" if safe.shake else "no"
)
)}
##########################################################################################
# Use the EXCLUDE_OBJECT_START gcode macro to record the current height
#
[gcode_macro EXCLUDE_OBJECT_START]
rename_existing: _EXCLUDE_OBJECT_START
gcode:
_EXCLUDE_OBJECT_START {rawparams}
{% if printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'].first_layer %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=first_layer VALUE=False
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_height VALUE={printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'].print_layer_height}
{% else %}
{% set pos = printer.gcode_move.gcode_position %}
{% set last_height = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'].print_previous_height|float %}
{% if pos.z > last_height %}
{% set last_layer = (pos.z - last_height)|round(2) %}
{% set print_height = (pos.z + last_layer)|round(2) %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_previous_height VALUE={pos.z}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_height VALUE={print_height}
{% endif %}
{% endif %}
##########################################################################################
# Reset the safe descend variables.
#
[gcode_macro _BLOBIFIER_RESET_SAFE_DESCEND]
gcode:
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=tray VALUE=True
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=brush VALUE=True
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=shake VALUE=True
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=first_layer VALUE=True
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_height VALUE=0
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_previous_height VALUE=0
##########################################################################################
# Determine if it is safe to drop the toolhead (e.g. not hit a print)
#
[gcode_macro _BLOBIFIER_SAFE_DESCEND]
variable_tray: True # Assume it is safe
variable_brush: True
variable_shake: True
variable_first_layer: True
variable_print_height: 0
variable_print_previous_height: 0
variable_print_layer_height: 0.3
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
{% set pos_max = printer.toolhead.axis_maximum %}
{% set position_y = pos_max.y - bl.skew_correction %}
{% set tray = [bl.purge_x + bl.toolhead_x, position_y - bl.toolhead_y] %}
{% set brush = [bl.brush_start + bl.brush_width + bl.toolhead_x, position_y - bl.toolhead_y] %}
{% set shake = [bl.purge_x + bl.toolhead_x, position_y - bl.toolhead_y - 4] %}
{% set objects = printer.exclude_object.objects | map(attribute='polygon') %}
{% for polygon in objects %}
{% for point in polygon %}
{% if point[0] < tray[0] and point[1] > tray[1] %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=tray VALUE=False
{% endif %}
{% if point[0] < brush[0] and point[1] > brush[1] %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=brush VALUE=False
{% endif %}
{% if point[0] < shake[0] and point[1] > shake[1] %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=shake VALUE=False
{% endif %}
{% endfor %}
{% endfor %}
##########################################################################################
# Increment the blob count with 1 and check if the bucket is full. Pause
# the printer if it is.
#
[gcode_macro _BLOBIFIER_COUNT]
# Don't change these variables
variable_current_blobs: 0
variable_last_shake: 0
variable_next_shake: 0
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
{% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %}
{% if current_blobs >= bl.max_blobs %}
{action_respond_info("BLOBIFIER: Empty purge bucket!")}
M117 Empty purge bucket!
PAUSE
{% else %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE={current_blobs + 1}
_BLOBIFIER_SAVE_STATE
{action_respond_info(
"BLOBIFIER: Blobs in bucket: %s/%s. Next shake @ %s"
% (current_blobs + 1, bl.max_blobs, next_shake)
)}
{% endif %}
##########################################################################################
# Reset the blob count to 0
#
[gcode_macro _BLOBIFIER_COUNT_RESET]
gcode:
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE=0
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE=0
_BLOBIFIER_SAVE_STATE
_BLOBIFIER_CALCULATE_NEXT_SHAKE
##########################################################################################
# Shake the blob bucket to disperse the blobs
#
[gcode_macro BLOBIFIER_SHAKE_BUCKET]
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
{% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %}
{% set original_accel = printer.toolhead.max_accel %}
{% set original_minimum_cruise_ratio = printer.toolhead.minimum_cruise_ratio %}
{% set position_x = bl.skew_correction %}
{% if "xyz" not in printer.toolhead.homed_axes %}
{action_raise_error("BLOBIFIER: Not homed. Home xyz first")}
{% endif %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={count.current_blobs}
_BLOBIFIER_SAVE_STATE
SAVE_GCODE_STATE NAME=shake_bucket
M400
M117 (^_^)
G90
{% set shakes = params.SHAKES|default(10)|int %}
{% set position_y = printer.configfile.config["stepper_y"]["position_max"]|float %}
# move to save y if not already there
{% if printer.toolhead.position.y != position_y %}
G1 X{bl.brush_start} Y{position_y} F{bl.travel_spd_xy}
{% endif %}
# move up a bit to prevent oozing on base
G1 Z{bl.shaker_arm_z} F{bl.travel_spd_z}
# slide into the slot
# G1 X{position_x} F{bl.travel_spd_xy}
G1 X4
M400
M117 (+(+_+)+)
SET_VELOCITY_LIMIT ACCEL={bl.shake_accel} MINIMUM_CRUISE_RATIO=0.1
# Shake away!
{% for shake in range(1, shakes) %}
G1 Y{position_y - 10} F3600
G1 Y{position_y} F30000
{% endfor %}
SET_VELOCITY_LIMIT ACCEL={original_accel} MINIMUM_CRUISE_RATIO={original_minimum_cruise_ratio}
# move out of slot
G1 X{bl.purge_x}
M400
M117 (X_x)
RESTORE_GCODE_STATE NAME=shake_bucket
##########################################################################################
# Calculate when the bucket should be shaken.
#
[gcode_macro _BLOBIFIER_CALCULATE_NEXT_SHAKE]
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
{% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %}
{% set remaining_blobs = bl.max_blobs - count.last_shake %}
{% set next_shake = (1 - bl.bucket_shake_frequency) * remaining_blobs + count.last_shake %}
_BLOBIFIER_SAVE_STATE
_BLOBIFIER_SET_NEXT_SHAKE VALUE={next_shake|int}
##########################################################################################
# Set when the bucket should be shaken next
# VALUE=[int] At what amount of blobs should it be shaken
#
[gcode_macro _BLOBIFIER_SET_NEXT_SHAKE]
gcode:
{% if params.VALUE %}
{% set next_shake = params.VALUE %}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=next_shake VALUE={next_shake}
_BLOBIFIER_SAVE_STATE
{% else %}
{action_respond_info("BLOBIFIER: Provide parameter VALUE=")}
{% endif %}
##########################################################################################
# Some sanity checks
#
[delayed_gcode BLOBIFIER_INIT]
initial_duration: 5.0
gcode:
_BLOBIFIER_INIT
BLOBIFIER_SERVO POS=out
[gcode_macro _BLOBIFIER_INIT]
gcode:
{% set bl = printer['gcode_macro BLOBIFIER'] %}
# Valid part cooling fan setting
{% if bl.part_cooling_fan != -1 and (bl.part_cooling_fan < 0 or bl.part_cooling_fan > 1) %}
{action_emergency_stop("BLOBIFIER: Value %f is invalid for variable part_cooling_fan. Either -1 or a value from 0 .. 1 is valid." % (bl.part_cooling_fan))}
{% endif %}
# Valid bucket shake frequency
{% if bl.bucket_shake_frequency < 0 or bl.bucket_shake_frequency > 1 %}
{action_emergency_stop("BLOBIFIER: Value %f is invalid for variable bucket_shake_frequency. Change it to a value between 0 .. 1" % (bl.bucket_shake_frequency))}
{% endif %}
# Check if position is on 'next'
{% if printer.mmu %}
{% if printer['gcode_macro _MMU_SEQUENCE_VARS'].restore_xy_pos != 'next' %}
{action_respond_info("BLOBIFIER: If not using a wipe tower, consider setting restore_xy_pos: 'next' in mmu_macro_vars.cfg")}
{% endif %}
{% endif %}
# Check the z_raise variable for normal values
{% if bl.z_raise < 3 %}
{action_respond_info("BLOBIFIER: variable_z_raise: %f is very low. This is the value z raises in total on a single blob. Make sure the value is correct before continuing." % (bl.z_raise))}
{% endif %}
# Z raise exponent
{% if bl.z_raise_exp > 1 or bl.z_raise_exp < 0.5 %}
{action_respond_info("BLOBIFIER: variable_z_raise_exp has value: %f. This value is out of spec (0.5 ... 1.0)." % (bl.z_raise))}
{% endif %}
[delayed_gcode BLOBIFIER_LOAD_STATE]
initial_duration: 2.0 # Give it some time to boot up
gcode:
{% set sv = printer.save_variables.variables.blobifier %}
{% if sv %}
# Restore state
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={sv.last_shake}
SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE={sv.current_blobs}
{% endif %}
_BLOBIFIER_CALCULATE_NEXT_SHAKE
[gcode_macro _BLOBIFIER_SAVE_STATE]
gcode:
{% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %}
{% set sv = {'current_blobs': count.current_blobs, 'last_shake': count.last_shake} %}
SAVE_VARIABLE VARIABLE=blobifier VALUE="{sv}"
[respond]
# [save_variables] is defined once in ../save_variables.cfg (shared with the PA macros)