# 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 relatively 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 omitted (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: 400 # 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 # 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: 1.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: 6 # These parameters define your scrubbing, travel speeds, safe z clearance and how many # times you want to wipe. Update as necessary. variable_clearance_z: 2 # When traveling, but not cleaning, the # clearance along the z-axis between nozzle # and brush. variable_wipe_qty: 2 # Number of complete (A complete wipe: left, # right, left OR right, left, right) wipes. variable_travel_spd_xy: 10000 # Travel (not cleaning) speed along x and # y-axis in mm/min. variable_travel_spd_z: 1000 # Travel (not cleaning) speed along z axis # in mm/min. variable_wipe_spd_xy: 10000 # 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: 0 # Blobifier sends the toolhead to the maximum y position during purge operations 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: 67 # For 300mm build #variable_brush_start: 84 # for 350mm build # width of the brush variable_brush_width: 35 # 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: 10 # 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.7 # Servo angles for tray positions variable_tray_angle_out: 0 variable_tray_angle_in: 180 # Increase this value if the servo doesn't have enough time to fully retract or extend variable_dwell_time: 200 # ======================================================================================== # ==================== 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.2 # The amount to raise Z variable_z_raise: 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 # Lift the nozzle slightly after creating the blob te release pressure on the tray. variable_eject_hop: 1.0 # Dwell time (ms) after purging and before cleaning to relieve pressure from the nozzle. variable_pressure_release_time: 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 # Define the part fan name if you are using a fan other than [fan] # Applies to [fan_generic] or other fan definitons # Example would be if you are using auxiliary fan control in Orcaslicer (https://github.com/SoftFever/OrcaSlicer/wiki/Auxiliary-fan) # If you are unsure if you need this, then probably just leave it commented out. #variable_fan_name: "fan_generic fan0" # ======================================================================================== # ==================== 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: 30 # The maximum amount of filament (in mm¹) to purge in a single blob. Blobifier will # automatically purge multiple blobs if the purge amount exceeds this. variable_purge_length_maximum: 150 # 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 wasteful. Tune it here to get optimal values. # 0.6 (60%) is a good starting point. variable_purge_length_modifier: 0.6 # 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: 0 # ======================================================================================== # ==================== BUCKET ============================================================ # ======================================================================================== # Maximum number of blobs that fit in the bucket. Pauses the print if it exceeds this # number. variable_max_blobs: 400 # 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 need 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: 2 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 %} {% set fan = fan_name|string %} # Save the part cooling fan speed to be enabled again later {% set backup_fan_speed = (printer[fan].speed if printer[fan] is defined else 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 sequence_vars = printer['gcode_macro _MMU_SEQUENCE_VARS'] %} {% set park_vars = printer['gcode_macro _MMU_PARK'] %} {% set filament_diameter = printer.configfile.config.extruder.filament_diameter|float %} {% set filament_cross_section = (filament_diameter/2) ** 2 * 3.1415 %} {% set from_tool = printer.mmu.last_tool %} {% set to_tool = printer.mmu.tool %} {% 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 %} # 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 + printer.mmu.extruder_filament_remaining + park_vars.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))} # ====================================================================================== # ==================== PURGING SEQUENCE ================================================ # ====================================================================================== # Set to absolute positioning. G90 # Check for purge length and purge if necessary. {% if purge_len|float > 0 %} # ==================================================================================== # ==================== POSITIONING =================================================== # ==================================================================================== # Retract the tray so it is not in the way BLOBIFIER_SERVO POS=in # 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 - 20, 30]|max} Y{position_y} F{travel_spd_xy} {% endif %} # ==================================================================================== # ==================== 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} {% endif %} # Move over to the tray after z change (For cases when the tool is lower than the tray) G1 X{purge_x} F{travel_spd_xy} # Extend the tray BLOBIFIER_SERVO POS=out # ==================================================================================== # ==================== 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 thorough 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-{park_vars.retracted_length} F{sequence_vars.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 %} # Retract the tray BLOBIFIER_SERVO POS=in RESTORE_GCODE_STATE NAME=BLOBIFIER_state ########################################################################################## # Wipes the nozzle on the brass brush # [gcode_macro BLOBIFIER_CLEAN] gcode: {% set bl = printer['gcode_macro BLOBIFIER'] %} {% set pos_max = printer.toolhead.axis_maximum %} {% set position_y = pos_max.y - bl.skew_correction %} {% 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 bl.brush_accel > 0 %} SET_VELOCITY_LIMIT ACCEL={bl.brush_accel} MINIMUM_CRUISE_RATIO=0.1 {% endif %} {% if pos.z < bl.brush_top + bl.clearance_z %} G1 Z{bl.brush_top + bl.clearance_z} F{bl.travel_spd_z} {% endif %} G1 X{bl.brush_start} F{bl.travel_spd_xy} G1 Y{position_y} G1 Z{bl.brush_top + bl.clearance_z} F{bl.travel_spd_z} # Move nozzle down into brush. G1 Z{bl.brush_top} F{bl.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, (bl.wipe_qty + 1)) %} G1 X{bl.brush_start + bl.brush_width} F{bl.wipe_spd_xy} G1 X{bl.brush_start} F{bl.wipe_spd_xy} {% endfor %} # Move away from the brush, but not onto the tray or in front of the filametrix cutter pin G1 X{[bl.brush_start - 20, 30]|max} F{bl.travel_spd_xy} 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 # Retract the tray BLOBIFIER_SERVO POS=in G1 X{[bl.brush_start - 20, 30]|max} Y{position_y} F{bl.travel_spd_xy} {% if safe.tray or ignore_safe %} G1 Z{bl.tray_top} F{bl.travel_spd_z} {% endif %} G1 X{bl.purge_x} F{bl.travel_spd_xy} # Extend the tray BLOBIFIER_SERVO POS=out {% 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] 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{bl.dwell_time} {% elif pos == "out" %} SET_SERVO SERVO=blobifier ANGLE={bl.tray_angle_out} G4 P{bl.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( "BLOBIFIER: 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! MMU_PAUSE MSG="Empty purge bucket!" {% 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 pos_max = printer.toolhead.axis_maximum %} {% set position_y = pos_max.y - bl.skew_correction %} # 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 %} # Retract the tray BLOBIFIER_SERVO POS=in # 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} 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 - 4} G1 Y{position_y} {% 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 # Extend and retract the tray to test BLOBIFIER_SERVO POS=out BLOBIFIER_SERVO POS=in [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_exp))} {% endif %} # cap user defined accels at printer max_accel if greater {% if bl.shake_accel > printer.configfile.config.printer.max_accel|int %} {action_respond_info("BLOBIFIER: variable_shake_accel has value: %d which is higher than your printer limit of %d. Reduce this if your printer skips steps." % (bl.shake_accel,printer.configfile.config.printer.max_accel|int))} {% endif %} {% if bl.brush_accel > printer.configfile.config.printer.max_accel|int %} {action_respond_info("BLOBIFIER: variable_brush_accel has value: %d which is higher than your printer limit of %d. Reduce this if your printer skips steps." % (bl.brush_accel,printer.configfile.config.printer.max_accel|int))} {% 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}"