DryRun is a G-code simulator that runs in the browser. It reads a program the way a GRBL 1.1 or grblHAL controller reads it and runs it on a model of your router: Shapeoko, X-Carve, a 3018, an OpenBuilds LEAD or a LongMill. Lines GRBL would refuse are marked with GRBL's own error number. Moves past the travel get the soft-limit wording. Collisions and the cut are computed against the sheet on the spoilboard.
This page says what the simulator does for the GRBL family and what it assumes. It was written from the GRBL and grblHAL source code, their documentation, the CAM post-processors' source and users' reports. It was not written on a real controller. Where the behaviour depends on your sender, your firmware build or your settings, the "Not confirmed" section at the end says so, item by item. The user guide has the full reference: section 6.3 for the codes, section 4.2 for the machine settings.
To see it straight away, open the GRBL demo at /app?demo=demo-grbl. It runs a Shapeoko 4 XXL with three programs: a clean two-tool Fusion 360 job, a clean one-tool Carbide Create / VCarve file, and a program from a Fanuc post that GRBL would stop on.
Which machines
Pick one of these in Project settings → Machine, or in the New project wizard. Each one has the vendor's travel, a simple box-and-cylinder frame, a spoilboard and a Z head. The Z head is simplified, so measure yours before you trust collisions with it.
| Machine | Travel X × Y × Z (mm) | Controller | Homes at | Soft limits |
|---|---|---|---|---|
| Genmitsu 3018-PROVer V2 | 290 × 180 × 40 | GRBL 1.1 | front-left (assumed) | off (assumed) |
| Shapeoko 4 XXL | 838 × 838 × 102 | GRBL 1.1 (Carbide Motion) | back-right | on |
| X-Carve 1000 mm | 750 × 750 × 65 (Z disputed: another source gives 114) | GRBL 1.1 (X-Controller) | back-right | on |
| OpenBuilds LEAD 1010 | 730 × 810 × 100 | GRBL 1.1 (BlackBox) | front-left | on |
| Sienci LongMill MK2 30×30 | 762 × 762 × 125 (X and Y nominal: the exact travel is not published) | grblHAL (SuperLongBoard), tool table | front-left | on |
Your own machine is a folder with a machine.json (user guide section 5 and section 4.2). Set "dialect": "grbl-1.1" or "grblhal" in its controller block. FluidNC and Grbl-Mega-5X read the G-code CAM posts write the same way GRBL 1.1 does, so use grbl-1.1 with your machine's axes. One known difference: FluidNC accepts G59.1–G59.3 and M62–M68, and grbl-1.1 rejects them.
What GRBL rejects, and why it matters
GRBL checks every line completely before it runs it. If one word is wrong, the whole line is discarded and GRBL answers error: and a number. GRBL itself then goes on with the next line. Carbide Motion, gSender, UGS and CNCjs stop the job on an error: reply. On the machine that means the job ends part-way through, with the spindle running and the tool possibly in the material.
DryRun rejects those lines, with GRBL's number (it was compared line by line with GRBL 1.1h itself, built as grbl-sim; the known differences are listed below):
- the line gets a red
Not executedentry, for exampleGRBL rejects G81 (error:20, unsupported command). turn off canned cycles in the post or use the GRBL post; - the first rejection of a program adds
— the sender stops the job here; the simulation goes on as if you continued; - the verdict is DO NOT RUN, with the count and the first line:
4 rejected lines — GRBL rejects line 2 (error:20); the sender stops the job there.
The simulation continues after a rejected line, so one pass shows every problem in the program.
A line longer than GRBL's line buffer is rejected too: GRBL keeps 79 characters of a line, not counting spaces, / and comments, and answers error:11 to a longer one (GRBL rejects the line: 80 characters without spaces and comments, GRBL keeps 79). Posts that write many decimals on 5-axis-style lines can reach it.
Known differences from GRBL 1.1. Where DryRun and GRBL 1.1h still answer a line differently:
- Values over 100 000 mm: GRBL accepts the line and drives to it (the machine stops at a limit); DryRun refuses the line with
number out of range: X200000 — GRBL accepts it and drives there; the simulator stops at 100 000 mm, because coordinates that large would stall the simulation. M2/M30: GRBL resets the modes and goes on with any lines the sender still streams; the simulation stops there.%and$lines: a note and a warning (senders strip%and run$commands themselves), where GRBL would answer error:1 and run the command.
The usual causes, from users' reports:
G43 H1→ error:20. A Fanuc or generic post writes a tool length offset from a tool table. GRBL has no tool table. DryRun's hint:GRBL has no tool table — a Fanuc/generic post; use a GRBL post. When a motion code comes first on the same line (G0 G43 Z15 H1,G90 G54 G0 X0 Y0 G43 Z15 H1), GRBL answers error:24 instead: it checks for two axis commands before it refuses G43.G81/G83→ error:20. Canned drilling cycles from a post that is not a GRBL post. The Fusion 360 "Grbl" post writes the drilling out as G0/G1 moves.M6→ error:20 when it reaches GRBL itself. Usually the sender handles M6 (see Tool changes below).- error:33 on arcs: the end point is not on the arc within GRBL's tolerance. Posts that write inches with too few decimals cause it.
G64→ error:20, and anAword → error:20 on a 3-axis GRBL.- error:22: the first feed move of a file has no F. GRBL starts with no feed rate set.
wrongpost.nc in the GRBL demo is such a program: a Fanuc post's output streamed to GRBL. DryRun rejects O1001 (line 2), G64 (line 6), G43 H1 (line 10) and G81 (line 11), each with error:20. The N numbers are accepted, because GRBL accepts them. The % line gets a note, because senders strip it. No move collides, and the verdict is still DO NOT RUN, because the job would stop on line 2.
Supported codes (GRBL 1.1)
| Code | What DryRun does |
|---|---|
| G0, G1 | Rapid and feed moves. G0 is one straight move. A feed move before any F is rejected (error:22). |
| G2, G3 | Arcs in G17, G18 or G19, with IJK (always incremental) or R. Helical arcs and R < 0 run. GRBL's radius check: rejected (error:33) when the end radius differs from the start by more than 0.005 mm and by more than 0.5 mm or 0.1 % of the radius, computed in single-precision floats as GRBL does (G2 X100.05 I50 is 0.0500031 off, error:33). A zero-radius arc (G2 X0 Y0 I0 J0) is a straight feed to its end. |
| G4 | Dwell. P is in seconds on GRBL: G4 P0.5 is half a second. Axis words on the line move after the dwell, in the active motion mode (G4 P1 X1). |
| G10 L2, G10 L20 | Change a work offset for this run, with a warning that the controller stores it. |
| G17, G18, G19 | Arc plane. |
| G20, G21 | Inch and mm input. |
| G28, G30, G28.1, G30.1 | Go to the stored position from controller.g28 / g30 (GRBL's default after $RST=# is machine 0), or store one for the rest of the run. With axis words only the named axes go there. |
| G38.2–G38.5 | Probing: the line is skipped with a warning. |
| G40, G61, G91.1, M1 | Accepted, no effect, as on GRBL. |
| G43.1 Z, G49 | Dynamic tool length offset, added to the touched-off length; cancel. GRBL reads G49 with any decimal (G49.1) as G49. |
| G53 | Machine coordinates for this line, with G0 or G1 (error:30 otherwise). |
| G54–G59 | Work offsets from the project. |
| G80 | Cancels the motion mode. Axis words after it are error:31. |
| G90, G91 | Absolute and incremental. |
| G92, G92.1 | Shift the work coordinates; clear the shift. |
| G93 | Inverse time: F is 1/minutes for its own line, so G93 G1 X10 F2 takes half a minute. Every G1/G2/G3 in G93 needs its F (error:22 without), and after G94 the old feed is gone: the next feed move needs a new F (error:22). |
| G94 | Feed per minute. |
| M0, M2, M30 | Pause (not simulated), program end. |
| M3, M4, M5, M8, M9 | Spindle and coolant: accepted, no effect on the check. |
| M6 | A tool change handled by the sender, with Z touched off again (controller.toolChange). |
| Everything else | Rejected with GRBL's number: G41/G42, G43, G64, G73/G81–G89, G98/G99, M7, M98, O, H, Q and D are error:20; G59.1–G59.3 and Mxx.x are error:23. GRBL has no # variables or expressions: a # or [ where a letter is due is error:1 (#1=5), as a value error:2 (G0 X#1, G0 X[1+2]). |
Syntax: ; and ( ) comments, lower case, packed words (G0X0Y0), / and N numbers are all documented GRBL syntax and give no note. GRBL ignores a / anywhere in the line and an unclosed ( comments out the rest of it; a ) without ( is error:1. A negative M (M-3) is error:23. A % line gives one note (senders strip it; GRBL itself would answer error:1). A $ line ($H, $J=…) is a system command: a warning, no motion. A program needs no M30, because GRBL runs lines as they arrive.
grblHAL
grblHAL keeps GRBL 1.1's error numbers and machine space and adds codes. In DryRun, dialect: "grblhal" runs the GRBL 1.1 table above plus:
- Canned cycles G73, G81, G82, G83, G85, G86 and G89, with G98/G99. They drill as grblHAL's source does. G73 retracts 1 mm between pecks; grblHAL takes that distance from
$28, so a note says so. - M6 runs as a tool change. M61 Q sets the tool without a change.
- G43 H applies the tool's length from the project, but only with
"toolTable": true(the SuperLongBoard has 32 tools). Without a tool table it is error:20, as on GRBL 1.1. - G43.2 H (with a tool table) adds tool H's length to the active offset. G49 is not an axis command on grblHAL:
G49 X5cancels the offset and moves to X5, andG0 G49 X1runs (GRBL 1.1: error:24). - Arcs with P:
G2 X10 I5 P2makes P turns (one extra full circle); P must be a whole number (error:23) and not 0 (error:39,value out of range). - M7 and M62–M68 are accepted. D, H, O and Q are valid words. G43.1 accepts any axis; only its Z is simulated.
- Skipped with a warning: splines G5/G5.1, scaling G50/G51, G59.1–G59.3,
#parameter lines and O-word flow control. - Rejected (error:20): G84 (it needs a spindle that reports "at speed"), G87, G88, G64, G61.1, G90.1, G41/G42 and the lathe codes. grblHAL's own errors 19 (
non-positive value) and 54 (retract position below the hole bottom) are used for bad cycle words.
The LongMill MK2 30×30 in the catalogue is a grblHAL machine with a tool table.
GRBL error codes
The error numbers and texts of GRBL 1.1, from gnea/grbl's error_codes_en_US.csv. grblHAL uses the same numbers. DryRun's messages give a short form of the text, for example (error:20, unsupported command).
| Code | Text |
|---|---|
| 1 | G-code words consist of a letter and a value. Letter was not found. |
| 2 | Missing the expected G-code word value or numeric value format is not valid. |
| 3 | Grbl '$' system command was not recognized or supported. |
| 4 | Negative value received for an expected positive value. |
| 5 | Homing cycle failure. Homing is not enabled via settings. |
| 6 | Minimum step pulse time must be greater than 3usec. |
| 7 | An EEPROM read failed. Auto-restoring affected EEPROM to default values. |
| 8 | Grbl '$' command cannot be used unless Grbl is IDLE. Ensures smooth operation during a job. |
| 9 | G-code commands are locked out during alarm or jog state. |
| 10 | Soft limits cannot be enabled without homing also enabled. |
| 11 | Max characters per line exceeded. Received command line was not executed. |
| 12 | Grbl '$' setting value cause the step rate to exceed the maximum supported. |
| 13 | Safety door detected as opened and door state initiated. |
| 14 | Build info or startup line exceeded EEPROM line length limit. Line not stored. |
| 15 | Jog target exceeds machine travel. Jog command has been ignored. |
| 16 | Jog command has no '=' or contains prohibited g-code. |
| 17 | Laser mode requires PWM output. |
| 20 | Unsupported or invalid g-code command found in block. |
| 21 | More than one g-code command from same modal group found in block. |
| 22 | Feed rate has not yet been set or is undefined. |
| 23 | G-code command in block requires an integer value. |
| 24 | More than one g-code command that requires axis words found in block. |
| 25 | Repeated g-code word found in block. |
| 26 | No axis words found in block for g-code command or current modal state which requires them. |
| 27 | Line number value is invalid. |
| 28 | G-code command is missing a required value word. |
| 29 | G59.x work coordinate systems are not supported. |
| 30 | G53 only allowed with G0 and G1 motion modes. |
| 31 | Axis words found in block when no command or current modal state uses them. |
| 32 | G2 and G3 arcs require at least one in-plane axis word. |
| 33 | Motion command target is invalid. |
| 34 | Arc radius value is invalid. |
| 35 | G2 and G3 arcs require at least one in-plane offset word. |
| 36 | Unused value words found in block. |
| 37 | G43.1 dynamic tool length offset is not assigned to configured tool length axis. |
| 38 | Tool number greater than max supported value. |
Codes 18 and 19 are unused in GRBL 1.1. DryRun gives 11 to a line over 79 characters (above); it never gives 3, 5–10 and 12–17: they come from $ commands, settings, homing, jogging and hardware.
What makes DryRun give each number (the rules are those of GRBL's gcode.c):
| Error | When | Example a CAM file can contain |
|---|---|---|
| 20 | any G or M code GRBL 1.1 does not support; any letter other than F I J K L N P R S T X Y Z | G81, G43 H1, G64, M7, G90.1, O1001, A90 on a 3-axis machine, D1 |
| 21 | two codes of one modal group | G0 G80, M8 M9, M3 M5 |
| 22 | G1/G2/G3 before any F | G1 Z-1 as the first feed |
| 23 | a decimal where an integer is required | G59.1, M3.1 |
| 24 | two codes that use axis words (GRBL checks this before the modal groups, and before it refuses G43, G38.x or Gxx.x after G0–G3, G10, G28, G30, G92 or G43.1/G49) | G0 G1 X1, G28 G1 X0, G0 G43 Z15 H1, G1 G1.5 X1 |
| 25 | the same letter twice | X1 X2 |
| 26 | G10, G92 or G2/G3 without axis words | G2 I5 J0 |
| 27 | N above 10 000 000 | |
| 28 | G4 without P; G10 without P or L | G4 |
| 29 | G10 with P above 6 | G10 L2 P7 X0 |
| 30 | G53 while G2/G3 or G80 is active | G2 … then G53 X0 |
| 31 | axis words while G80 is active | G80 then G92 X0 |
| 32 | an arc with no axis word in its plane | G17 G2 Z5 I1 |
| 33 | an arc whose end is not on it, an R arc to its start point | |
| 34 | R smaller than half the chord | |
| 35 | an IJK arc with no offset in its plane | G17 G2 X1 Y1 K1 |
| 36 | value words no command uses | G0 X1 R2, G1 X1 P1 |
| 37 | G43.1 with an axis other than Z | G43.1 X1 |
| 38 | T above 255 | T300 |
| 4 | a negative F, N, P, S or T | G4 P-1 |
| 2 | a letter without a number, or with a # or [ as its value |
G1 X, G0 X#1 |
| 1 | a character that is not part of a word | G0 X1 ,, #1=5 |
| 11 | more than 79 characters, not counting spaces, / and comments |
a 5-axis post with 8 decimals |
Machine space, homing and soft limits
After homing, GRBL counts machine coordinates from the homed end: every axis runs from minus its travel ($130–$132) to 0. A GRBL machine in DryRun has the same limits, [−travel, 0], so the readout shows negative machine coordinates, as your controller does.
Every program starts at controller.home, the place homing leaves the machine. For a Shapeoko that is about (−5, −5, −5), back-right. For a LEAD 1010 it is front-left, (−725, −805, −5). So the first rapid is drawn and checked from that corner. The real machine starts wherever you jogged it after homing.
A move past the travel is an Axis limit warning that says what the controller does, from controller.softLimits (your $20):
- on:
X -612.000 < min -600.000 — GRBL stops the job here with ALARM:2 (soft limit), and the first one adds; the rest of the job does not run on the machine; - off:
… — soft limits are off ($20=0): the carriage runs into its end.
The spoilboard allowance. A through-cut goes a little into the spoilboard on purpose. The catalogue routers have a spoilboard with a 1.5 mm cut allowance, so the cutter may go up to 1.25 mm into the board (1.5 minus the 0.25 mm collision margin) without a collision. Deeper is a red entry with the real depth, for example cutter T1 cuts 1.26 mm into spoilboard (allowance 1.25 mm) at …. The shank, the holder and the Z head get no allowance. The allowance is per machine, not per job, so keep it small.
Tool changes
GRBL itself only stores T, and it rejects M6. Tool changes happen in the sender, and the usual workflow is: pause, change the tool, touch off Z again with the new one (touch plate, BitSetter, tool-length sensor). DryRun models that workflow by default ("toolChange": "touch-off"). Every tool's length from the project applies from the start and after every M6, so each tip reaches the same work Z. The texts say "touched off": Zero set with T1, touched off, and the G54 Z to enter is the one the controller shows after touching off.
What the senders do with M6, from their documentation:
| Sender | What it does with M6 |
|---|---|
| Carbide Motion | intercepts M6: raises the spindle, prompts for the change, re-measures with the BitSetter, resumes |
| gSender | Config → Tool Changing: "Ignore" is the default, then Pause, Code (macro), wizards, Passthrough |
| CNCjs ≥ 1.9.11 | pauses on M6 with a confirmation pop-up |
| UGS | intercepts M6 and pauses (setting name not verified) |
| grblHAL firmware | $341 modes 1–3 touch off; mode 4 ignores M6 |
So the first M6 of a program gets a note: M6: simulated as a manual change with Z touched off again, as Carbide Motion, CNCjs, UGS and grblHAL do; gSender's default (Tool Changing: Ignore) skips it and keeps the old tool; GRBL itself rejects M6 (error:20).
The Fusion 360 "Grbl" post writes a bare T2 and no M6 unless "Use M6" is on. Nothing then stops on any sender. DryRun warns: T2 without M6: GRBL and the senders do not stop here — the machine keeps cutting with T1 …, and keeps T1 in the spindle. A one-tool file with no M6 is the normal GRBL case; it gets the note no tool change in program — GRBL ignores T; T1 from the setup is in the spindle.
Posts tried
The interpreter's tests run output in the style of these posts. Each sample was put together from the post's source code or from files users published; none was captured from every version of the post. On GRBL 1.1 every one of them runs without a rejected line, except the Fanuc post:
- Fusion 360 "Grbl" (
grbl.cps):G28 G91 Z0retracts, a bareT1,G4 Pin seconds. One note: no tool change in the program. - Fusion 360 "Carbide 3D (Grbl)": the same with
T1 M6. One note: the M6 note. - Carbide Create, current and older (
%, packed words, an empty comment): the one-tool note, and on the older one the%note. - Vectric VCarve "Grbl (mm)", whose header starts with
T1 M6: the M6 note. With"toolChange": "controller"(M6 sent to GRBL raw) that line would be error:20. - Sienci's grblHAL post, Easel and Kiri:Moto. Kiri:Moto writes
G4 P500 ; dwell for 500ms, and GRBL reads that as 500 seconds; DryRun does too. - A Fanuc post with an
Onumber,G43 H1andG81: rejected on those three lines, aswrongpost.ncshows.
Quick view guesses GRBL. When you open programs with nothing else open, and every program looks like GRBL output, they run on the Shapeoko 4 XXL instead of the DDCS demo gantry. A program looks like GRBL output when its header is Carbide Create's, when a comment names a GRBL machine, post or sender (Shapeoko, X-Carve, LongMill, gSender, Carbide Motion, …), when it has Kiri:Moto's GRBL header, or when it looks like Fusion's GRBL post. The strip says that this is a guess: … so it runs on the default GRBL machine (Shapeoko 4 XXL) with no fixture and a 300 × 200 × 18 block — a guess: pick your machine in Create project.
Not confirmed
Assumed but not verified on a real GRBL controller. Each item needs a real machine (or grbl-sim) and your sender. DryRun follows the assumption in the left column. The right column says how to check it on your machine.
| # | Assumption | How to settle |
|---|---|---|
| G1 | The sender stops the job on the first error: |
run a file with G81 as its third line in your sender: does it stop, pause or skip? |
| G2 | The sender strips % lines |
a file starting with % |
| G3 | M6 pauses for a manual change and Z is re-zeroed with the new tool (touch plate, BitSetter, $341) |
your sender's tool-change setting. gSender's default "Ignore" does not pause |
| G4 | Machine 0 is the +X +Y +Z end and the job starts at the homed corner (controller.home) |
$H, then ?: the MPos shown is the start pose; $23 and $130–$132 give the corner and the envelope |
| G5 | The stored G28 position is machine 0 (EEPROM default) | $# shows [G28:…] |
| G6 | Soft limits on or off as controller.softLimits says |
$20 (and $22, which soft limits need) |
| G7 | The firmware is stock 1.1: M7 rejected, no HOMING_FORCE_SET_ORIGIN |
$I build info; the X-Controller build enables M7 |
| G8 | A vendor sender's filtering does not change motion. Carbide Motion ignores G54–G59 and G43 | the sender's documentation; on Carbide Motion use its own zero |
| G9 | grblHAL builds: canned cycles as LinuxCNC (G73 retract from $28), G43 H only with a tool table |
$I, $$ on the controller |
| G10 | The time estimate: G0 at rapidFeed; no per-axis $11x caps or $12x acceleration |
time a job |
| G11 | A dwell in a Kiri:Moto-style program (G4 P500 ; dwell for 500ms) is 500 seconds on GRBL |
— (GRBL's unit is documented; the post's intent is not) |
Not every item has its own warning in the app. G1, G2, G3, G6 and G9 show up in the texts above: the sender sentence on the first rejection, the % note, the M6 note, the soft-limit wording and the G73 note. For the rest, check your controller once.
Compared with the DDCS V4.1 notes: on the DDCS, unknown codes are errors or warnings, and the doubtful behaviour carries "behaviour on DDCS not confirmed" warnings (canned cycles, the G28 target, ;, lower case, / and N). GRBL is precisely specified by its source, so DryRun rejects instead of guessing. On GRBL, what is not confirmed is outside the firmware: the sender, the vendor's build and your settings.
Sources
- GRBL 1.1: gnea/grbl (the 1.1h release):
gcode.c, the error and alarm tables indoc/csv/, and the wiki's commands and configuration pages. - grblHAL: grblHAL/core (build 20261004) and its wiki.
- Posts: Autodesk's Fusion post library (
grbl,carbide3d, revision 44229), Carbide 3D's supported G-codes, Sienci's tool changing page, and users' files on the Carbide 3D, Sienci and Easel forums. - The full research report with every source and evidence level is
docs/research/05-grbl-dialects.mdin the DryRun repository.