├── .gitignore ├── README.md ├── blobifier.cfg ├── blobifier.old.cfg └── blobifier_hw.cfg /.gitignore: -------------------------------------------------------------------------------- 1 | blob_vars.cfg 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Turtleblobifier 2 | 3 | All hardwork has been done by Dendrowen for this mod + code. 4 | 5 | This can be found here: https://github.com/Dendrowen/Blobifier 6 | 7 | I have since taken the code to made it compatable with the AFC for box turtle as vanila Blobifier is desighed to work with HappyHare. 8 | 9 | 10 | ## Instructions: 11 | 1. Clone this project into your printer config dir: 12 | ```git clone https://github.com/ImSundee/Turtleblobifier ~/printer_data/config/blobifier``` 13 | 14 | 2. Inside your printer.cfg add the following include: 15 | ```[include blobifier/blobifier.cfg]``` 16 | 17 | 3. Tell AFC about the new routines in the AFC/AFC.cfg file: 18 | Replace with the following: 19 | ``` 20 | park_cmd: BLOBIFIER_PARK 21 | poop_cmd: BLOBIFIER 22 | ``` 23 | 4. Ensure the following is disabled in AFC/AFC.cfg: 24 | ``` 25 | kick: False 26 | wipe: False 27 | form_tip: False 28 | ``` 29 | * Cut should be hanled by AFC. 30 | 5. Setup Blobifier as normal setting the correct variables in blobifier/blobifier_hw.cfg and blobifier/blobifier.cfg - they come Preset for my printer so becareful. 31 | -------------------------------------------------------------------------------- /blobifier.cfg: -------------------------------------------------------------------------------- 1 | # Include servo hardware definition separately to allow for automatic upgrade 2 | [include blobifier_hw.cfg] 3 | 4 | ########################################################################################## 5 | 6 | # Sample config to be used in conjunction with Blobifier Purge Tray, Bucket & Nozzle 7 | # Scrubber mod. Created by Dendrowen (dendrowen on Discord). The Macro is based on a 8 | # version, and Nozzle Scrubber is made by Hernsl (hernsl#8860 on Discord). The device is 9 | # designed around a Voron V2.4 300mm, but should work for 250mm and 350mm too. This 10 | # version only supports the assembly on the rear-left of the bed. If you decide to change 11 | # that, please consider contributing to the project by creating a pull request with the 12 | # needed changes. 13 | 14 | # IMPORTANT: The rear-left part of your bed becomes unusable by this mod because the 15 | # toolhead needs to lower down to 0. Be sure not to use the left-rear 130x35mm. 16 | 17 | # The goals of this combination of devices is to dispose of purged filament during a 18 | # multicolored print without the need of a purge block and without the flurries of 19 | # filament poops consuming your entire 3D printer room. The Blobifier achieves that by 20 | # purging onto a retractable tray which causes the filament to turn into a tiny blob 21 | # rather then a large spiral. This keeps the waste relativly small. The bucket should be 22 | # able to account for up to 200 filament swaps (for the 300mm V2). 23 | 24 | # The Blobifier uses some room at the back-left side of your printer, depending on your 25 | # printer limits and positions. (usually max_pos.y - toolhead_y and brush_start + 26 | # brush_width + toolhead_x). If you do place objects within this region, Blobifier will 27 | # skip purging automatically. It does this by extending the EXCLUDE_OBJECT_* macro's, so 28 | # make sure you have exclude objects enabled in your slicer. 29 | 30 | # If your using Blobifier in conjunction with the filament cutter on the stealthburner 31 | # toolhead, you can place the pin at max_pos.y - 7 (e.g., max pos y is 307, place it at 32 | # 300). The pin will then poke through the cavity in your toolhead. (Be careful with 33 | # manually moving the toolhead. I have broken many filament cutter pins) 34 | 35 | # It is advised to use the start_gcode from Happy Hare. Then you will be able to fully 36 | # and efficiently use this mod. Check the Happy Hare document at gcode_preprocessing.md 37 | # in the Happy Hare github for more details. 38 | 39 | ###################################### DISCLAIMER ######################################## 40 | 41 | # You, and you alone, are responsible for the correct execution of these macros and 42 | # gcodes. Any damage that may occur to your machine remains your responsibility. 43 | # Especially when executing this macro for the first few times, keep an eye on your 44 | # printer and the 45 | # emergency stop. 46 | 47 | ########################################################################################## 48 | 49 | ########################################################################################## 50 | # Main macro. Usually you should only need to call this one or place it in the Happy Hare 51 | # _MMU_POST_LOAD macro using the variable_user_post_load_extension: 52 | # 53 | # variable_user_post_load_extension : `BLOBIFIER` 54 | # 55 | # Notes on parameters: 56 | # PURGE_LENGTH=[float] (optional) The length to purge. If ommited (default) it will check 57 | # the purge_volumes matrix or variable_purge_length. This can be used 58 | # to override and for testing. 59 | # 60 | [gcode_macro BLOBIFIER] 61 | # These parameters define your filament purging. 62 | # Note that the control of retraction is set in 'mmu_macro_vars.cfg' which can be increased 63 | # if you experience excessive oozing. 64 | variable_purge_spd: 600 # Speed, in mm/min, of the purge. 65 | variable_purge_temp_min: 200 # Minimum nozzle purge temperature. 66 | variable_toolhead_x: 70 # From the nozzle to the left of your toolhead 67 | variable_toolhead_y: 50 # From the nozzle to the front of your toolhead 68 | 69 | variable_retract_before_park: 3.0 # amount in mm. 0 to disable 70 | variable_retract_speed: 2000 # mm/m 71 | 72 | 73 | ##custom 74 | variable_retracted_length: 0.4 # Amount of current retraction 75 | 76 | # This macro will prevent a gcode movement downward while 'blobbing' if there might be a 77 | # print in the way (e.g. You print something large and need the area where Blobifier does 78 | # its... 'business'). However, at low heights (or at print start) this might not be 79 | # desireable. You can force a 'safe descend' with this variable. Keep in mind that the 80 | # height of the print is an estimation based on previous heights and certain assumptions 81 | # so it might be wise to include a safety margin of 0.2mm 82 | variable_force_safe_descend_height_until: 1.0 83 | 84 | # Adjust this so that your nozzle scrubs within the brush. Be careful not to go too low! 85 | # Start out with a high value (like, 6) and go 86 | # down from there. 87 | variable_brush_top: 3.4 88 | 89 | # These parameters define your scrubbing, travel speeds, safe z clearance and how many 90 | # times you want to wipe. Update as necessary. 91 | variable_clearance_z: 2 # When traveling, but not cleaning, the 92 | # clearance along the z-axis between nozzle 93 | # and brush. 94 | variable_wipe_qty: 4 # Number of complete (A complete wipe: left, 95 | # right, left OR right, left, right) wipes. 96 | variable_travel_spd_xy: 25000 # Travel (not cleaning) speed along x and 97 | # y-axis in mm/min. 98 | variable_travel_spd_z: 2000 # Travel (not cleaning) speed along z axis 99 | # in mm/min. 100 | variable_wipe_spd_xy: 30000 # Nozzle wipe speed in mm/min. 101 | 102 | # The acceleration to use when using the brush action. If set to 0, it uses the already 103 | # set acceleration. However, in some cases this is not desirable for the last motion 104 | # could be an 'outer contour' acceleration which is usually lower. 105 | variable_brush_accel: 10000 106 | 107 | # Blobifier sends the toolhead to the maximum y position during purge oeprations and 108 | # minimum x position during shake operations. This can cause issues when skew correction 109 | # is set up. If you have skew correction enabled and get 'move out of range' errors 110 | # regarding blobifier while skew is enabled, try increasing this value. Keep the 111 | # adjustments small though! (0.1mm - 0.5mm) and increase it until it works. 112 | variable_skew_correction: 0.1 113 | 114 | # These parameters define the size of the brush. Update as necessary. A visual reference 115 | # is provided below. 116 | # 117 | # ← brush_width → 118 | # _________________ 119 | # | | ↑ Y position is acquired from your 120 | # brush_start (x) | | brush_depth stepper_y position_max. Adjust 121 | # |_________________| ↓ your brush physically in Y so 122 | # (y) that the nozzle scrubs within the 123 | # brush_front brush. 124 | # __________________________________________________________ 125 | # PRINTER FRONT 126 | # 127 | # 128 | # Start location of the brush. Defaults for 250, 300 and 350mm are provided below. 129 | # Uncomment as necessary 130 | #variable_brush_start: 34 # For 250mm build 131 | variable_brush_start: 52 # For 300mm build 132 | #variable_brush_start: 84 # for 350mm build 133 | 134 | # width of the brush 135 | variable_brush_width: 45 136 | 137 | # Location of where to purge. The tray is 15mm in length, so if you assemble it against 138 | # the side of the bed (default), 10mm is a good location 139 | variable_purge_x: 4 140 | 141 | # Height of the tray. If it's below your bed, give this a negative number equal to the 142 | # difference. If it's above your bed, give it a positive number. You can find this number 143 | # by homing, optional QGL or equivalent, and moving you toolhead above the tray, and 144 | # lowering it with the paper method. 145 | variable_tray_top: 1.4 146 | 147 | # Servo angles for tray positions 148 | variable_tray_angle_out: 0 149 | variable_tray_angle_in: 180 150 | 151 | # ======================================================================================== 152 | # ==================== BLOB TUNING ======================================================= 153 | # ======================================================================================== 154 | 155 | # The following section defines how the purging sequence is executed. This is where you 156 | # tune the purging to create pretty blobs. Refer to the visual reference for a better 157 | # understanding. The visual is populated with example values. Below are some guides 158 | # provided to help with tuning. 159 | # 160 | # \_____________/ 161 | # |___|___| 162 | # \_/ ______________ < End of third iteration. 163 | # / \ HEIGHT: 3 x iteration_z_raise - (2 + 1) x iteration_z_change (3 x 5 - 2 x 1.2 = 11.4) 164 | # | | EXTRUDED: 3 x max_iteration_length (3 x 50 = 150) 165 | # / \ ______________ < End of second iteration. 166 | # | \ HEIGHT: 2 x iteration_z_raise - 1 x iteration_z_change (2 x 5 - 1 x 1.2 = 8.8) 167 | # / | EXTRUDED: 2 x max_iteration_length (2 x 50 = 100) 168 | # | \ ______________ < End of first iteration. 169 | # / \ HEIGHT: 1 x iteration_z_raise (1 x 5 = 5) 170 | # | | EXTRUDED: 1 x max_iteration_length (1 x 50 = 50) 171 | #___________ \ / ______________ < Start height of the nozzle. default value: 1.5mm 172 | # |_______________\___________/_ ______________ < Bottom of the tray 173 | # |_____________________________| 174 | # | 175 | # 176 | ########################### BLOB TUNING ############################## 177 | # +-------------------------------------+----------------------------+ 178 | # | Filament sticks to the nozzle at | Incr. purge start | 179 | # | initial purge (first few mm) | | 180 | # +-------------------------------------+----------------------------+ 181 | # | Filament scoots out from under | Incr. temperature | 182 | # | the nozzle at the first iteration | Decr. z_raise | 183 | # | | Incr. purge_length_maximum | 184 | # +-------------------------------------+----------------------------+ 185 | # | Filament scoots out from under the | Decr. purge_spd | 186 | # | the nozzle at later iterations | Decr. z_raise_exp | 187 | # | | Decr. z_raise | 188 | # | | Incr. purge_length_maximum | 189 | # +-------------------------------------+----------------------------+ 190 | # | Filament sticks to the nozzle at | Incr. z_raise_exp | 191 | # | later iterations | (Not above 1) | 192 | # +-------------------------------------+----------------------------+ 193 | # 194 | 195 | # The height to raise the nozzle above the tray before purging. This allows any built up 196 | # pressure to escape before the purge. 197 | variable_purge_start: 0.2 198 | 199 | # The amount to raise Z 200 | variable_z_raise: 12 201 | 202 | # As the nozzle gets higher and the blob wider, the Z raise needs to be reduced, this 203 | # follows the following formula: 204 | # (extruded_amount/max_purge_length)^z_raise_exp * z_raise 205 | # 1 is linear, below 1 will cause z to raise less quickly over time, above 1 will make it 206 | # raise quicker over time. 0.85 is a good starting point and you should not have it above 1 207 | variable_z_raise_exp: 0.85 208 | 209 | # Lift the nozzle slightly after creating the blob te release pressure on the tray. 210 | variable_eject_hop: 1.0 211 | 212 | # Dwell time (ms) after purging and before cleaning to relieve pressure from the nozzle. 213 | variable_pressure_release_time: 1000 214 | 215 | # Set the part cooling fan speed. Disabling can help prevent the nozzle from cooling down 216 | # and stimulate flow, Enabling it can prevent blobs from sticking together. Values range 217 | # from 0 .. 1, or -1 if you don't want it changed. 218 | #variable_part_cooling_fan: -1 # Leave it unchanged 219 | #variable_part_cooling_fan: 0 # Disable the fan 220 | variable_part_cooling_fan: 1 # Run it at full speed 221 | 222 | 223 | # ======================================================================================== 224 | # ==================== PURGE LENGTH TUNING =============================================== 225 | # ======================================================================================== 226 | 227 | # The absolute minimum to purge, even if you don't changed tools. This is to prime the 228 | # nozzle before printing 229 | variable_purge_length_minimum: 35 230 | 231 | # The maximum amount of filament (in mm¹) to purge in a single blob. Blobifier will 232 | # automatically purge multiple blobs if the purge amount exeeds this. 233 | variable_purge_length_maximum: 120 234 | 235 | # Default purge length to fall back on when neither the tool map purge_volumes or 236 | # parameter PURGE_LENGTH is set. 237 | variable_purge_length: 150 238 | 239 | # The slicer values often are a bit too wastefull. Tune it here to get optimal values. 240 | # 0.6 (60%) is a good starting point. 241 | variable_purge_length_modifier: 0.6 242 | 243 | # Fixed length of filament to add after the purge volume calculation. Happy Hare already 244 | # shares info on the extra amount of filament to purge based on known residual filament, 245 | # tip cutting fragment and initial retraction setting. However this setting can add a fixed 246 | # amount on top on that if necessary although it is recommended to start with 0 and tune 247 | # slicer purge matrix first. 248 | # When should you alter this value: 249 | # INCREASE: When the dark to light swaps are good, but light to dark aren't. 250 | # DECREASE: When the light to dark swaps are good, but dark to light aren't. Don't 251 | # forget to increase the purge_length_modifier 252 | variable_purge_length_addition: 30 253 | 254 | # ======================================================================================== 255 | # ==================== BUCKET ============================================================ 256 | # ======================================================================================== 257 | 258 | # Maximum number of blobs that fit in the bucket. Pauses the print if it exceeds this 259 | # number. 260 | variable_max_blobs: 400 261 | # Enable the bucket shaker. You need to have the shaker.stl installed 262 | variable_enable_shaker: 1 263 | # The number of back-and-forth motions of one shake 264 | variable_bucket_shakes: 10 265 | # During shaking acceleration can often be higher because you don't neeed to keep print 266 | # quality in mind. Higher acceleration helps better with dispersing the blobs. 267 | variable_shake_accel: 10000 268 | 269 | # The frequency at which to shake the bucket. A decimal value ranging from 0 to 1, where 0 270 | # is never, and 1 is every time. This way the shaking occurs more often as the bucket 271 | # fills up. Sensible values range from 0.75 to 0.95 272 | variable_bucket_shake_frequency: 0.95 273 | 274 | # Height of the shaker arm. If your hotend hits your tray during shaking, increase. 275 | variable_shaker_arm_z: 2 276 | 277 | gcode: 278 | 279 | # ====================================================================================== 280 | # ==================== RECORD STATE (INCL. FANS, SPEEDS, ETC...) ======================= 281 | # ====================================================================================== 282 | 283 | # General state 284 | SAVE_GCODE_STATE NAME=BLOBIFIER_state 285 | 286 | 287 | 288 | # ====================================================================================== 289 | # ==================== CHECK HOMING STATUS ============================================= 290 | # ====================================================================================== 291 | 292 | {% if "xyz" not in printer.toolhead.homed_axes %} 293 | RESPOND MSG="BLOBIFIER: Not homed! Home xyz before blobbing" 294 | {% elif printer.quad_gantry_level and printer.quad_gantry_level.applied == False %} 295 | RESPOND MSG="BLOBIFIER: QGL not applied! run quad_gantry_level before blobbing" 296 | {% else %} 297 | 298 | # Part cooling fan 299 | {% if part_cooling_fan >= 0 %} 300 | # Save the part cooling fan speed to be enabled again later 301 | {% set backup_fan_speed = printer.fan.speed %} 302 | # Set part cooling fan speed 303 | M106 S{part_cooling_fan * 255} 304 | {% endif %} 305 | 306 | # Set feedrate to 100% for correct speed purging 307 | {% set backup_feedrate = printer.gcode_move.speed_factor %} 308 | M220 S100 309 | 310 | # ====================================================================================== 311 | # ==================== DEFINE BASIC VARIABLES ========================================== 312 | # ====================================================================================== 313 | 314 | {% set park_vars = printer['gcode_macro AFC_PARK'] %} 315 | {% set filament_diameter = printer.configfile.config.extruder.filament_diameter|float %} 316 | {% set filament_cross_section = (filament_diameter/2) ** 2 * 3.1415 %} 317 | {% set bl_count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 318 | {% set pos = printer.gcode_move.gcode_position %} 319 | {% set safe = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'] %} 320 | {% set ignore_safe = safe.print_height < force_safe_descend_height_until %} 321 | {% set restore_z = [printer['gcode_macro BLOBIFIER_PARK'].restore_z,pos.z]|max %} 322 | {% set pos_max = printer.toolhead.axis_maximum %} 323 | {% set position_y = pos_max.y - skew_correction %} 324 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 325 | 326 | # Get purge volumes from the slicer (if set up right. see 327 | # https://github.com/moggieuk/Happy-Hare/wiki/Gcode-Preprocessing) 328 | # {% set pv = printer.mmu.slicer_tool_map.purge_volumes %} 329 | 330 | 331 | 332 | # ====================================================================================== 333 | # ==================== DETERMINE PURGE LENGTH ========================================== 334 | # ====================================================================================== 335 | 336 | {% if params.PURGE_LENGTH %} # =============== PARAM PURGE LENGTH ====================== 337 | {action_respond_info("BLOBIFIER: param PURGE_LENGTH provided")} 338 | {% set purge_len = params.PURGE_LENGTH|float %} 339 | # {% elif from_tool == to_tool and to_tool >= 0 %} # ==== TOOL DIDN'T CHANGE ============= 340 | # {action_respond_info("BLOBIFIER: Tool didn't change (T%s > T%s), %s" % (from_tool, to_tool, "priming" if purge_length_minimum else "skipping"))} 341 | # {% set purge_len = 0 %} 342 | 343 | # {% elif pv %} # ============== FETCH FROM HAPPY HARE (LIKELY FROM SLICER) ============== 344 | # {% if from_tool < 0 and to_tool >= 0%} 345 | # {action_respond_info("BLOBIFIER: from tool unknown. Finding largest value for T? > T%d" % to_tool)} 346 | # {% set purge_vol = pv|map(attribute=to_tool)|max %} 347 | # {% elif to_tool < 0 %} 348 | # {action_respond_info("BLOBIFIER: tool(s) unknown. Finding largest value")} 349 | # {% set purge_vol = pv|map('max')|max %} 350 | # {% else %} 351 | # {% set purge_vol = pv[from_tool][to_tool]|float * purge_length_modifier %} 352 | # {action_respond_info("BLOBIFIER: Swapped T%s > T%s" % (from_tool, to_tool))} 353 | # {% endif %} 354 | # {% set purge_len = purge_vol / filament_cross_section %} 355 | 356 | # {% set purge_len = purge_len + printer.mmu.extruder_filament_remaining + park_vars.retracted_length + purge_length_addition %} 357 | 358 | {% else %} # ========================= USE CONFIG VARIABLE ============================= 359 | {action_respond_info("BLOBIFIER: No toolmap or PURGE_LENGTH. Using default")} 360 | {% set purge_len = purge_length|float + bl.retracted_length %} 361 | {% endif %} 362 | 363 | # # ==================================== APPLY PURGE MINIMUM ============================= 364 | {% set purge_len = [purge_len,purge_length_minimum]|max|round(0, 'ceil')|int %} 365 | {action_respond_info("BLOBIFIER: Purging %dmm of filament" % (purge_len))} 366 | 367 | {% if params.PURGE_LENGTH %} 368 | {% set purge_len = params.PURGE_LENGTH|float %} 369 | {% else %} 370 | {% set purge_len = purge_length %} 371 | {% endif %} 372 | 373 | # Apply purge minimum 374 | #{% set purge_len = [purge_len, purge_length_minimum]|max %} 375 | {% set purge_len = [purge_len,purge_length_minimum]|max|round(0, 'ceil')|int %} 376 | {action_respond_info("BLOBIFIER: Purging %dmm of filament" % (purge_len))} 377 | 378 | # ====================================================================================== 379 | # ==================== PURGING SEQUENCE ================================================ 380 | # ====================================================================================== 381 | 382 | # Set to absolute positioning. 383 | G90 384 | 385 | # Check for purge length and purge if necessary. 386 | {% if purge_len|float > 0 %} 387 | 388 | # ==================================================================================== 389 | # ==================== POSITIONING =================================================== 390 | # ==================================================================================== 391 | 392 | # Move to the assembly, first a bit more to the right (brush_start) to avoid a 393 | # potential filametrix pin if it's not already on the same Y coordinate. 394 | {% if printer.toolhead.position.y != position_y %} 395 | G1 X{brush_start} Y{position_y} F{travel_spd_xy} 396 | {% endif %} 397 | # Move over to the tray 398 | G1 X{purge_x} F{travel_spd_xy} 399 | 400 | # ==================================================================================== 401 | # ==================== BUCKET SHAKE ================================================== 402 | # ==================================================================================== 403 | 404 | {% if enable_shaker and (safe.shake or ignore_safe) %} 405 | {% if (bl_count.current_blobs + 1) >= bl_count.next_shake %} 406 | BLOBIFIER_SHAKE_BUCKET SHAKES={bucket_shakes} 407 | _BLOBIFIER_CALCULATE_NEXT_SHAKE 408 | {% endif %} 409 | {% endif %} 410 | 411 | # ==================================================================================== 412 | # ==================== POSITIONING ON TRAY =========================================== 413 | # ==================================================================================== 414 | {% if safe.tray or ignore_safe %} 415 | G1 Z{tray_top + purge_start} F{travel_spd_z} 416 | 417 | # Extend the blobifier 418 | BLOBIFIER_SERVO POS=out 419 | {% endif %} 420 | 421 | # ==================================================================================== 422 | # ==================== HEAT HOTEND =================================================== 423 | # ==================================================================================== 424 | 425 | {% if printer.extruder.temperature < purge_temp_min %} 426 | {% if printer.extruder.target < purge_temp_min %} 427 | M109 S{purge_temp_min} 428 | {% else %} 429 | TEMPERATURE_WAIT SENSOR=extruder MINIMUM={purge_temp_min} 430 | {% endif %} 431 | {% endif %} 432 | 433 | # ==================================================================================== 434 | # ==================== START ITERATING =============================================== 435 | # ==================================================================================== 436 | 437 | # Calculate total number of iterations based on the purge length and the max_iteration 438 | # length. 439 | {% set blobs = (purge_len / purge_length_maximum)|round(0, 'ceil')|int %} 440 | {% set purge_per_blob = purge_len|float / blobs %} 441 | {% set retracts_per_blob = (purge_per_blob / 40)|round(0, 'ceil')|int %} 442 | {% set purge_per_retract = (purge_per_blob / retracts_per_blob)|int %} 443 | {% set pulses_per_retract = (purge_per_blob / retracts_per_blob / 5)|round(0, 'ceil')|int %} 444 | {% set pulses_per_blob = (purge_per_blob / 5)|round(0, 'ceil')|int %} 445 | {% set purge_per_pulse = purge_per_blob / pulses_per_blob %} 446 | {% set pulse_time_constant = purge_per_pulse * 0.95 / purge_spd / (purge_per_pulse * 0.95 / purge_spd + purge_per_pulse * 0.05 / 50) %} 447 | {% set pulse_duration = purge_per_pulse / purge_spd %} 448 | 449 | # Repeat the process until purge_len is reached 450 | {% for blob in range(blobs) %} 451 | RESPOND MSG={"'BLOBIFIER: Blob %d of %d (%.1fmm)'" % (blob + 1, blobs, purge_per_blob)} 452 | 453 | {% if safe.tray or ignore_safe %} 454 | G1 Z{tray_top + purge_start} F{travel_spd_z} 455 | {% endif %} 456 | 457 | # relative positioning 458 | G91 459 | # relative extrusion 460 | M83 461 | 462 | # Purge filament in a pulsating motion to purge the filament quicker and better 463 | {% for pulse in range(pulses_per_blob) %} 464 | # Calculations to determine z-speed 465 | {% set purged_this_blob = pulse * purge_per_pulse %} 466 | {% set z_last_pos = purge_start + ((purged_this_blob)/purge_length_maximum)**z_raise_exp * z_raise %} 467 | {% set z_pos = purge_start + ((purged_this_blob + purge_per_pulse)/purge_length_maximum)**z_raise_exp * z_raise %} 468 | {% set z_up = z_pos - z_last_pos %} 469 | {% set speed = z_up / pulse_duration %} 470 | 471 | # Purge quickly 472 | G1 Z{z_up * pulse_time_constant} E{purge_per_pulse * 0.95} F{speed} 473 | # Purge a tiny bit slowly 474 | G1 Z{z_up * (1 - pulse_time_constant)} E{purge_per_pulse * 0.05} F{speed} 475 | 476 | # retract and unretract filament every now and then for thourough cleaning 477 | {% if pulse % pulses_per_retract == 0 and pulse > 0 %} 478 | G1 E-2 F1800 479 | G1 E2 F800 480 | {% endif %} 481 | 482 | {% endfor %} 483 | 484 | # Retract to match what Happy Hare is expecting 485 | G1 E-{bl.retracted_length} F{bl.retract_speed * 60} 486 | 487 | # ================================================================================== 488 | # ==================== DEPOSIT BLOB ================================================ 489 | # ================================================================================== 490 | {% if safe.tray or ignore_safe %} 491 | # Raise z a bit to relieve pressure on the blob preventing it to go sideways 492 | G1 Z{eject_hop} F{travel_spd_z} 493 | # Retract the tray 494 | BLOBIFIER_SERVO POS=in 495 | # Move the toolhead down to purge_start height lowering the blob below the tray 496 | G90 # absolute positioning 497 | G1 Z{tray_top} F{travel_spd_z} 498 | # Extend the tray to 'cut off' the blob and prepare for the next blob 499 | BLOBIFIER_SERVO POS=out 500 | BLOBIFIER_SERVO POS=in 501 | BLOBIFIER_SERVO POS=out 502 | # Keep track of the # of blobs 503 | _BLOBIFIER_COUNT 504 | {% endif %} 505 | {% endfor %} 506 | {% endif %} 507 | {% if safe.tray or ignore_safe %} 508 | G1 Z{tray_top + 1} F{travel_spd_z} 509 | G4 P{pressure_release_time} 510 | {% endif %} 511 | {% if safe.brush or ignore_safe %} 512 | BLOBIFIER_CLEAN 513 | {% else %} 514 | G1 X{brush_start} F{travel_spd_xy} 515 | {% endif %} 516 | 517 | # ====================================================================================== 518 | # ==================== RESTORE STATE =================================================== 519 | # ====================================================================================== 520 | G90 # absolute positioning 521 | G1 Z{restore_z} F{travel_spd_z} 522 | 523 | {% if part_cooling_fan >= 0 %} 524 | # Reset part cooling fan if it was changed 525 | M106 S{(backup_fan_speed * 255)|int} 526 | {% endif %} 527 | 528 | M220 S{(backup_feedrate * 100)|int} 529 | {% endif %} 530 | 531 | RESTORE_GCODE_STATE NAME=BLOBIFIER_state 532 | 533 | 534 | ########################################################################################## 535 | # Wipes the nozzle on the brass brush 536 | # 537 | [gcode_macro BLOBIFIER_CLEAN] 538 | gcode: 539 | {% set bb = printer['gcode_macro BLOBIFIER'] %} 540 | {% set position_y = printer.configfile.config["stepper_y"]["position_max"]|float %} 541 | {% set original_accel = printer.toolhead.max_accel %} 542 | {% set original_minimum_cruise_ratio = printer.toolhead.minimum_cruise_ratio %} 543 | {% set pos = printer.gcode_move.gcode_position %} 544 | 545 | SAVE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state 546 | 547 | G90 548 | 549 | {% if bb.brush_accel > 0 %} 550 | SET_VELOCITY_LIMIT ACCEL={bb.brush_accel} MINIMUM_CRUISE_RATIO=0.1 551 | {% endif %} 552 | 553 | {% if pos.z < bb.brush_top + bb.clearance_z %} 554 | G1 Z{bb.brush_top + bb.clearance_z} F{bb.travel_spd_z} 555 | {% endif %} 556 | G1 X{bb.brush_start} F{bb.travel_spd_xy} 557 | G1 Y{position_y} 558 | G1 Z{bb.brush_top + bb.clearance_z} F{bb.travel_spd_z} 559 | 560 | # Move nozzle down into brush. 561 | G1 Z{bb.brush_top} F{bb.travel_spd_z} 562 | 563 | SET_VELOCITY_LIMIT ACCEL={original_accel} MINIMUM_CRUISE_RATIO={original_minimum_cruise_ratio} 564 | 565 | # Perform wipe. Wipe direction based off bucket_pos for cool random scrubby routine. 566 | {% for wipes in range(1, (bb.wipe_qty + 1)) %} 567 | G1 X{bb.brush_start + bb.brush_width} F{bb.wipe_spd_xy} 568 | G1 X{bb.brush_start} F{bb.wipe_spd_xy} 569 | {% endfor %} 570 | 571 | RESTORE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state 572 | 573 | 574 | 575 | ########################################################################################## 576 | # Park the nozzle on the tray to prevent oozing during filament swaps. Place this 577 | # extension in the post_form_tip extension in mmu_macro_vars.cfg: 578 | # variable_user_post_form_tip_extension: "BLOBIFIER_PARK" 579 | # 580 | [gcode_macro BLOBIFIER_PARK] 581 | variable_restore_z: 0 582 | gcode: 583 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 584 | {% set pos = printer.gcode_move.gcode_position %} 585 | {% set safe = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'] %} 586 | {% set pos_max = printer.toolhead.axis_maximum %} 587 | {% set position_y = pos_max.y - bl.skew_correction %} 588 | 589 | SET_GCODE_VARIABLE MACRO=BLOBIFIER_PARK VARIABLE=restore_z VALUE={pos.z} 590 | 591 | SAVE_GCODE_STATE NAME=blobifier_park_state 592 | 593 | {% if "xyz" in printer.toolhead.homed_axes and printer.quad_gantry_level and printer.quad_gantry_level.applied %} 594 | G90 595 | 596 | G1 X{bl.brush_start} Y{position_y} F{bl.travel_spd_xy} 597 | G1 X{bl.purge_x} 598 | {% if safe.tray or ignore_safe %} 599 | G1 Z{bl.tray_top} F{bl.travel_spd_z} 600 | {% endif %} 601 | {% else %} 602 | RESPOND MSG="Please home (and QGL) before parking" 603 | {% endif %} 604 | 605 | RESTORE_GCODE_STATE NAME=blobifier_park_state 606 | 607 | ########################################################################################## 608 | # Retract or extend the tray 609 | # POS=[in|out] Retractor extend the tray 610 | # 611 | [gcode_macro BLOBIFIER_SERVO] 612 | # Increase this value if the servo doesn't have enough time to fully retract or extend 613 | variable_dwell_time: 200 614 | gcode: 615 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 616 | {% set pos = params.POS %} 617 | {% if pos == "in" %} 618 | SET_SERVO SERVO=blobifier ANGLE={bl.tray_angle_in} 619 | G4 P{dwell_time} 620 | {% elif pos == "out" %} 621 | SET_SERVO SERVO=blobifier ANGLE={bl.tray_angle_out} 622 | G4 P{dwell_time} 623 | {% else %} 624 | {action_respond_info("BLOBIFIER: provide POS=[in|out]")} 625 | {% endif %} 626 | SET_SERVO SERVO=blobifier WIDTH=0 627 | 628 | ########################################################################################## 629 | # Define exclude objects for those who haven't already 630 | # 631 | [exclude_object] 632 | 633 | ########################################################################################## 634 | # Overwrite the existing EXCLUDE_OBJECT_DEFINE to also check for safe descend. 635 | # 636 | [gcode_macro EXCLUDE_OBJECT_DEFINE] 637 | rename_existing: _EXCLUDE_OBJECT_DEFINE 638 | gcode: 639 | # only reset on the first object at the beginning of a print 640 | {% if printer.exclude_object.objects|length < 1 %} 641 | _BLOBIFIER_RESET_SAFE_DESCEND 642 | {% endif %} 643 | _EXCLUDE_OBJECT_DEFINE {rawparams} 644 | _BLOBIFIER_SAFE_DESCEND 645 | UPDATE_DELAYED_GCODE ID=BLOBIFIER_SHOW_SAFE_DESCEND DURATION=1 646 | 647 | [delayed_gcode BLOBIFIER_SHOW_SAFE_DESCEND] 648 | gcode: 649 | {% set safe = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'] %} 650 | {action_respond_info( 651 | "BLOBIIER: Safe descend possible:\n - tray: %s\n - brush: %s\n - shake: %s" % 652 | ( 653 | "yes" if safe.tray else "no", 654 | "yes" if safe.brush else "no", 655 | "yes" if safe.shake else "no" 656 | ) 657 | )} 658 | 659 | ########################################################################################## 660 | # Use the EXCLUDE_OBJECT_START gcode macro to record the current height 661 | # 662 | [gcode_macro EXCLUDE_OBJECT_START] 663 | rename_existing: _EXCLUDE_OBJECT_START 664 | gcode: 665 | _EXCLUDE_OBJECT_START {rawparams} 666 | {% if printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'].first_layer %} 667 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=first_layer VALUE=False 668 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_height VALUE={printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'].print_layer_height} 669 | {% else %} 670 | {% set pos = printer.gcode_move.gcode_position %} 671 | {% set last_height = printer['gcode_macro _BLOBIFIER_SAFE_DESCEND'].print_previous_height|float %} 672 | {% if pos.z > last_height %} 673 | {% set last_layer = (pos.z - last_height)|round(2) %} 674 | {% set print_height = (pos.z + last_layer)|round(2) %} 675 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_previous_height VALUE={pos.z} 676 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_height VALUE={print_height} 677 | {% endif %} 678 | {% endif %} 679 | 680 | 681 | ########################################################################################## 682 | # Reset the safe descend variables. 683 | # 684 | [gcode_macro _BLOBIFIER_RESET_SAFE_DESCEND] 685 | gcode: 686 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=tray VALUE=True 687 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=brush VALUE=True 688 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=shake VALUE=True 689 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=first_layer VALUE=True 690 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_height VALUE=0 691 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=print_previous_height VALUE=0 692 | 693 | ########################################################################################## 694 | # Determine if it is safe to drop the toolhead (e.g. not hit a print) 695 | # 696 | [gcode_macro _BLOBIFIER_SAFE_DESCEND] 697 | variable_tray: True # Assume it is safe 698 | variable_brush: True 699 | variable_shake: True 700 | variable_first_layer: True 701 | variable_print_height: 0 702 | variable_print_previous_height: 0 703 | variable_print_layer_height: 0.3 704 | gcode: 705 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 706 | {% set pos_max = printer.toolhead.axis_maximum %} 707 | {% set position_y = pos_max.y - bl.skew_correction %} 708 | {% set tray = [bl.purge_x + bl.toolhead_x, position_y - bl.toolhead_y] %} 709 | {% set brush = [bl.brush_start + bl.brush_width + bl.toolhead_x, position_y - bl.toolhead_y] %} 710 | {% set shake = [bl.purge_x + bl.toolhead_x, position_y - bl.toolhead_y - 4] %} 711 | {% set objects = printer.exclude_object.objects | map(attribute='polygon') %} 712 | 713 | {% for polygon in objects %} 714 | {% for point in polygon %} 715 | {% if point[0] < tray[0] and point[1] > tray[1] %} 716 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=tray VALUE=False 717 | {% endif %} 718 | {% if point[0] < brush[0] and point[1] > brush[1] %} 719 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=brush VALUE=False 720 | {% endif %} 721 | {% if point[0] < shake[0] and point[1] > shake[1] %} 722 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_SAFE_DESCEND VARIABLE=shake VALUE=False 723 | {% endif %} 724 | {% endfor %} 725 | {% endfor %} 726 | 727 | ########################################################################################## 728 | # Increment the blob count with 1 and check if the bucket is full. Pause 729 | # the printer if it is. 730 | # 731 | [gcode_macro _BLOBIFIER_COUNT] 732 | # Don't change these variables 733 | variable_current_blobs: 0 734 | variable_last_shake: 0 735 | variable_next_shake: 0 736 | gcode: 737 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 738 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 739 | {% if current_blobs >= bl.max_blobs %} 740 | {action_respond_info("BLOBIFIER: Empty purge bucket!")} 741 | M117 Empty purge bucket! 742 | PAUSE 743 | {% else %} 744 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE={current_blobs + 1} 745 | _BLOBIFIER_SAVE_STATE 746 | {action_respond_info( 747 | "BLOBIFIER: Blobs in bucket: %s/%s. Next shake @ %s" 748 | % (current_blobs + 1, bl.max_blobs, next_shake) 749 | )} 750 | {% endif %} 751 | 752 | ########################################################################################## 753 | # Reset the blob count to 0 754 | # 755 | [gcode_macro _BLOBIFIER_COUNT_RESET] 756 | gcode: 757 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE=0 758 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE=0 759 | _BLOBIFIER_SAVE_STATE 760 | 761 | _BLOBIFIER_CALCULATE_NEXT_SHAKE 762 | 763 | ########################################################################################## 764 | # Shake the blob bucket to disperse the blobs 765 | # 766 | [gcode_macro BLOBIFIER_SHAKE_BUCKET] 767 | gcode: 768 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 769 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 770 | {% set original_accel = printer.toolhead.max_accel %} 771 | {% set original_minimum_cruise_ratio = printer.toolhead.minimum_cruise_ratio %} 772 | {% set position_x = bl.skew_correction %} 773 | 774 | {% if "xyz" not in printer.toolhead.homed_axes %} 775 | {action_raise_error("BLOBIFIER: Not homed. Home xyz first")} 776 | {% endif %} 777 | 778 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={count.current_blobs} 779 | _BLOBIFIER_SAVE_STATE 780 | SAVE_GCODE_STATE NAME=shake_bucket 781 | 782 | M400 783 | M117 (^_^) 784 | 785 | G90 786 | {% set shakes = params.SHAKES|default(10)|int %} 787 | {% set position_y = printer.configfile.config["stepper_y"]["position_max"]|float %} 788 | 789 | # move to save y if not already there 790 | {% if printer.toolhead.position.y != position_y %} 791 | G1 X{bl.brush_start} Y{position_y} F{bl.travel_spd_xy} 792 | {% endif %} 793 | 794 | # move up a bit to prevent oozing on base 795 | G1 Z{bl.shaker_arm_z} F{bl.travel_spd_z} 796 | # slide into the slot 797 | G1 X{position_x} F{bl.travel_spd_xy} 798 | 799 | M400 800 | M117 (+(+_+)+) 801 | 802 | SET_VELOCITY_LIMIT ACCEL={bl.shake_accel} MINIMUM_CRUISE_RATIO=0.1 803 | 804 | # Shake away! 805 | {% for shake in range(1, shakes) %} 806 | G1 Y{position_y - 4} 807 | G1 Y{position_y} 808 | {% endfor %} 809 | 810 | SET_VELOCITY_LIMIT ACCEL={original_accel} MINIMUM_CRUISE_RATIO={original_minimum_cruise_ratio} 811 | # move out of slot 812 | G1 X{bl.purge_x} 813 | 814 | M400 815 | M117 (X_x) 816 | 817 | RESTORE_GCODE_STATE NAME=shake_bucket 818 | 819 | ########################################################################################## 820 | # Calculate when the bucket should be shaken. 821 | # 822 | [gcode_macro _BLOBIFIER_CALCULATE_NEXT_SHAKE] 823 | gcode: 824 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 825 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 826 | 827 | {% set remaining_blobs = bl.max_blobs - count.last_shake %} 828 | {% set next_shake = (1 - bl.bucket_shake_frequency) * remaining_blobs + count.last_shake %} 829 | _BLOBIFIER_SAVE_STATE 830 | _BLOBIFIER_SET_NEXT_SHAKE VALUE={next_shake|int} 831 | 832 | ########################################################################################## 833 | # Set when the bucket should be shaken next 834 | # VALUE=[int] At what amount of blobs should it be shaken 835 | # 836 | [gcode_macro _BLOBIFIER_SET_NEXT_SHAKE] 837 | gcode: 838 | {% if params.VALUE %} 839 | {% set next_shake = params.VALUE %} 840 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=next_shake VALUE={next_shake} 841 | _BLOBIFIER_SAVE_STATE 842 | {% else %} 843 | {action_respond_info("BLOBIFIER: Provide parameter VALUE=")} 844 | {% endif %} 845 | 846 | ########################################################################################## 847 | # Some sanity checks 848 | # 849 | [delayed_gcode BLOBIFIER_INIT] 850 | initial_duration: 5.0 851 | gcode: 852 | _BLOBIFIER_INIT 853 | BLOBIFIER_SERVO POS=out 854 | 855 | [gcode_macro _BLOBIFIER_INIT] 856 | gcode: 857 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 858 | 859 | # Valid part cooling fan setting 860 | {% if bl.part_cooling_fan != -1 and (bl.part_cooling_fan < 0 or bl.part_cooling_fan > 1) %} 861 | {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))} 862 | {% endif %} 863 | 864 | # Valid bucket shake frequency 865 | {% if bl.bucket_shake_frequency < 0 or bl.bucket_shake_frequency > 1 %} 866 | {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))} 867 | {% endif %} 868 | 869 | # Check if position is on 'next' 870 | {% if printer.mmu %} 871 | {% if printer['gcode_macro _MMU_SEQUENCE_VARS'].restore_xy_pos != 'next' %} 872 | {action_respond_info("BLOBIFIER: If not using a wipe tower, consider setting restore_xy_pos: 'next' in mmu_macro_vars.cfg")} 873 | {% endif %} 874 | {% endif %} 875 | 876 | # Check the z_raise variable for normal values 877 | {% if bl.z_raise < 3 %} 878 | {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))} 879 | {% endif %} 880 | 881 | # Z raise exponent 882 | {% if bl.z_raise_exp > 1 or bl.z_raise_exp < 0.5 %} 883 | {action_respond_info("BLOBIFIER: variable_z_raise_exp has value: %f. This value is out of spec (0.5 ... 1.0)." % (bl.z_raise))} 884 | {% endif %} 885 | 886 | 887 | 888 | [delayed_gcode BLOBIFIER_LOAD_STATE] 889 | initial_duration: 2.0 # Give it some time to boot up 890 | gcode: 891 | {% set sv = printer.save_variables.variables.blobifier %} 892 | 893 | {% if sv %} 894 | # Restore state 895 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={sv.last_shake} 896 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE={sv.current_blobs} 897 | {% endif %} 898 | _BLOBIFIER_CALCULATE_NEXT_SHAKE 899 | 900 | [gcode_macro _BLOBIFIER_SAVE_STATE] 901 | gcode: 902 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 903 | {% set sv = {'current_blobs': count.current_blobs, 'last_shake': count.last_shake} %} 904 | SAVE_VARIABLE VARIABLE=blobifier VALUE="{sv}" 905 | 906 | 907 | 908 | [respond] 909 | 910 | [save_variables] 911 | filename: ~/printer_data/config/blobifier/blob_vars.cfg 912 | -------------------------------------------------------------------------------- /blobifier.old.cfg: -------------------------------------------------------------------------------- 1 | [include blobifier_hw.cfg] 2 | 3 | ########################################################################################## 4 | 5 | # Sample config to be used in conjunction with Blobifier Purge Tray, Bucket & Nozzle 6 | # Scrubber mod. Created by Dendrowen (dendrowen on Discord). The Macro is based on a 7 | # version, and Nozzle Scrubber is made by Hernsl (hernsl#8860 on Discord). The device is 8 | # designed around a Voron V2.4 300mm, but should work for 250mm and 350mm too. This 9 | # version only supports the assembly on the rear-left of the bed. If you decide to change 10 | # that, please consider contributing to the project by creating a pull request with the 11 | # needed changes. 12 | 13 | # IMPORTANT: The rear-left part of your bed becomes unusable by this mod because the 14 | # toolhead needs to lower down to 0. Be sure not to use the left-rear 130x35mm. 15 | 16 | # The goals of this combination of devices is to dispose of purged filament during a 17 | # multicolored print without the need of a purge block and without the flurries of 18 | # filament poops consuming your entire 3D printer room. The Blobifier achieves that by 19 | # purging onto a retractable tray which causes the filament to turn into a tiny blob 20 | # rather then a large spiral. This keeps the waste relativly small. The bucket should be 21 | # able to account for up to 200 filament swaps (for the 300mm V2). 22 | 23 | # Upload this file to your klipper config directory and include it in printer.cfg 24 | # [include blobifier.cfg] 25 | 26 | # It is advised to use the start_gcode from Happy Hare. Then you will be able to fully 27 | # and efficiently use this mod. Check the Happy Hare document at gcode_preprocessing.md 28 | # in the Happy Hare github for more details. 29 | 30 | ###################################### DISCLAIMER ######################################## 31 | 32 | # You, and you alone, are responsible for the correct execution of these macros and 33 | # gcodes. Any damage that may occur to your machine remains your responsibility. 34 | # Especially when executing this macro for the first few times, keep an eye on your 35 | # printer and the 36 | # emergency stop. 37 | 38 | ########################################################################################## 39 | 40 | ########################################################################################## 41 | # Main macro. Usually you should only need to call this one or place it in the Happy Hare 42 | # _MMU_POST_LOAD macro or variable_user_post_load_extension: 43 | # PURGE_LENGTH=[float] (optional) The length to purge. If ommited it will check the 44 | # purge_volumes matrix or variable_purge_length 45 | # PURGE_RET=[float] (optional) The length to retract between moves 46 | # 47 | [gcode_macro BLOBIFIER] 48 | # These parameters define your filament purging. The retract variable is used to retract 49 | # right after purging to prevent unnecessary oozing. Some filament are particularly oozy 50 | # and may continue to ooze out of the nozzle for a second or two after retracting. Update 51 | # as necessary. 52 | variable_purge_spd: 400 # Speed, in mm/min, of the purge. 53 | variable_purge_temp_min: 200 # Minimum nozzle purge temperature. 54 | variable_retract_before_park: 1.0 # amount in mm. 0 to disable 55 | variable_retract_speed: 2000 # mm/m 56 | 57 | # Adjust this so that your nozzle scrubs within the brush. Be careful not to go too low! 58 | # Start out with a high value (like, 6) and go 59 | # down from there. 60 | variable_brush_top: 3.4 61 | 62 | # These parameters define your scrubbing, travel speeds, safe z clearance and how many 63 | # times you want to wipe. Update as necessary. 64 | variable_clearance_z: 10 # When traveling, but not cleaning, the 65 | # clearance along the z-axis between nozzle 66 | # and brush. 67 | variable_wipe_qty: 2 # Number of complete (A complete wipe: left, 68 | # right, left OR right, left, right) wipes. 69 | variable_travel_spd_xy: 13000 # Travel (not cleaning) speed along x and 70 | # y-axis in mm/min. 71 | variable_travel_spd_z: 1000 # Travel (not cleaning) speed along z axis 72 | # in mm/min. 73 | variable_wipe_spd_xy: 12000 # Nozzle wipe speed in mm/min. 74 | 75 | 76 | # These parameters define the size of the brush. Update as necessary. A visual reference 77 | # is provided below. 78 | # 79 | # ← brush_width → 80 | # _________________ 81 | # | | ↑ Y position is acquired from your 82 | # brush_start (x) | | brush_depth stepper_y position_max. Adjust 83 | # |_________________| ↓ your brush physically in Y so 84 | # (y) that the nozzle scrubs within the 85 | # brush_front brush. 86 | # __________________________________________________________ 87 | # PRINTER FRONT 88 | # 89 | # 90 | # Start location of the brush. Defaults for 250, 300 and 350mm are provided below. 91 | # Uncomment as necessary 92 | #variable_brush_start: 34 # For 250mm build 93 | variable_brush_start: 50 # For 300mm build 94 | #variable_brush_start: 84 # for 350mm build 95 | 96 | # width of the brush 97 | variable_brush_width: 52 98 | 99 | # Location of where to purge. The tray is 15mm in length, so if you assemble it against 100 | # the side of the bed (default), 10mm is a good location 101 | variable_purgey_x: 4 102 | 103 | # Height of the tray. If it's below your bed, give this a negative number equal to the 104 | # difference. If it's above your bed, give it a positive number. You can find this number 105 | # by homing, optional QGL or equivalent, and moving you toolhead above the tray, and 106 | # lowering it with the paper method. 107 | variable_tray_top: 1.4 108 | 109 | 110 | # ======================================================================================== 111 | # ==================== BLOB TUNING ======================================================= 112 | # ======================================================================================== 113 | 114 | # The following section defines how the purging sequence is executed. This is where you 115 | # tune the purging to create pretty blobs. Refer to the visual reference for a better 116 | # understanding. The visual is populated with example values. Below are some guides 117 | # provided to help with tuning. 118 | # 119 | # \_____________/ 120 | # |___|___| 121 | # \_/ ______________ < End of third iteration. 122 | # / \ HEIGHT: 3 x iteration_z_raise - (2 + 1) x iteration_z_change (3 x 5 - 2 x 1.2 = 11.4) 123 | # | | EXTRUDED: 3 x max_iteration_length (3 x 50 = 150) 124 | # / \ ______________ < End of second iteration. 125 | # | \ HEIGHT: 2 x iteration_z_raise - 1 x iteration_z_change (2 x 5 - 1 x 1.2 = 8.8) 126 | # / | EXTRUDED: 2 x max_iteration_length (2 x 50 = 100) 127 | # | \ ______________ < End of first iteration. 128 | # / \ HEIGHT: 1 x iteration_z_raise (1 x 5 = 5) 129 | # | | EXTRUDED: 1 x max_iteration_length (1 x 50 = 50) 130 | #___________ \ / ______________ < Start height of the nozzle. default value: 1.5mm 131 | # |_______________\___________/_ ______________ < Bottom of the tray 132 | # |_____________________________| 133 | # | 134 | # 135 | ########################### BLOB TUNING ############################## 136 | # +-------------------------------------+----------------------------+ 137 | # | Filament sticks to the nozzle at | Incr. purge start | 138 | # | initial purge (first few mm) | | 139 | # +-------------------------------------+----------------------------+ 140 | # | Filament scoots out from under | Incr. temperature | 141 | # | the nozzle at the first iteration | Decr. iteration_z_raise | 142 | # | | Incr. max_iteration_length | 143 | # +-------------------------------------+----------------------------+ 144 | # | Filament scoots out from under the | Decr. purge_spd | 145 | # | the nozzle at later iterations | Incr. iteration_z_change | 146 | # +-------------------------------------+----------------------------+ 147 | # | Filament sticks to the nozzle at | Decr. iteration_z_change | 148 | # | later iterations | | 149 | # +-------------------------------------+----------------------------+ 150 | # | I think my blobs can | Balsy... Increase | 151 | # | be much bigger! | max_iterations_per_blob | 152 | # +-------------------------------------+----------------------------+ 153 | # 154 | 155 | # The height to raise the nozzle above the tray before purging. This allows any built up 156 | # pressure to escape before the purge. 157 | variable_purge_start: 0.2 158 | 159 | # The maximum mm of filament (length) to extrude one iteration. A blob contains multiple 160 | # iterations 161 | variable_max_iteration_length: 40 162 | 163 | # The amount to raise Z for each iteration 164 | variable_iteration_z_raise: 6 165 | 166 | # As the nozzle gets higher and the blob wider, the Z raise needs to be reduced. 167 | variable_iteration_z_change: 0.6 168 | 169 | # Maximum iterations per blob. 3 Should be sufficient for most flushes. If not, Blobifier 170 | # will create multiple blobs for a single purge. 171 | variable_max_iterations_per_blob: 3 172 | 173 | # Lift the nozzle slightly after creating the blob te release pressure on the tray. 174 | variable_eject_hop: 10 175 | 176 | # Dwell time (ms) after purging and before cleaning to relieve pressure from the nozzle. 177 | variable_pressure_release_time: 1000 178 | 179 | # Set the part cooling fan speed. Disabling can help prevent the nozzle from cooling down 180 | # and stimulate flow, Enabling it can prevent blobs from sticking together. Values range 181 | # from 0 .. 1, or -1 if you don't want it changed. 182 | #variable_part_cooling_fan: -1 # Leave it unchanged 183 | #variable_part_cooling_fan: 0 # Disable the fan 184 | variable_part_cooling_fan: 1 # Run it at full speed 185 | 186 | 187 | # ======================================================================================== 188 | # ==================== PURGE LENGTH TUNING =============================================== 189 | # ======================================================================================== 190 | 191 | # Default purge length to fall back on when neither the tool map purge_volumes or 192 | # parameter PURGE_LENGTH is set. 193 | variable_purge_length: 150 194 | 195 | # The absolute minimum to purge, even if you don't changed tools. This is to prime the 196 | # nozzle before printing 197 | variable_purge_length_minimum: 30 198 | 199 | # The slicer values often are a bit too wastefull. Tune it here to get optimal values. 0.6 200 | # is a good starting point. 201 | variable_purge_length_modifier: 0.5 202 | 203 | # Length of filament to add after the purge volume. Purge volumes don't always take 204 | # cutters into account and therefor a swap from red to white might be long enough, but 205 | # from white to red can be far too short. When should you alter this value: 206 | # INCREASE: When the dark to light swaps are good, but light to dark aren't. 207 | # DECREASE: When the light to dark swaps are good, but dark to light aren't. Don't 208 | # forget to increase the purge_length_modifier 209 | variable_purge_length_addition: 30 210 | 211 | # ======================================================================================== 212 | # ==================== BUCKET ============================================================ 213 | # ======================================================================================== 214 | 215 | # Maximum number of blobs that fit in the bucket. Pauses the print if it exceeds this 216 | # number. 217 | variable_max_blobs: 400 218 | # Enable the bucket shaker. You need to have the shaker.stl installed 219 | variable_enable_shaker: 1 220 | # The number of back-and-forth motions of one shake 221 | variable_bucket_shakes: 10 222 | # During shaking acceleration can often be higher because you don't neeed to keep print 223 | # quality in mind. Higher acceleration helps better with dispersing the blobs. 224 | variable_shake_accel: 10000 225 | 226 | # The frequency at which to shake the bucket. A decimal value ranging from 0 to 1, where 0 227 | # is never, and 1 is every time. This way the shaking occurs more often as the bucket 228 | # fills up. Sensible values range from 0.75 to 0.95 229 | variable_bucket_shake_frequency: 0.95 230 | 231 | # Height of the shaker arm. If your hotend hits your tray during shaking, increase. 232 | variable_shaker_arm_z: 2 233 | 234 | gcode: 235 | 236 | # ====================================================================================== 237 | # ==================== RECORD STATE (INCL. FANS, SPEEDS, ETC...) ======================= 238 | # ====================================================================================== 239 | 240 | # General state 241 | SAVE_GCODE_STATE NAME=BLOBIFIER_state 242 | 243 | # Part cooling fan 244 | {% if part_cooling_fan >= 0 %} 245 | # Save the part cooling fan speed to be enabled again later 246 | {% set backup_fan_speed = printer.fan.speed %} 247 | # Set part cooling fan speed 248 | M106 S{part_cooling_fan * 255} 249 | {% endif %} 250 | 251 | # Set feedrate to 100% for correct speed purging 252 | {% set backup_feedrate = printer.gcode_move.speed_factor %} 253 | M220 S100 254 | 255 | # ====================================================================================== 256 | # ==================== CHECK HOMING STATUS ============================================= 257 | # ====================================================================================== 258 | 259 | {% if "xyz" not in printer.toolhead.homed_axes %} 260 | {action_raise_error("BLOBIFIER: Not homed! Home xyz first")} 261 | {% endif %} 262 | 263 | {% if printer.quad_gantry_level and printer.quad_gantry_level.applied == False %} 264 | {action_raise_error("BLOBIFIER: QGL not applied!")} 265 | {% endif %} 266 | 267 | # ====================================================================================== 268 | # ==================== DEFINE BASIC VARIABLES ========================================== 269 | # ====================================================================================== 270 | 271 | {% set filament_diameter = printer.configfile.config.extruder.filament_diameter|float %} 272 | {% set filament_cross_section = (filament_diameter/2) ** 2 * 3.1415 %} 273 | #{% set from_tool = printer.mmu.last_tool %} 274 | #{% set to_tool = printer.mmu.tool %} 275 | {% set bl_count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 276 | {% set pos = printer.gcode_move.gcode_position %} 277 | {% set restore_z = [printer['gcode_macro BLOBIFIER_PARK'].restore_z,pos.z]|max %} 278 | {% set pos_max = printer.toolhead.axis_maximum %} 279 | {% set position_y = pos_max.y %} 280 | 281 | # Get purge volumes from the slicer (if set up right. see 282 | # https://github.com/moggieuk/Happy-Hare/blob/variables/doc/gcode_preprocessing.md) 283 | 284 | #{% set pv = printer.mmu.slicer_tool_map.purge_volumes %} 285 | 286 | # ====================================================================================== 287 | # ==================== DETERMINE PURGE LENGTH ========================================== 288 | # ====================================================================================== 289 | 290 | {% if params.PURGE_LENGTH %} # ======= PARAM PURGE LENGTH ============================== 291 | {action_respond_info("BLOBIFIER: param PURGE_LENGTH provided")} 292 | {% set purge_len = params.PURGE_LENGTH|float %} 293 | # {% elif from_tool == to_tool %} # ==== TOOL DIDN'T CHANGE ============================== 294 | # {action_respond_info("BLOBIFIER: Tool didn't change (T%s > T%s), " ~ 295 | # "priming" if purge_length_minimum else "skipping" % (from_tool, to_tool))} 296 | # {% set purge_len = 0 %} 297 | # {% elif pv %} # ====================== FECTH FROM SLICER =============================== 298 | # {% if from_tool < 0 and to_tool >= 0%} 299 | # {action_respond_info("BLOBIFIER: from tool unknown. Finding largest value for T? > T%d" % to_tool)} 300 | # {% set purge_vol = pv|map(attribute=to_tool)|max %} 301 | # {% elif to_tool < 0 %} 302 | # {action_respond_info("BLOBIFIER: tool(s) unknown. Finding largest value")} 303 | # {% set purge_vol = pv|map('max')|max %} 304 | # {% else %} 305 | # {% set purge_vol = pv[from_tool][to_tool]|float * purge_length_modifier %} 306 | # {action_respond_info("BLOBIFIER: Swapped T%s > T%s" % (from_tool, to_tool))} 307 | # {% endif %} 308 | {% set purge_len = purge_vol / filament_cross_section + purge_length_addition %} 309 | {% else %} # ========================= USE CONFIG VARIABLE ============================= 310 | {action_respond_info("BLOBIFIER: No toolmap or PURGE_LENGTH. Using default")} 311 | {% set purge_len = purge_length|float %} 312 | {% endif %} 313 | # ==================================== APPLY PURGE MINIMUM ============================= 314 | {% set purge_len = [purge_len,purge_length_minimum]|max %} 315 | {action_respond_info("BLOBIFIER: Purging %.2fmm of filament" % (purge_len|float))} 316 | 317 | # ====================================================================================== 318 | # ==================== PURGING SEQUENCE ================================================ 319 | # ====================================================================================== 320 | 321 | # Set to absolute positioning. 322 | G90 323 | 324 | # Check for purge length and purge if necessary. 325 | {% if purge_len|float > 0 %} 326 | 327 | # ==================================================================================== 328 | # ==================== POSITIONING =================================================== 329 | # ==================================================================================== 330 | 331 | # Move to the assembly, first a bit more to the right (brush_start) to avoid a 332 | # potential filametrix pin if it's not already on the same Y coordinate. 333 | {% if printer.toolhead.position.y != position_y %} 334 | G1 X{brush_start} Y{position_y} F{travel_spd_xy} 335 | {% endif %} 336 | # Move over to the tray 337 | #G1 X{purgey_x} F{travel_spd_xy} 338 | {action_respond_info("BLOBIFIER: Moving over Tray")} 339 | G1 X4 F{travel_spd_xy} 340 | 341 | # ==================================================================================== 342 | # ==================== BUCKET SHAKE ================================================== 343 | # ==================================================================================== 344 | 345 | {% if enable_shaker %} 346 | {% if (bl_count.current_blobs + 1) >= bl_count.next_shake %} 347 | BLOBIFIER_SHAKE_BUCKET SHAKES={bucket_shakes} 348 | _BLOBIFIER_CALCULATE_NEXT_SHAKE 349 | {% endif %} 350 | {% endif %} 351 | 352 | # ==================================================================================== 353 | # ==================== POSITIONING ON TRAY =========================================== 354 | # ==================================================================================== 355 | G1 Z{tray_top + purge_start} F{travel_spd_z} 356 | 357 | # Extend the blobifier 358 | BLOBIFIER_SERVO POS=out 359 | # Check if the servo is in correct position 360 | # {% if printer.mmu is defined and printer.mmu.enabled %} 361 | # {% if printer.configfile.config.mmu.sync_to_extruder %} 362 | # MMU_SYNC_GEAR_MOTOR SYNC=1 363 | # {% else %} 364 | # MMU_SERVO POS=up 365 | # {% endif %} 366 | # {% endif %} 367 | 368 | # ==================================================================================== 369 | # ==================== HEAT HOTEND =================================================== 370 | # ==================================================================================== 371 | 372 | {% if printer.extruder.temperature < purge_temp_min %} 373 | {% if printer.extruder.target < purge_temp_min %} 374 | M109 S{purge_temp_min} 375 | {% else %} 376 | TEMPERATURE_WAIT SENSOR=extruder MINIMUM={purge_temp_min} 377 | {% endif %} 378 | {% endif %} 379 | 380 | # ==================================================================================== 381 | # ==================== START ITERATING =============================================== 382 | # ==================================================================================== 383 | 384 | # Calculate total number of iterations based on the purge length and the max_iteration 385 | # length. 386 | {% set iterations = (purge_len / max_iteration_length)|round(0, 'ceil')|int %} 387 | 388 | # Repeat the process until purge_len is reached 389 | {% for n in range(iterations) %} 390 | 391 | # Calculate current iteration in current blob 392 | {% set step = n % max_iterations_per_blob %} 393 | 394 | {% if step == 0 %} 395 | G1 Z{tray_top + purge_start} F{travel_spd_z} 396 | {% endif %} 397 | 398 | # Determine the amount to extrude. Either the remaining purge_len or 399 | # max_iteration_length. 400 | {% set purge_amount_left = purge_len - (max_iteration_length * n) %} 401 | {% set extrude_amount = [purge_amount_left,max_iteration_length]|min %} 402 | {% set extrude_ratio = extrude_amount / max_iteration_length %} 403 | 404 | # relative positioning 405 | G91 406 | M83 407 | 408 | # Calculate the amount z has to be raised 409 | # If step equals 0, the start position of the nozzle is already a little higher. 410 | {% set step_triangular = step * (step + 1) / 2 %} 411 | {% set z_raise_substract = purge_start if step == 0 else 412 | step_triangular * iteration_z_change %} 413 | {% set raise_z = (iteration_z_raise - z_raise_substract) * extrude_ratio %} 414 | 415 | # make sure raise_z never goes negative, dropping the nozzle while purging. 416 | {% set raise_z = [raise_z,0]|max %} 417 | 418 | # Calculate the raise speed based on the purge speed. 419 | {% set duration = extrude_amount / purge_spd %} 420 | {% set speed = raise_z / duration %} 421 | 422 | # Purge one iteration 423 | G1 Z{raise_z} E{extrude_amount} F{speed} 424 | 425 | # ================================================================================== 426 | # ==================== DEPOSIT BLOB ================================================ 427 | # ================================================================================== 428 | {% set max_iterations_reached = step == max_iterations_per_blob - 1 %} 429 | {% set purge_length_reached = purge_len - max_iteration_length * (n+1) <= 0 %} 430 | {% if max_iterations_reached or purge_length_reached %} 431 | # Raise z a bit to relieve pressure on the blob preventing it to go sideways 432 | G1 Z{eject_hop} F{travel_spd_z} 433 | # Retract the tray 434 | BLOBIFIER_SERVO POS=in 435 | # Move the toolhead down to purge_start height lowering the blob below the tray 436 | G90 # absolute positioning 437 | G1 Z{tray_top} 438 | # Extend the tray to 'cut off' the blob and prepare for the next blob 439 | BLOBIFIER_SERVO POS=out 440 | BLOBIFIER_SERVO POS=in 441 | BLOBIFIER_SERVO POS=out 442 | # Keep track of the # of blobs 443 | _BLOBIFIER_COUNT 444 | {% endif %} 445 | {% endfor %} 446 | {% endif %} 447 | G4 P{pressure_release_time} 448 | BLOBIFIER_CLEAN 449 | 450 | # ====================================================================================== 451 | # ==================== RESTORE STATE =================================================== 452 | # ====================================================================================== 453 | 454 | G1 Z{restore_z} F{travel_spd_z} 455 | 456 | {% if part_cooling_fan >= 0 %} 457 | # Reset part cooling fan if it was changed 458 | M106 S{backup_fan_speed * 255} 459 | {% endif %} 460 | 461 | M220 S{backup_feedrate * 100} 462 | 463 | RESTORE_GCODE_STATE NAME=BLOBIFIER_state 464 | 465 | ########################################################################################## 466 | # Wipes the nozzle on the brass brush 467 | # 468 | [gcode_macro BLOBIFIER_CLEAN] 469 | gcode: 470 | {% set bb = printer['gcode_macro BLOBIFIER'] %} 471 | {% set position_y = printer.configfile.config["stepper_y"]["position_max"]|float %} 472 | # Position for wipe. Either left or right of brush based off bucket_pos to avoid 473 | # unnecessary travel. 474 | 475 | SAVE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state 476 | 477 | G90 478 | 479 | G1 Z{bb.brush_top + bb.clearance_z} F{bb.travel_spd_z} 480 | G1 X{bb.brush_start} F{bb.travel_spd_xy} 481 | G1 Y{position_y} 482 | 483 | # Move nozzle down into brush. 484 | G1 Z{bb.brush_top} F{bb.travel_spd_z} 485 | 486 | # Perform wipe. Wipe direction based off bucket_pos for cool random scrubby routine. 487 | {% for wipes in range(1, (bb.wipe_qty + 1)) %} 488 | G1 X{bb.brush_start + bb.brush_width} F{bb.wipe_spd_xy} 489 | G1 X{bb.brush_start} F{bb.wipe_spd_xy} 490 | {% endfor %} 491 | 492 | RESTORE_GCODE_STATE NAME=BLOBIFIER_CLEAN_state 493 | 494 | 495 | 496 | ########################################################################################## 497 | # Park the nozzle on the tray to prevent oozing during filament swaps. Place this 498 | # extension in the pre-unload extension in mmu_macro_vars.cfg: 499 | # variable_user_pre_unload_extension: "BLOBIFIER_PARK" 500 | # 501 | [gcode_macro BLOBIFIER_PARK] 502 | variable_restore_z: 0 503 | gcode: 504 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 505 | {% set pos = printer.gcode_move.gcode_position %} 506 | {% set pos_max = printer.toolhead.axis_maximum %} 507 | {% set position_y = pos_max.y %} 508 | 509 | SET_GCODE_VARIABLE MACRO=BLOBIFIER_PARK VARIABLE=restore_z VALUE={pos.z} 510 | 511 | SAVE_GCODE_STATE NAME=blobifier_park_state 512 | 513 | {% if "xy" not in printer.toolhead.homed_axes %} 514 | RESPOND MSG="Automatically homing XY" 515 | G28 X Y 516 | {% endif %} 517 | 518 | {% if "z" not in printer.toolhead.homed_axes %} 519 | RESPOND MSG="Automatically homing Z" 520 | G28 Z 521 | {% endif %} 522 | 523 | {% if printer.quad_gantry_level and printer.quad_gantry_level.applied == False %} 524 | RESPOND MSG="Automatically QGL" 525 | quad_gantry_level 526 | {% endif %} 527 | 528 | G90 529 | 530 | G1 E-{bl.retract_before_park} F{bl.retract_speed} 531 | G1 X{bl.brush_start} Y{position_y} F{bl.travel_spd_xy} 532 | G1 X{bl.purgey_x} 533 | G1 Z{bl.tray_top} F{bl.travel_spd_z} 534 | 535 | RESTORE_GCODE_STATE NAME=blobifier_park_state 536 | 537 | ########################################################################################## 538 | # Retract or extend the tray 539 | # POS=[in|out] Retractor extend the tray 540 | # 541 | [gcode_macro BLOBIFIER_SERVO] 542 | # increase this value if the servo doesn't have enough time to fully retract or extend 543 | variable_dwell_time: 200 544 | gcode: 545 | {% set pos = params.POS %} 546 | {% if pos == "in" %} 547 | SET_SERVO SERVO=blobifier ANGLE=180 548 | G4 P{dwell_time} 549 | {% elif pos == "out" %} 550 | SET_SERVO SERVO=blobifier ANGLE=0 551 | G4 P{dwell_time} 552 | {% else %} 553 | {action_respond_info("BLOBIFIER: provide POS=[in|out]")} 554 | {% endif %} 555 | SET_SERVO SERVO=blobifier WIDTH=0 556 | 557 | ########################################################################################## 558 | # Increment the blob count with 1 and check if the bucket is full. Pause 559 | # the printer if it is. 560 | # 561 | [gcode_macro _BLOBIFIER_COUNT] 562 | # Don't change these variables 563 | variable_current_blobs: 0 564 | variable_last_shake: 0 565 | variable_next_shake: 0 566 | gcode: 567 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 568 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 569 | {% if current_blobs >= bl.max_blobs %} 570 | {action_respond_info("BLOBIFIER: Empty purge bucket!")} 571 | M117 Empty purge bucket! 572 | PAUSE 573 | {% else %} 574 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE={current_blobs + 1} 575 | _BLOBIFIER_SAVE_STATE 576 | {action_respond_info( 577 | "BLOBIFIER: Blobs in bucket: %s/%s. Next shake @ %s" 578 | % (current_blobs + 1, bl.max_blobs, next_shake) 579 | )} 580 | {% endif %} 581 | 582 | ########################################################################################## 583 | # Reset the blob count to 0 584 | # 585 | [gcode_macro _BLOBIFIER_COUNT_RESET] 586 | gcode: 587 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE=0 588 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE=0 589 | _BLOBIFIER_SAVE_STATE 590 | 591 | _BLOBIFIER_CALCULATE_NEXT_SHAKE 592 | 593 | ########################################################################################## 594 | # Shake the blob bucket to disperse the blobs 595 | # 596 | [gcode_macro BLOBIFIER_SHAKE_BUCKET] 597 | gcode: 598 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 599 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 600 | {% set original_accel = printer.toolhead.max_accel %} 601 | {% set original_accel_to_decel = printer.toolhead.max_accel_to_decel %} 602 | 603 | {% if "xyz" not in printer.toolhead.homed_axes %} 604 | {action_raise_error("BLOBIFIER: Not homed. Home xyz first")} 605 | {% endif %} 606 | 607 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={count.current_blobs} 608 | _BLOBIFIER_SAVE_STATE 609 | SAVE_GCODE_STATE NAME=shake_bucket 610 | 611 | M400 612 | M117 (^_^) 613 | 614 | G90 615 | {% set shakes = params.SHAKES|default(10)|int %} 616 | {% set position_y = printer.configfile.config["stepper_y"]["position_max"]|float %} 617 | 618 | # move to save y if not already there 619 | {% if printer.toolhead.position.y != position_y %} 620 | G1 X{bl.brush_start} Y{position_y} F{bl.travel_spd_xy} 621 | {% endif %} 622 | 623 | # move up a bit to prevent oozing on base 624 | G1 Z{bl.shaker_arm_z} F{bl.travel_spd_z} 625 | # slide into the slot 626 | G1 X{position_x} F{bl.travel_spd_xy} 627 | 628 | M400 629 | M117 (+(+_+)+) 630 | 631 | SET_VELOCITY_LIMIT ACCEL={bl.shake_accel} ACCEL_TO_DECEL={bl.shake_accel/2} 632 | 633 | # Shake away! 634 | {% for shake in range(1, shakes) %} 635 | G1 Y{position_y - 4} 636 | G1 Y{position_y} 637 | {% endfor %} 638 | 639 | SET_VELOCITY_LIMIT ACCEL={original_accel} ACCEL_TO_DECEL={original_accel_to_decel} 640 | # move out of slot 641 | G1 X{bl.purgey_x} 642 | 643 | M400 644 | M117 (X_x) 645 | 646 | RESTORE_GCODE_STATE NAME=shake_bucket 647 | 648 | ########################################################################################## 649 | # Calculate when the bucket should be shaken. 650 | # 651 | [gcode_macro _BLOBIFIER_CALCULATE_NEXT_SHAKE] 652 | gcode: 653 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 654 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 655 | 656 | {% set remaining_blobs = bl.max_blobs - count.last_shake %} 657 | {% set next_shake = (1 - bl.bucket_shake_frequency) * remaining_blobs + count.last_shake %} 658 | _BLOBIFIER_SAVE_STATE 659 | _BLOBIFIER_SET_NEXT_SHAKE VALUE={next_shake|int} 660 | 661 | ########################################################################################## 662 | # Set when the bucket should be shaken next 663 | # VALUE=[int] At what amount of blobs should it be shaken 664 | # 665 | [gcode_macro _BLOBIFIER_SET_NEXT_SHAKE] 666 | gcode: 667 | {% if params.VALUE %} 668 | {% set next_shake = params.VALUE %} 669 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=next_shake VALUE={next_shake} 670 | _BLOBIFIER_SAVE_STATE 671 | {% else %} 672 | {action_respond_info("BLOBIFIER: Provide parameter VALUE=")} 673 | {% endif %} 674 | 675 | ########################################################################################## 676 | # Some sanity checks 677 | # 678 | [delayed_gcode BLOBIFIER_INIT] 679 | initial_duration: 5.0 680 | gcode: 681 | _BLOBIFIER_INIT 682 | BLOBIFIER_SERVO POS=out 683 | 684 | [gcode_macro _BLOBIFIER_INIT] 685 | gcode: 686 | {% set bl = printer['gcode_macro BLOBIFIER'] %} 687 | {% set raise_z_min = (bl.iteration_z_raise - ((bl.max_iterations_per_blob - 1) * (bl.max_iterations_per_blob) / 2) * bl.iteration_z_change) %} 688 | 689 | # No drop on purging 690 | {% if raise_z_min < 0 %} 691 | {action_emergency_stop("BLOBIFIER: Your current blob settings cause the nozzle to drop on the final iteration(s). increase iteration_z_raise or decrease either iteration_z_change or max_iterations_per_blob.")} 692 | {% endif %} 693 | 694 | # Valid part cooling fan setting 695 | {% if bl.part_cooling_fan != -1 and (bl.part_cooling_fan < 0 or bl.part_cooling_fan > 1) %} 696 | {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))} 697 | {% endif %} 698 | 699 | # Valid bucket shake frequency 700 | {% if bl.bucket_shake_frequency < 0 or bl.bucket_shake_frequency > 1 %} 701 | {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))} 702 | {% endif %} 703 | 704 | # {% if printer.mmu %} 705 | # {% if printer['gcode_macro _MMU_SEQUENCE_VARS'].restore_xy_pos %} 706 | # {action_respond_info("BLOBIFIER: If not using a wipe tower, consider disabling restore_xy_pos in mmu_macro_vars.cfg")} 707 | # {% endif %} 708 | 709 | # # Blobifier park enabled 710 | # {% if printer['gcode_macro _MMU_SEQUENCE_VARS'].user_pre_unload_extension == "BLOBIFIER_PARK" %} 711 | 712 | # # MMU park is enabled 713 | # {% if printer['gcode_macro _MMU_SEQUENCE_VARS'].enable_park %} 714 | # {action_respond_info("BLOBIFIER: MMU park is enabled in mmu_macro_vars.cfg. This could interfere with BLOBIFIER_PARK")} 715 | 716 | # # MMU Park after form tip is enabled 717 | # {% if printer['gcode_macro _MMU_SEQUENCE_VARS'].park_after_form_tip %} 718 | # {action_respond_info("BLOBIFIER: MMU park_after_form_tip in mmu_macro_vars.cfg is enabled and could interfere with BLOBIFIER_PARK")} 719 | # {% endif %} 720 | # {% endif %} 721 | 722 | # # Filametrix installed? 723 | # {% if printer.configfile.config.mmu.form_tip_macro == '_MMU_CUT_TIP' %} 724 | 725 | # # Restore position after CUT disabled 726 | # {% if not printer['gcode_macro _MMU_CUT_TIP_VARS'].restore_position %} 727 | # {action_respond_info("BLOBIFIER: _MMU_CUT_TIP restore_position in mmu_macro_vars.cfg is disabled, this disables BLOBIFIER_PARK's use")} 728 | # {% endif %} 729 | 730 | # # Tray_top too low 731 | # {% if bl.tray_top <= 0.2 %} 732 | # {action_emergency_stop("BLOBIFIER: Both BLOBIFIER_PARK and _MMU_CUT_TIP are enabled, but the tray_top is too low (< 0.2). This will cause the nozzle to run into the bed during cutting")} 733 | # {% endif %} 734 | 735 | # {% endif %} 736 | 737 | # # Blobifier enabled, but Blobifier_park isn't 738 | # {% elif printer['gcode_macro _MMU_SEQUENCE_VARS'].user_post_load_extension == 'BLOBIFIER' %} 739 | # {action_respond_info("BLOBIFIER: Consider setting user_pre_unload_extension in mmu_macro_vars.cfg to BLOBIFIER_PARK")} 740 | # {% endif %} 741 | # {% endif %} 742 | 743 | 744 | [delayed_gcode BLOBIFIER_LOAD_STATE] 745 | initial_duration: 2.0 # Give it some time to boot up 746 | gcode: 747 | {% set sv = printer.save_variables.variables.blobifier %} 748 | 749 | {% if sv %} 750 | # Restore state 751 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=last_shake VALUE={sv.last_shake} 752 | SET_GCODE_VARIABLE MACRO=_BLOBIFIER_COUNT VARIABLE=current_blobs VALUE={sv.current_blobs} 753 | {% endif %} 754 | _BLOBIFIER_CALCULATE_NEXT_SHAKE 755 | 756 | [gcode_macro _BLOBIFIER_SAVE_STATE] 757 | gcode: 758 | {% set count = printer['gcode_macro _BLOBIFIER_COUNT'] %} 759 | {% set sv = {'current_blobs': count.current_blobs, 'last_shake': count.last_shake} %} 760 | SAVE_VARIABLE VARIABLE=blobifier VALUE="{sv}" 761 | 762 | [save_variables] 763 | filename: ~/printer_data/config/blobifier/blob_vars.cfg 764 | 765 | -------------------------------------------------------------------------------- /blobifier_hw.cfg: -------------------------------------------------------------------------------- 1 | ########################################################################################## 2 | # The servo hardware configuration. Change the values to your needs. 3 | # 4 | [servo blobifier] 5 | # Pin for the servo. 6 | pin: PG14 7 | # Adjust this value until a 'BLOBIFIER_SERVO POS=in' retracts the tray fully without a 8 | # buzzing sound 9 | minimum_pulse_width: 0.00053 10 | # Adjust this value until a 'BLOBIFIER_SERVO POS=out' extends the tray fully without a 11 | # buzzing sound 12 | maximum_pulse_width: 0.0023 13 | # Leave this value at 180 14 | maximum_servo_angle: 180 15 | 16 | ########################################################################################## 17 | # The bucket hardware configuration. Change the pin to whatever pin you've connected the 18 | # switch to. 19 | # 20 | [gcode_button bucket] 21 | pin: ^PG15 # The pullup ( ^ ) is important here. 22 | press_gcode: 23 | M117 bucket installed 24 | release_gcode: 25 | M117 bucket removed 26 | _BLOBIFIER_COUNT_RESET 27 | --------------------------------------------------------------------------------