Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- Automated Printing on A1 Mini Tutorial
- ***Disclaimer: This could potentially damage you printer as every machine can have different version, however updating firmware and watching the first tries closely will likely not cause any problem. Use it at your own risks***
- PRINTS MUST BE PLACED ON THE RIGHT SIDE OF THE PLATE, AS CLOSE AS POSSIBLE TO THE A1 MINI ARM, AT Y=100MM maximum (world coordinates)
- Safe: Print a small 5cm tall, 4mm thick cylinder with 2mm outside brim to test first
- Using https://makerworld.com/fr/collections/6408035-print-automation mount and a custom built .gcode generator I made a system to automate print on my A1 Mini as well as speed up the overall process
- #1: In BambuStudio:
- Create a custom printer profile, Click on the little grey pen to edit your printer profile (ONLY TESTED on A1 MINI).
- Add this start gcode
- Under Machine gcode > Machine start G-code:
- ;===== machine: A1 mini =========================
- ;===== date: 20240620 =====================
- ;===== start to heat bed&hotend==========
- M1002 gcode_claim_action : 2
- M1002 set_filament_type:{filament_type[initial_no_support_extruder]}
- M104 S170
- M140 S[bed_temperature_initial_layer_single]
- G392 S0 ;turn off clog detect
- M9833.2
- ;=====start printer sound ===================
- M17
- M400 S1
- M1006 S1
- M1006 A0 B0 L80 C40 D5 M100 E40 F5 N200
- M1006 A0 B0 L80 C43 D5 M100 E43 F5 N200
- M1006 A0 B0 L80 C47 D5 M100 E47 F5 N200
- M1006 A0 B0 L80 C52 D5 M100 E52 F5 N200
- M1006 A0 B0 L80 C40 D5 M100 E40 F5 N200
- M1006 A0 B0 L80 C43 D5 M100 E43 F5 N200
- M1006 A0 B0 L80 C47 D5 M100 E47 F5 N200
- M1006 A0 B0 L80 C52 D5 M100 E52 F5 N200
- M1006 A0 B0 L80 C40 D5 M100 E40 F5 N200
- M1006 A0 B0 L80 C43 D5 M100 E43 F5 N200
- M1006 A0 B0 L80 C47 D5 M100 E47 F5 N200
- M1006 A0 B0 L80 C52 D5 M100 E52 F5 N200
- M1006 W
- M18
- ;=====avoid end stop =================
- G91
- G380 S2 Z30 F1200
- G380 S3 Z-20 F1200
- G1 Z5 F1200
- G90
- ;===== reset machine status =================
- M204 S6000
- M630 S0 P0
- G91
- M17 Z0.3 ; lower the z-motor current
- G90
- M17 X0.7 Y0.9 Z0.5 ; reset motor current to default
- M960 S5 P1 ; turn on logo lamp
- G90
- M83
- M220 S100 ;Reset Feedrate
- M221 S100 ;Reset Flowrate
- M73.2 R1.0 ;Reset left time magnitude
- ;====== cog noise reduction=================
- M982.2 S1 ; turn on cog noise reduction
- ;===== prepare print temperature and material ==========
- M400
- M18
- M109 S100 H170
- M104 S170
- M400
- M17
- M400
- G28 X
- M211 X0 Y0 Z0 ;turn off soft endstop ; turn off soft endstop to prevent protential logic problem
- M975 S1 ; turn on
- G1 X0.0 F30000
- G1 X-13.5 F3000
- M620 M ;enable remap
- M620 S[initial_no_support_extruder]A ; switch material if AMS exist
- G392 S0 ;turn on clog detect
- M1002 gcode_claim_action : 4
- M400
- M1002 set_filament_type:UNKNOWN
- M109 S[nozzle_temperature_initial_layer]
- M104 S250
- M400
- T[initial_no_support_extruder]
- G1 X-13.5 F3000
- M400
- M620.1 E F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60} T{nozzle_temperature_range_high[initial_no_support_extruder]}
- M109 S250 ;set nozzle to common flush temp
- M106 P1 S0
- G92 E0
- G1 E50 F200
- M400
- M1002 set_filament_type:{filament_type[initial_no_support_extruder]}
- M104 S{nozzle_temperature_range_high[initial_no_support_extruder]}
- G92 E0
- G1 E50 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}
- M400
- M106 P1 S178
- G92 E0
- G1 E5 F{filament_max_volumetric_speed[initial_no_support_extruder]/2.4053*60}
- M109 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-20} ; drop nozzle temp, make filament shink a bit
- M104 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-40}
- G92 E0
- G1 E-0.5 F300
- G1 X0 F30000
- G1 X-13.5 F3000
- G1 X0 F30000 ;wipe and shake
- G1 X-13.5 F3000
- G1 X0 F12000 ;wipe and shake
- G1 X0 F30000
- G1 X-13.5 F3000
- M109 S{nozzle_temperature_initial_layer[initial_no_support_extruder]-40}
- G392 S0 ;turn off clog detect
- M621 S[initial_no_support_extruder]A
- M400
- M106 P1 S0
- ;===== prepare print temperature and material end =====
- ;===== mech mode fast check============================
- M1002 gcode_claim_action : 3
- G0 X25 Y175 F20000 ; find a soft place to home
- ;M104 S0
- G28 Z P0 T300; home z with low precision,permit 300deg temperature
- G29.2 S0 ; turn off ABL
- M104 S170
- ; build plate detect
- M1002 judge_flag build_plate_detect_flag
- M622 S1
- G39.4
- M400
- M623
- G1 Z5 F3000
- G1 X90 Y-1 F30000
- M400 P200
- M970.3 Q1 A7 K0 O2
- M974 Q1 S2 P0
- G1 X90 Y0 Z5 F30000
- M400 P200
- M970 Q0 A10 B50 C90 H15 K0 M20 O3
- M974 Q0 S2 P0
- M975 S1
- G1 F30000
- G1 X-1 Y10
- G28 X ; re-home XY
- ;===== wipe nozzle ===============================
- M1002 gcode_claim_action : 14
- M975 S1
- M104 S170 ; set temp down to heatbed acceptable
- M106 S255 ; turn on fan (G28 has turn off fan)
- M211 S; push soft endstop status
- M211 X0 Y0 Z0 ;turn off Z axis endstop
- M83
- G1 E-1 F500
- G90
- M83
- M109 S170
- M104 S140
- G0 X90 Y-4 F30000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X91 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X92 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X93 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X94 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X95 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X96 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X97 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X98 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X99 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X99 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X99 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X99 F10000
- G380 S3 Z-5 F1200
- G1 Z2 F1200
- G1 X99 F10000
- G380 S3 Z-5 F1200
- G1 Z5 F30000
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- G1 X25 Y175 F30000.1 ;Brush material
- G1 Z0.2 F30000.1
- G1 Y185
- G91
- G1 X-30 F30000
- G1 Y-2
- G1 X27
- G1 Y1.5
- G1 X-28
- G1 Y-2
- G1 X30
- G1 Y1.5
- G1 X-30
- G90
- M83
- G1 Z5 F3000
- G0 X50 Y175 F20000 ; find a soft place to home
- G28 Z P0 T300; home z with low precision, permit 300deg temperature
- G29.2 S0 ; turn off ABL
- G0 X85 Y185 F10000 ;move to exposed steel surface and stop the nozzle
- G0 Z-1.01 F10000
- G91
- G2 I1 J0 X2 Y0 F2000.1
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G2 I1 J0 X2
- G2 I-0.75 J0 X-1.5
- G90
- G1 Z5 F30000
- G1 X25 Y175 F30000.1 ;Brush material
- G1 Z0.2 F30000.1
- G1 Y185
- G91
- G1 X-30 F30000
- G1 Y-2
- G1 X27
- G1 Y1.5
- G1 X-28
- G1 Y-2
- G1 X30
- G1 Y1.5
- G1 X-30
- G90
- M83
- G1 Z5
- G0 X55 Y175 F20000 ; find a soft place to home
- G28 Z P0 T300; home z with low precision, permit 300deg temperature
- G29.2 S0 ; turn off ABL
- G1 Z10
- G1 X85 Y185
- G1 Z-1.01
- G1 X95
- G1 X90
- M211 R; pop softend status
- M106 S0 ; turn off fan , too noisy
- ;===== wipe nozzle end ================================
- ;===== wait heatbed ====================
- M1002 gcode_claim_action : 2
- M104 S0
- M190 S[bed_temperature_initial_layer_single];set bed temp
- M109 S140
- G1 Z5 F3000
- G29.2 S1
- G1 X10 Y10 F20000
- ;===== bed leveling ==================================
- ;M1002 set_flag g29_before_print_flag=1
- M1002 judge_flag g29_before_print_flag
- M622 J1
- M1002 gcode_claim_action : 1
- G29 A1 X{first_layer_print_min[0]} Y{first_layer_print_min[1]} I{first_layer_print_size[0]} J{first_layer_print_size[1]}
- M400
- M500 ; save cali data
- M623
- ;===== bed leveling end ================================
- ;===== home after wipe mouth============================
- M1002 judge_flag g29_before_print_flag
- M622 J0
- M1002 gcode_claim_action : 13
- G28 T145
- M623
- ;===== home after wipe mouth end =======================
- M975 S1 ; turn on vibration supression
- ;===== nozzle load line ===============================
- M975 S1
- G90
- M83
- T1000
- G1 X-13.5 Y0 Z10 F10000
- G1 E1.2 F500
- M400
- M1002 set_filament_type:UNKNOWN
- M109 S{nozzle_temperature[initial_extruder]}
- M400
- M412 S1 ; ===turn on filament runout detection===
- M400 P10
- G392 S0 ;turn on clog detect
- M620.3 W1; === turn on filament tangle detection===
- M400 S2
- M1002 set_filament_type:{filament_type[initial_no_support_extruder]}
- ;M1002 set_flag extrude_cali_flag=1
- M1002 judge_flag extrude_cali_flag
- M622 J1
- M1002 gcode_claim_action : 8
- M400
- M900 K0.0 L1000.0 M1.0
- G90
- M83
- G0 X68 Y-4 F30000
- G0 Z0.3 F18000 ;Move to start position
- M400
- G1 X-13.5 Y0 Z10 F10000
- M400
- G1 E10 F{outer_wall_volumetric_speed/2.4*60}
- M983 F{outer_wall_volumetric_speed/2.4} A0.3 H[nozzle_diameter]; cali dynamic extrusion compensation
- M106 P1 S178
- M400 S7
- G1 X0 F18000
- G1 X-13.5 F3000
- G1 X0 F18000 ;wipe and shake
- G1 X-13.5 F3000
- G1 X0 F12000 ;wipe and shake
- G1 X-13.5 F3000
- M400
- M106 P1 S0
- M1002 judge_last_extrude_cali_success
- M622 J0
- M983 F{outer_wall_volumetric_speed/2.4} A0.3 H[nozzle_diameter]; cali dynamic extrusion compensation
- M106 P1 S178
- M400 S7
- G1 X0 F18000
- G1 X-13.5 F3000
- G1 X0 F18000 ;wipe and shake
- G1 X-13.5 F3000
- G1 X0 F12000 ;wipe and shake
- M400
- M106 P1 S0
- M623
- G1 X-13.5 F3000
- M400
- M984 A0.1 E1 S1 F{outer_wall_volumetric_speed/2.4} H[nozzle_diameter]
- M106 P1 S178
- M400 S7
- G1 X0 F18000
- G1 X-13.5 F3000
- G1 X0 F18000 ;wipe and shake
- G1 X-13.5 F3000
- G1 X0 F12000 ;wipe and shake
- G1 X-13.5 F3000
- M400
- M106 P1 S0
- M623 ; end of "draw extrinsic para cali paint"
- ;===== extrude cali test ===============================
- M104 S{nozzle_temperature_initial_layer[initial_extruder]}
- G90
- M83
- G90
- M83
- G0 E50 F100
- M400
- M400
- ;========turn off light and wait extrude temperature =============
- M1002 gcode_claim_action : 0
- M400 ; wait all motion done before implement the emprical L parameters
- ;===== for Textured PEI Plate , lower the nozzle as the nozzle was touching topmost of the texture when homing ==
- ;curr_bed_type={curr_bed_type}
- {if curr_bed_type=="Textured PEI Plate"}
- G29.1 Z{-0.02} ; for Textured PEI Plate
- {endif}
- M960 S1 P0 ; turn off laser
- M960 S2 P0 ; turn off laser
- M106 S0 ; turn off fan
- M106 P2 S0 ; turn off big fan
- M106 P3 S0 ; turn off chamber fan
- M975 S1 ; turn on mech mode supression
- G90
- M83
- T1000
- M211 X0 Y0 Z0 ;turn off soft endstop
- M1007 S1
- #################### END OF MACHINE START G-CODE ###############
- 2.Add this end g-code
- ;===== date: 20231229 =====================
- ;turn off nozzle clog detect
- G392 S0
- M400 ; wait for buffer to clear
- G92 E0 ; zero the extruder
- G1 E-0.8 F1800 ; retract
- G1 Z{max_layer_z + 0.5} F900 ; lower z a little
- G1 X0 Y{first_layer_center_no_wipe_tower[1]} F18000 ; move to safe pos
- G1 X-13.0 F3000 ; move to safe pos
- {if !spiral_mode && print_sequence != "by object"}
- M1002 judge_flag timelapse_record_flag
- M622 J1
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M400 P100
- M971 S11 C11 O0
- M991 S0 P-1 ;end timelapse at safe pos
- M623
- {endif}
- M140 S0 ; turn off bed
- M106 S0 ; turn off fan
- M106 P2 S0 ; turn off remote part cooling fan
- M106 P3 S0 ; turn off chamber cooling fan
- ;G1 X27 F15000 ; wipe
- ; pull back filament to AMS
- M620 S255
- G1 X181 F12000
- T255
- G1 X0 F18000
- G1 X-13.0 F3000
- G1 X0 F18000 ; wipe
- M621 S255
- M104 S0 ; turn off hotend
- M400 ; wait all motion done
- M17 S
- M17 Z0.4 ; lower z motor current to reduce impact if there is something in the bottom
- {if (max_layer_z + 100.0) < 180}
- G1 Z{max_layer_z + 100.0} F600
- G1 Z{max_layer_z +98.0}
- {else}
- G1 Z180 F600
- G1 Z180
- {endif}
- M400 P100
- M17 R ; restore z current
- G90
- G1 X-13 Y180 F3600
- G91
- G1 Z-1 F600
- G90
- M83
- M220 S100 ; Reset feedrate magnitude
- M201.2 K1.0 ; Reset acc magnitude
- M73.2 R1.0 ;Reset left time magnitude
- M1002 set_gcode_claim_speed_level : 0
- ;=====printer finish sound=========
- M17
- M400 S1
- M1006 S1
- M1006 A0 B20 L100 C37 D20 M100 E42 F20 N100
- M1006 A0 B10 L100 C44 D10 M100 E44 F10 N100
- M1006 A0 B10 L100 C46 D10 M100 E46 F10 N100
- M1006 A44 B20 L100 C39 D20 M100 E48 F20 N100
- M1006 A0 B10 L100 C44 D10 M100 E44 F10 N100
- M1006 A0 B10 L100 C0 D10 M100 E0 F10 N100
- M1006 A0 B10 L100 C39 D10 M100 E39 F10 N100
- M1006 A0 B10 L100 C0 D10 M100 E0 F10 N100
- M1006 A0 B10 L100 C44 D10 M100 E44 F10 N100
- M1006 A0 B10 L100 C0 D10 M100 E0 F10 N100
- M1006 A0 B10 L100 C39 D10 M100 E39 F10 N100
- M1006 A0 B10 L100 C0 D10 M100 E0 F10 N100
- M1006 A44 B10 L100 C0 D10 M100 E48 F10 N100
- M1006 A0 B10 L100 C0 D10 M100 E0 F10 N100
- M1006 A44 B20 L100 C41 D20 M100 E49 F20 N100
- M1006 A0 B20 L100 C0 D20 M100 E0 F20 N100
- M1006 A0 B20 L100 C37 D20 M100 E37 F20 N100
- M1006 W
- ;=====printer finish sound=========
- M400 S1
- M18 X Y Z
- ###########END OF MACHINE END G-CODE #######
- Save your profile as a easy to remember name: such as: A1 MINI - FARMLOOP - v0beta
- Create a small cylinder on a empty plate, right click add primitive, cylinder, scale (top right) deactivate uniform scale, 5mm Z, 2mm X, 2mm Y, print setting > Others > add outer brim 2mm width
- Slice plate
- Under Slice plate button (drop down menu), select "export plate sliced file"
- #2 Use the website mini-tool
- Create a file a1minihack-minitool.html
- paste>>
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>G-code Farm Loop Modifier (.3mf Support)</title>
- <style>
- * { margin: 0; padding: 0; box-sizing: border-box; }
- body { font-family: Arial, sans-serif; background: #f5f5f5; padding: 20px; }
- .container { max-width: 1000px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
- h1 { color: #333; margin-bottom: 20px; text-align: center; }
- .section { margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
- .section h2 { color: #555; margin-bottom: 15px; font-size: 1.2em; }
- .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 15px; }
- .param-group { display: flex; flex-direction: column; }
- .param-group label { margin-bottom: 5px; font-weight: bold; color: #666; }
- .param-group input, .param-group select { padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
- .btn { padding: 10px 20px; color: white; border: none; border-radius: 4px; cursor: pointer; margin-right: 10px; margin-bottom: 10px; }
- .btn:disabled { background: #ccc; cursor: not-allowed; }
- .btn-primary { background: #007bff; }
- .btn-primary:hover:not(:disabled) { background: #0056b3; }
- .btn-success { background: #28a745; }
- .btn-success:hover:not(:disabled) { background: #218838; }
- .btn-info { background: #17a2b8; }
- .btn-info:hover:not(:disabled) { background: #138496; }
- .file-input { padding: 10px; border: 2px dashed #ccc; border-radius: 4px; text-align: center; cursor: pointer; }
- .file-input:hover { border-color: #007bff; }
- .file-info { background: #e8f5e8; padding: 10px; border-radius: 4px; margin-top: 10px; }
- .status { padding: 10px; border-radius: 4px; margin-bottom: 15px; }
- .status.success { background: #d4edda; color: #155724; }
- .status.error { background: #f8d7da; color: #721c24; }
- .status.info { background: #cce7ff; color: #004085; }
- .status.warning { background: #fff3cd; color: #856404; }
- .preview { background: #f8f9fa; padding: 15px; border-radius: 4px; max-height: 400px; overflow-y: auto; }
- .preview pre { margin: 0; white-space: pre-wrap; font-family: monospace; font-size: 0.9em; }
- .hidden { display: none; }
- .file-type-badge { display: inline-block; padding: 3px 8px; color: white; border-radius: 12px; font-size: 0.8em; margin-left: 10px; }
- .file-type-badge.threemf { background: #ff6b35; }
- .file-type-badge.gcode { background: #28a745; }
- .progress { width: 100%; height: 20px; background: #e9ecef; border-radius: 10px; overflow: hidden; margin: 10px 0; }
- .progress-bar { height: 100%; background: #007bff; transition: width 0.3s ease; }
- </style>
- </head>
- <body>
- <div class="container">
- <h1>G-code Farm Loop Modifier (.3mf Support)</h1>
- <div class="section">
- <h2>Load File</h2>
- <div class="file-input" onclick="document.getElementById('fileInput').click()">
- <input type="file" id="fileInput" accept=".gcode,.g,.txt,.3mf" style="display: none;">
- Click to select G-code file (.gcode, .g, .txt) or 3MF file (.3mf)
- </div>
- <div id="fileInfo" class="file-info hidden"></div>
- <div id="progressContainer" class="hidden">
- <div class="progress">
- <div id="progressBar" class="progress-bar" style="width: 0%"></div>
- </div>
- <div id="progressText">Processing...</div>
- </div>
- </div>
- <div class="section">
- <h2>Parameters</h2>
- <div class="grid">
- <div class="param-group">
- <label for="loopCount">Number of Loops</label>
- <input type="number" id="loopCount" min="1" max="50" value="3">
- </div>
- <div class="param-group">
- <label for="cooldownTemp">Cooldown Temperature (°C)</label>
- <input type="number" id="cooldownTemp" min="20" max="200" value="25">
- </div>
- <div class="param-group">
- <label for="waitTime">Wait Time (seconds)</label>
- <input type="number" id="waitTime" min="0" max="3600" value="30">
- </div>
- <div class="param-group">
- <label for="wipeEnabled">Enable Wipe Sequence</label>
- <select id="wipeEnabled" disabled>
- <option value="true" selected>Yes (Always Enabled)</option>
- </select>
- </div>
- </div>
- </div>
- <div class="section">
- <h2>Process</h2>
- <div id="status" class="status hidden"></div>
- <button id="processBtn" class="btn btn-primary" disabled>Process G-code</button>
- <button id="previewBtn" class="btn btn-primary" disabled>Preview Added Blocks</button>
- <div id="downloadSection" class="hidden">
- <button id="downloadGcodeBtn" class="btn btn-success">Download G-code</button>
- <button id="download3mfBtn" class="btn btn-info hidden">Download 3MF</button>
- </div>
- </div>
- <div id="previewSection" class="section hidden">
- <h2>Preview - New G-code Blocks Added (First Loop)</h2>
- <div class="preview">
- <pre id="previewContent"></pre>
- </div>
- </div>
- </div>
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js"></script>
- <script>
- class GCodeFarmModifier {
- constructor() {
- this.originalGCode = '';
- this.modifiedGCode = '';
- this.fileName = '';
- this.fileType = '';
- this.addedBlocks = '';
- this.threemfZip = null;
- this.gcodeFilePath = null;
- this.init();
- }
- init() {
- const $ = id => document.getElementById(id);
- $('fileInput').addEventListener('change', e => this.loadFile(e));
- $('processBtn').addEventListener('click', () => this.process());
- $('downloadGcodeBtn').addEventListener('click', () => this.downloadGcode());
- $('download3mfBtn').addEventListener('click', () => this.download3mf());
- $('previewBtn').addEventListener('click', () => this.preview());
- }
- updateProgress(percent, text) {
- const container = document.getElementById('progressContainer');
- const bar = document.getElementById('progressBar');
- const textEl = document.getElementById('progressText');
- container.classList.toggle('hidden', false);
- bar.style.width = percent + '%';
- textEl.textContent = text;
- if (percent >= 100) setTimeout(() => container.classList.add('hidden'), 2000);
- }
- async loadFile(event) {
- const file = event.target.files[0];
- if (!file) return;
- this.fileName = file.name;
- this.fileType = file.name.toLowerCase().endsWith('.3mf') ? '3mf' : 'gcode';
- this.reset();
- try {
- this.fileType === '3mf' ? await this.load3mf(file) : await this.loadGcode(file);
- } catch (error) {
- this.showStatus(`Error loading file: ${error.message}`, 'error');
- }
- }
- reset() {
- this.originalGCode = '';
- this.modifiedGCode = '';
- this.addedBlocks = '';
- this.threemfZip = null;
- this.gcodeFilePath = null;
- }
- async loadGcode(file) {
- this.updateProgress(30, 'Reading G-code file...');
- const text = await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = e => resolve(e.target.result);
- reader.onerror = () => reject(new Error('Failed to read file'));
- reader.readAsText(file);
- });
- if (!text?.trim()) throw new Error('G-code file is empty');
- this.originalGCode = text;
- this.updateProgress(100, 'G-code loaded successfully!');
- this.showFileInfo(file);
- this.updateButtons();
- }
- async load3mf(file) {
- this.updateProgress(30, 'Parsing 3MF structure...');
- const buffer = await new Promise((resolve, reject) => {
- const reader = new FileReader();
- reader.onload = e => resolve(e.target.result);
- reader.onerror = () => reject(new Error('Failed to read file'));
- reader.readAsArrayBuffer(file);
- });
- const zip = new JSZip();
- this.threemfZip = await zip.loadAsync(buffer);
- this.updateProgress(70, 'Extracting G-code...');
- const gcodeResult = await this.extractGcodeFrom3mf(this.threemfZip);
- if (!gcodeResult?.content?.trim()) throw new Error('No G-code found in 3MF file');
- this.originalGCode = gcodeResult.content;
- this.gcodeFilePath = gcodeResult.path;
- this.updateProgress(100, 'Complete!');
- this.showFileInfo(file);
- this.updateButtons();
- }
- async extractGcodeFrom3mf(zip) {
- const paths = ['Metadata/plate_1.gcode', 'Metadata/plate_2.gcode', 'Metadata/plate_3.gcode',
- 'Metadata/plate_4.gcode', 'Metadata/plate_5.gcode', 'Metadata/print.gcode'];
- for (const path of paths) {
- const file = zip.file(path);
- if (file) {
- try {
- const content = await file.async('text');
- if (this.isGcode(content)) return { content, path };
- } catch (e) { continue; }
- }
- }
- // Search all files
- const allFiles = [];
- zip.forEach((path, file) => !file.dir && allFiles.push(path));
- for (const path of allFiles.sort((a, b) => (a.includes('.gcode') ? -1 : 1))) {
- const file = zip.file(path);
- if (file) {
- try {
- const content = await file.async('text');
- if (this.isGcode(content)) return { content, path };
- } catch (e) { continue; }
- }
- }
- return null;
- }
- isGcode(content) {
- if (!content || content.length < 50) return false;
- const patterns = [/^G[0-9]+/m, /^M[0-9]+/m, /^T[0-9]+/m, /; generated by/i, /G28/, /G1.*E/];
- return patterns.filter(p => p.test(content)).length >= 3;
- }
- showFileInfo(file) {
- const info = document.getElementById('fileInfo');
- const badge = this.fileType === '3mf' ?
- '<span class="file-type-badge threemf">3MF</span>' :
- '<span class="file-type-badge gcode">G-code</span>';
- const lines = this.originalGCode.split('\n').length;
- const size = (file.size / 1024).toFixed(1);
- info.innerHTML = `<strong>File:</strong> ${file.name} ${badge}<br>
- <strong>Size:</strong> ${size} KB<br>
- <strong>G-code Lines:</strong> ${lines}`;
- info.classList.remove('hidden');
- }
- updateButtons() {
- const hasFile = !!this.originalGCode;
- const hasModified = !!this.modifiedGCode;
- document.getElementById('processBtn').disabled = !hasFile;
- document.getElementById('previewBtn').disabled = !hasFile;
- document.getElementById('downloadSection').classList.toggle('hidden', !hasFile);
- document.getElementById('downloadGcodeBtn').disabled = !hasModified;
- document.getElementById('download3mfBtn').disabled = !hasModified;
- document.getElementById('download3mfBtn').classList.toggle('hidden', this.fileType !== '3mf');
- }
- showStatus(message, type = 'info') {
- const status = document.getElementById('status');
- status.textContent = message;
- status.className = status ${type};
- status.classList.remove('hidden');
- }
- process() {
- if (!this.originalGCode) return this.showStatus('Please load a file first.', 'error');
- try {
- const params = {
- loopCount: parseInt(document.getElementById('loopCount').value),
- cooldownTemp: parseInt(document.getElementById('cooldownTemp').value),
- waitTime: parseInt(document.getElementById('waitTime').value)
- };
- this.showStatus('Processing G-code...', 'info');
- const { header, printCore, footer } = this.parseGCode();
- this.modifiedGCode = this.generateLoops(header, printCore, footer, params);
- this.generatePreview(params);
- this.showStatus(`Successfully processed G-code with ${params.loopCount} loops!`, 'success');
- this.updateButtons();
- } catch (error) {
- this.showStatus(`Error: ${error.message}`, 'error');
- }
- }
- parseGCode() {
- const lines = this.originalGCode.split('\n');
- let headerEnd = lines.findIndex(line => {
- const l = line.trim();
- return (l.startsWith('G1') && l.includes('E')) || l.includes('LAYER_CHANGE') ||
- l.includes('layer') || (l.includes('Z0.') && l.startsWith('G1'));
- });
- let footerStart = -1;
- for (let i = lines.length - 1; i >= 0; i--) {
- const l = lines[i].trim();
- if (l.includes('M400') || l.includes('M104 S0') || l.includes('M140 S0') ||
- l.includes('M84') || (l.startsWith('G1') && l.includes('Z'))) {
- footerStart = i;
- break;
- }
- }
- if (headerEnd === -1) headerEnd = Math.min(50, Math.floor(lines.length * 0.1));
- if (footerStart === -1) footerStart = Math.max(lines.length - 20, Math.floor(lines.length * 0.9));
- return {
- header: lines.slice(0, headerEnd).join('\n'),
- printCore: lines.slice(headerEnd, footerStart).join('\n'),
- footer: lines.slice(footerStart).join('\n')
- };
- }
- generateLoops(header, printCore, footer, params) {
- let result = '';
- const { loopCount, cooldownTemp, waitTime } = params;
- for (let i = 1; i <= loopCount; i++) {
- result += \n; === LOOP ${i} OF ${loopCount} ===\n;
- if (header?.trim()) {
- result += (i === 1 ? this.updateTimeEstimates(header, params) : header);
- if (!header.endsWith('\n')) result += '\n';
- }
- if (printCore?.trim()) {
- result += printCore;
- if (!printCore.endsWith('\n')) result += '\n';
- }
- result += '\n' + this.getWipeSequence(cooldownTemp) + '\n';
- if (i < loopCount) {
- result += \n; === SETUP FOR NEXT LOOP ===\n;
- result += M104 S150 ; Set hotend temperature\n;
- result += M140 S${cooldownTemp} ; Set bed temperature\n;
- result += M109 S150 ; Wait for hotend temperature\n;
- result += M190 S${cooldownTemp} ; Wait for bed temperature\n;
- result += G4 S${waitTime} ; Wait between loops\n;
- }
- }
- if (footer?.trim()) {
- result += '\n' + footer;
- if (!footer.endsWith('\n')) result += '\n';
- }
- return result;
- }
- generatePreview(params) {
- const { loopCount, cooldownTemp, waitTime } = params;
- const bedCooldownTime = this.calculateBedCooldownTime(cooldownTemp);
- const formatTime = s => ${Math.floor(s/60)}m ${s%60}s;
- this.addedBlocks = `; === LOOP 1 OF ${loopCount} ===
- ; HEADER_BLOCK_START
- ; BambuStudio [version]
- ; model printing time: [original_time × ${loopCount}]; total estimated time: [includes cooldown & wipe]
- ; [Original G-code header and print core would be here]
- ; === COOLDOWN & WIPE SEQUENCE ===
- ; Bed cooldown time (65°C → ${cooldownTemp}°C): ${formatTime(bedCooldownTime)}
- ; Wipe sequence time: 1m 10s (2x speed)
- ${this.getWipeSequence(cooldownTemp)}
- ${loopCount > 1 ? `; === SETUP FOR NEXT LOOP ===
- ; Wait time between loops: ${formatTime(waitTime)}
- M104 S150 ; Set hotend temperature
- M140 S${cooldownTemp} ; Set bed temperature
- M109 S150 ; Wait for hotend temperature
- M190 S${cooldownTemp} ; Wait for bed temperature
- G4 S${waitTime} ; Wait between loops
- ` : ''}; === TIME BREAKDOWN FOR ${loopCount} LOOPS ===
- ; Total bed cooldown time: ${formatTime(bedCooldownTime * loopCount)}
- ; Total wipe time: ${formatTime(70 * loopCount)}
- ; Total wait time between loops: ${formatTime(waitTime * (loopCount - 1))}
- ; Print time multiplied by ${loopCount} loops`;
- }
- updateTimeEstimates(header, params) {
- const { loopCount, cooldownTemp, waitTime } = params;
- const lines = header.split('\n');
- let foundBambuStudio = false;
- return lines.map((line, index) => {
- // Check if this line is HEADER_BLOCK_START
- if (line.trim() === '; HEADER_BLOCK_START') {
- foundBambuStudio = false;
- return line;
- }
- // Check if this line is BambuStudio version line
- if (line.trim().match(/^;\s*BambuStudio\s+[\d.]+/)) {
- foundBambuStudio = true;
- return line;
- }
- // Check if this is the time estimation line after BambuStudio
- if (foundBambuStudio && line.includes('model printing time:') && line.includes('total estimated time:')) {
- const bambuTimeMatch = line.match(/^(;\s*model printing time:\s*)([^;]+)(;\s*total estimated time:\s*)([^;]*)(.*?)$/);
- if (bambuTimeMatch) {
- const [, prefix, modelTime, middle, totalTime, suffix] = bambuTimeMatch;
- const originalSeconds = this.parseTime(modelTime.trim());
- const additionalTime = (180 + this.calculateBedCooldownTime(cooldownTemp) + 70) loopCount + waitTime (loopCount - 1);
- const newModelTime = originalSeconds * loopCount;
- const newTotalTime = newModelTime + additionalTime;
- foundBambuStudio = false; // Reset for next occurrence
- return ${prefix}${this.formatTime(newModelTime)}${middle}${this.formatTime(newTotalTime)}${suffix};
- }
- }
- // Fallback for other time estimation formats
- const timeMatch = line.match(/^(;\s*estimated printing time.*?:?\s*)(\d+[hmsd\s]+)(.*)$/i);
- if (timeMatch) {
- const [, prefix, timeStr, suffix] = timeMatch;
- const originalSeconds = this.parseTime(timeStr);
- const additionalTime = (180 + this.calculateBedCooldownTime(cooldownTemp) + 70) loopCount + waitTime (loopCount - 1);
- return ${prefix}${this.formatTime(originalSeconds * loopCount + additionalTime)}${suffix};
- }
- return line;
- }).join('\n');
- }
- calculateBedCooldownTime(cooldownTemp) {
- let totalTime = 0;
- let currentTemp = 65;
- while (currentTemp > cooldownTemp) {
- totalTime += currentTemp > 50 ? 12 : currentTemp > 40 ? 18 : 30;
- currentTemp--;
- }
- // Add extra time for temperature stability and verification
- totalTime += 90; // 30s + 60s additional cooling time
- return totalTime;
- }
- parseTime(timeStr) {
- const matches = {
- d: timeStr.match(/(\d+)d/),
- h: timeStr.match(/(\d+)h/),
- m: timeStr.match(/(\d+)m/),
- s: timeStr.match(/(\d+)s/)
- };
- return (matches.d ? parseInt(matches.d[1]) * 86400 : 0) +
- (matches.h ? parseInt(matches.h[1]) * 3600 : 0) +
- (matches.m ? parseInt(matches.m[1]) * 60 : 0) +
- (matches.s ? parseInt(matches.s[1]) : 0);
- }
- formatTime(seconds) {
- const units = [
- [86400, 'd'],
- [3600, 'h'],
- [60, 'm'],
- [1, 's']
- ];
- let result = '';
- for (const [divisor, unit] of units) {
- const count = Math.floor(seconds / divisor);
- if (count > 0) {
- result += ${count}${unit} ;
- seconds %= divisor;
- }
- }
- return result.trim() || '0s';
- }
- getWipeSequence(cooldownTemp) {
- const positions = [160, 140, 120, 100, 80, 60, 40, 20, 0];
- const wipeCommands = positions.map(x => G1 X${x} F20000\nG1 Y0 F2000\nG1 Y185 F20000).join('\n');
- return `; === COOLDOWN BEFORE WIPE ===
- M104 S0 ; Turn off hotend
- M140 S${cooldownTemp} ; Set bed to cooldown temperature
- ; === ENHANCED COOLING WAIT ===
- ; Wait for bed to cool down with temperature monitoring
- M190 S${cooldownTemp} ; Wait for bed to reach cooldown temperature
- G4 S30 ; Additional wait to ensure temperature stability
- ; === TEMPERATURE VERIFICATION ===
- ; If bed is still too hot, wait additional time
- M117 Cooling bed to ${cooldownTemp}C ; Display message
- G4 S60 ; Extra cooling time to ensure proper temperature
- ; === SOUND BEFORE WIPE ===
- M17
- M400 S1
- M1006 S1
- M1006 A0 B0 L100 C37 D10 M100 E37 F10 N100
- M1006 A43 B10 L100 C39 D10 M100 E46 F10 N100
- M1006 W
- M18
- G4 S10
- ; === WIPE SEQUENCE (2x Speed) ===
- G1 X160 Y185 F20000 ; Move to rear-right (2x speed)
- G1 Z2 F600 ; Set wipe height
- M400 ; Wait for moves
- ${wipeCommands}
- M400 ; Wait for wipe complete`;
- }
- downloadGcode() {
- if (!this.modifiedGCode) return this.showStatus('Please process the G-code first.', 'warning');
- const blob = new Blob([this.modifiedGCode], { type: 'text/plain' });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
- a.href = url;
- a.download = this.generateFileName('gcode');
- a.click();
- URL.revokeObjectURL(url);
- this.showStatus('G-code downloaded successfully!', 'success');
- }
- async download3mf() {
- if (!this.modifiedGCode || !this.threemfZip) return this.showStatus('Please process the G-code first.', 'warning');
- try {
- this.showStatus('Creating modified 3MF file...', 'info');
- const newZip = new JSZip();
- // Copy all files
- const promises = [];
- this.threemfZip.forEach((path, file) => {
- if (!file.dir) {
- promises.push(file.async('arraybuffer').then(content => newZip.file(path, content)));
- }
- });
- await Promise.all(promises);
- // Update G-code
- newZip.file(this.gcodeFilePath || 'Metadata/print.gcode', this.modifiedGCode);
- const content = await newZip.generateAsync({
- type: 'blob',
- compression: 'DEFLATE',
- compressionOptions: { level: 6 }
- });
- const url = URL.createObjectURL(content);
- const a = document.createElement('a');
- a.href = url;
- a.download = this.generateFileName('3mf');
- a.click();
- URL.revokeObjectURL(url);
- this.showStatus('3MF file downloaded successfully!', 'success');
- } catch (error) {
- this.showStatus(`Error creating 3MF file: ${error.message}`, 'error');
- }
- }
- generateFileName(extension) {
- const params = {
- loopCount: document.getElementById('loopCount').value,
- cooldownTemp: document.getElementById('cooldownTemp').value
- };
- const now = new Date();
- const timestamp = now.toISOString().slice(0, 10).replace(/-/g, '');
- const time = now.toTimeString().slice(0, 8).replace(/:/g, '');
- const baseName = this.fileName.replace(/\.[^/.]+$/, '');
- return ${time}-${baseName}_${timestamp}-${params.loopCount}loop_${params.cooldownTemp}bed.${extension};
- }
- preview() {
- const section = document.getElementById('previewSection');
- const content = document.getElementById('previewContent');
- content.textContent = this.addedBlocks || 'No processed G-code available. Please process the file first.';
- section.classList.remove('hidden');
- section.scrollIntoView({ behavior: 'smooth' });
- }
- }
- document.addEventListener('DOMContentLoaded', () => new GCodeFarmModifier());
- </script>
- </body>
- </html>
- 3. Save
- 4. Open with your web browser
- 5. Load your file, select your settings, verify the earth won't be swallowed in a blackhole, and download your 3MF file (3MF is a zip file, you can inspect it by changing the extension, just sayin')
- #3 bonus lifehack for bambulab
- Go to your printer, find the ip adress
- Note the unlock PIN
- Note the ip adress
- Go to your computer
- Download Filezilla https://filezilla-project.org/
- Open Filezilla, Go to left corner, File > add site
- Select Protocol FTP
- FTP Implicit over TLS
- Normal Auth
- Add the noted ipadress of the printer
- Add the username "bblp" (default by bambulab)
- Add the noted PIN as password
- Save
- COnnect to the printer.
- #4 Drag and drop the 3MF file to the /model fodler in the printer folders tree (on the right in Filezilla)
- #5 In Bambulab Device > Storage > model > Print your <TheLooped3dFile>.3MF
- #6 Manually>> GO to your printer and print.
- Thank you,
- Hoping that helps, feel free to suggest any changes
Advertisement
Add Comment
Please, Sign In to add comment