Recovering From Failure (with g-code)
“Success is never final and failure never fatal. It’s courage that counts.” - Commonly misattributed to Winston Churchill
3D printers are more reliable than ever. Over the past decade, manufacturers and enthusiasts have substantially improved the software that prepares the prints, the firmware that operates the machines, and the hardware that monitors temperatures and filament. Yet however robust these components have become, failure in 3D FDM/FFF printing remains an omnipresent potential, a constant reminder not just of the experimental nature of the process but more broadly of the folly of hubris and the inevitability of decline and death. This is a short blog post introducing g-code and then discussing how we might manipulate it to recover from printing failures, in particular ones in which the model is left largely undisturbed: power failure, filament jam, heater error, network disconnection, etc.
The most common (and most accessible) slicers for 3D printing tend to isolate users from what happens under the hood. The rhetoric of printing so centers on abstractions like layers and walls and infill that it’s easy to overlook the fact that FDM printers are just boxes full of motors and heaters and that slicer g-code output is just a linear assortment of simple commands to control those components. G-code is simple, but it’s inaccessible due to its typically vast scale rather than its complexity. Better understanding how g-code works will allow customization of printer function and atypical operation modes, such as our present topic.
A quick look at g-code
G-code was developed in the 1950s and 60s for numerical control of machine tools and both the language itself and the processing pipeline of converting abstract computer models to sequential physical control were a natural fit for 3D printing. It is specified in ISO 6983-1, “Automation systems and integration — Numerical control of machines — Program format and definitions of address words”, although it’s probably much more helpful to look at the particular flavors (instruction sets) designed for your particular 3D printer. For example, many printers, including our Lulzbot TAZ 5, use the Marlin firmware g-code flavor. Another popular flavor is reprap. Using the right flavor of g-code is very important, since some functions are only available on a subset of printers and certain codes in different flavors map to different commands.
To get us started, let’s take a look at the default start g-code produced by the current version of Cura Lulzbot Edition for the TAZ 5 printer with standard toolhead. The start g-code simply contains the instructions that handle the preparatory tasks at the start of all prints and most slicers allow you to customize these through the printer configuration. Some slicers optionally export binary-encoded g-code and some platforms are binary-only (for example, the Sanguino3 s3g/x3g g-code used, notably, by early Makerbots). Those will require an additional decoding step and re-encoding step.
;Generated by Cura LulzBot Edition GCodeWriter Version: 3.6.20
;FLAVOR:Marlin
;TIME:8122
;Filament used: 3.39839m
;Layer height: 0.2
;POSTPROCESSED
;Generated with Cura_SteamEngine 3.6.20
M82 ;absolute extrusion mode
;This G-Code has been generated specifically for the LulzBot TAZ 5 with standard extruder
M75 ; start GLCD timer
M140 S60.0 ; start bed heating up
G90 ; absolute positioning
M107 ; disable fans
M82 ; set extruder to absolute mode
G28 X0 Y0 ; home X and Y
G28 Z0 ; home Z
G1 Z15.0 F175 ; move extruder up
M117 Heating... ; progress indicator message on LCD
M109 R210 ; wait for extruder to reach printing temp
M190 R60.0 ; wait for bed to reach printing temp
G92 E0 ; set extruder position to 0
G1 F200 E10 ; prime the nozzle with filament
G92 E0 ; re-set extruder position to 0
G1 F175 ; set travel speed
M203 X192 Y208 Z3 ; set limits on travel speed
M117 TAZ 5 Printing... ; progress indicator message on LCD
M221 S100 T0
; ...
Each line represents a single instruction. A semi-colon terminates the command, so whatever follows is a comment explaining the command. Typically, 3D printing g-code comprise g- (“preparatory” commands) and m-codes (“miscellaneous “ functions). G-codes are typically used to indicate and track motion (including extruder motion), while M-codes control everything else (heaters, fans, lights, LCDs, custom scripts like loading/unloading). Common variables are X,Y,Z, and E, which are references to the position on the three spacial axes and the extruder axis; and F, which indicates the feed rate (speed, for 3D printers). Extrusion, E, is tracked in the same way as X,Y, and Z, in distance of travel (of the filament feed).
Going line by line, we can see the commands that precede each print on the TAZ: the g-code starts the print timer, heats up the bed and extruder, homes the head, and primes the nozzle with filament by extruding a little bit of material. The G0 and G1 commands are typically the most common commands in 3D printer g-code by a very large margin. These commands the printer to perform linear moves to particular combinations of X,Y,Z, and E positions. For efficiency, slicers use G0 for moves that do not include extrusion and G1 for moves that do.
Two interesting commands in this start g-code to note are G90 and M82, which set the printer to absolute position mode for the print head and the extruder respectively (G91 and M83 set the head and the extruder to relative position mode). Absolute position means that subsequent moves will index on a preset home point rather than relative to the current position. That is, in absolute mode, the command “G1 X5” will command the printer to move the head to 5mm on the X axis from the home coordinate, very close to the left edge on the TAZ. In relative mode, the same command “G1 X5” will move the head 5mm in the positive X axes from the current position of the head (to the right when facing the printer, assuming the orientation of axes on the TAZ). G92 saves the current extruder position as zero, which is useful after performing a nozzle prime to avoid throwing off the rest of the g-code.
These modes will be important to remember shortly.
The main bulk of the slicer output continues in this fashion after the start g-code. My full example g-code file contains a little more than 92,000 lines, of which roughly 91,000 are G0 and G1 move commands. An end g-code snippet concludes the print and typically contains housekeeping functions like parking the head to the side and cooling down the extruder.
Modifying g-code to recover a print failure
Earlier in the week, I used the TAZ to print a model that contained a number of color change commands at specific layers, performed with a call to the Marlin filament change g-code command M600. These changes were inserted using Lulzbot Cura’s built-in ColorChange postprocessor extension. The ColorChange script seems to have a bug in it such that the layer detection logic does not reliably function. This means that the layer index is often incorrect and should be verified in the g-code.
During one of these color changes, I overenthusiastically fed new filament to purge the nozzle of the preceding color and triggered the Marlin firmware’s safety shutoff, presumably because the printer registered an unexpected dip in temperature as I manually sent material through the extruder. This reasonably caused a fatal error state on the printer where the only option was to reset the machine entirely.
I was lucky for a number of reasons. Because I was working on the printer, I noticed the error immediately and could recycle power and reset the bed temperature to the printing temperature. This avoided bed adhesion problems due to thermal contraction. The error also occurred at the very beginning of a line where I had triggered the M600 filament change command, so I knew exactly which line of g-code to return to.
In case of a more typical failure where filament jams or runs out, you might measure the height of the print at the time of failure using a good pair of calipers and then calculate the line number or simply locate the accompanying Z-move (this may not be compatible with fancy autoleveling methods because of the more dynamic Z axes). It might be better to locate the particular move and skip past it to avoid repeating lines (basically minor extruder crashes), but this is obviously a bit tricky. If the failure occurred during infill rather than wall printing, skipping a few internal moves to be safe will probably be undetectable.
In my case, I knew that the error happened at the start of layer 9 because I had told Cura to insert a color change there. Here’s the g-code at that point, with layer numbers helpfully documented by Cura.
; ...
;TIME_ELAPSED:7099.713734
;LAYER:9
;MESH:
M600 ; Generated by ColorChange plugin
G0 X44.875 Y105.935 Z2.2
;TYPE:WALL-INNER
G1 F2700 X245.125 Y105.935 E3681.91524
; ...
We see that at the very start of the layer, a M600 filament change command is called. This is where my print failed. After that, the printer was supposed to return to position X44.875 Y105.935 Z2.2 and resume printing with the inner wall of layer 9.
To tell the printer to resume from this point, all we needed to do was delete all the lines between the startup g-code (we still want to heat up everything and home axes) and the move command to reposition the head to the start of layer 9. We’ll probably also want to throw in an extra Z-move (G0 Z2.2) right after the start g-code keep from crashing the head into what’s already printed.
It’s really that easy.
Well, almost. Remember the absolute and relative positioning modes? We’re still in absolute mode. Since the start g-code sets the extruder mode to absolute (M82) and the position to 0 (G92 E0), the first extruding move command to start to print the inner wall of layer 9 (G1 F2700 X245.125 Y105.935 E3681.91524) will extrude 3.6 meters of filament. This is probably not what we want.
To correct this, we need to tell Cura to set the extruder position at the start to something closer. If we scroll up a few lines before the start of layer 9, we can see that the final extruding move of layer 8 put the extruder at E3678.77623. That’s where we want to start. So we just need to add an additional line to set the extruder position to that value instead of zero in the start g-code (G92 E0 to G92 E3678.77623).
;Generated by Cura LulzBot Edition GCodeWriter Version: 3.6.20
;FLAVOR:Marlin
;TIME:13405
;Filament used: 6.84631m
;Layer height: 0.2
;POSTPROCESSED
;Generated with Cura_SteamEngine 3.6.20
M82 ;absolute extrusion mode
;This G-Code has been generated specifically for the LulzBot TAZ 5 with standard extruder
M75 ; start GLCD timer
M140 S60.0 ; start bed heating up
G90 ; absolute positioning
M107 ; disable fans
M82 ; set extruder to absolute mode
G28 X0 Y0 ; home X and Y
G28 Z0 ; home Z
G1 Z15.0 F175 ; move extruder up
M117 Heating... ; progress indicator message on LCD
M109 R210 ; wait for extruder to reach printing temp
M190 R60.0 ; wait for bed to reach printing temp
G92 E0 ; set extruder position to 0
G1 F200 E10 ; prime the nozzle with filament
G92 E0 ; re-set extruder position to 0
G1 F175 ; set travel speed
M203 X192 Y208 Z3 ; set limits on travel speed
M117 TAZ 5 Printing... ; progress indicator message on LCD
M221 S100 T0
G92 E3678.77623 ; set extruder position to 0
;LAYER:9
;MESH:
G0 Z2.2 ; Move head up to avoid crash on resume
G0 X44.875 Y105.935 Z2.2 ; Move back to where we left off...
;TYPE:WALL-INNER
G1 F2700 X245.125 Y105.935 E3681.91524 ; ... and start printing again.
; ... 43,000 more lines of g-code
So that’s it. The printer will do its standard startup g-code ritual, move the head up above the model, then resume from where it left off. If you completely borked this process up, the result might be unsalvageable, but… well… courage!