aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJason S. McMullan <jason.mcmullan@gmail.com>2021-04-17 06:54:12 -0400
committerKevinOConnor <kevin@koconnor.net>2021-05-26 11:57:57 -0400
commit913649de2e8057e849be2a54e08d34302ee37dad (patch)
treeb7461d6351d46c73a5ad10e3aa49e7b8ad8facae
parent4ea434796bb1f5bcc9d44bfcd0d9d66b8476faa9 (diff)
downloadkutter-913649de2e8057e849be2a54e08d34302ee37dad.tar.gz
kutter-913649de2e8057e849be2a54e08d34302ee37dad.tar.xz
kutter-913649de2e8057e849be2a54e08d34302ee37dad.zip
loop_sdcard: Add loopable SD card file sections
To support continuous belt printing, add nestable repeat loop support via an `[sdcard_loop]` module. Supported G-Code: - SDCARD_LOOP_BEGIN COUNT=n ; Loop for N times, or infinitely if N is 0 - SDCARD_LOOP_END ; End of loop - SDCARD_LOOP_DESIST ; Complete all loops without iterating Marlin M808 compatibility example in `config/sample-macros.cfg`: - M808 Ln ; Loop for N times, or infinitely if N is 0 - M808 ; End of loop - M808 K ; Complete all loops without iterating Added unit tests in test/klippy/sdcard_loop.test See https://reprap.org/wiki/G-code#M808:_Set_or_Goto_Repeat_Marker Signed-off-by: Jason S. McMullan <jason.mcmullan@gmail.com>
-rw-r--r--config/sample-macros.cfg12
-rw-r--r--docs/Config_Reference.md14
-rw-r--r--docs/G-Codes.md7
-rw-r--r--klippy/extras/sdcard_loop.py73
-rw-r--r--klippy/extras/virtual_sdcard.py26
-rw-r--r--test/klippy/sdcard_loop.cfg89
-rw-r--r--test/klippy/sdcard_loop.test9
-rw-r--r--test/klippy/sdcard_loop/big.gcode350
8 files changed, 577 insertions, 3 deletions
diff --git a/config/sample-macros.cfg b/config/sample-macros.cfg
index ea37c584..0eb049ac 100644
--- a/config/sample-macros.cfg
+++ b/config/sample-macros.cfg
@@ -176,3 +176,15 @@ gcode:
"Humidity: %.2f%%" % (
sensor.temperature,
sensor.humidity))}
+
+# SDCard 'looping' (aka Marlin M808 commands) support
+#
+# Support SDCard looping
+[sdcard_loop]
+
+# 'Marlin' style M808 compatibility macro for SDCard looping
+[gcode_macro M808]
+gcode:
+ {% if params.K is not defined and params.L is defined %}SDCARD_LOOP_BEGIN COUNT={params.L|int}{% endif %}
+ {% if params.K is not defined and params.L is not defined %}SDCARD_LOOP_END{% endif %}
+ {% if params.K is defined and params.L is not defined %}SDCARD_LOOP_DESIST{% endif %}
diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md
index 30c2468a..812dd7a9 100644
--- a/docs/Config_Reference.md
+++ b/docs/Config_Reference.md
@@ -1312,6 +1312,20 @@ path:
# be provided.
```
+## [sdcard_loop]
+
+Some printers with stage-clearing features, such as a part ejector or
+a belt printer, can find use in looping sections of the sdcard file.
+(For example, to print the same part over and over, or repeat the
+a section of a part for a chain or other repeated pattern).
+
+See the `config/sample-macros.cfg` file for a Marlin compatible M808
+G-Code macro.
+
+```
+[sdcard_loop]
+```
+
## [force_move]
Support manually moving stepper motors for diagnostic purposes. Note,
diff --git a/docs/G-Codes.md b/docs/G-Codes.md
index 2f012bf5..cb84fa65 100644
--- a/docs/G-Codes.md
+++ b/docs/G-Codes.md
@@ -66,6 +66,13 @@ In addition, the following extended commands are availble when the
- Load a file and start SD print: `SDCARD_PRINT_FILE FILENAME=<filename>`
- Unload file and clear SD state: `SDCARD_RESET_FILE`
+When the [sdcard_loop config section](Config_Reference.md#sdcard_loop) is
+enabled, the following extended commands are available.
+- Begin a looped section in the SD print: `SDCARD_LOOP_BEGIN COUNT=<count>`
+ - A count of 0 indicates that the section should be looped indefinately.
+- End a looped section in the SD print: `SDCARD_LOOP_END`
+- Complete existing loops without further iterations: `SDCARD_LOOP_DESIST`
+
## G-Code arcs
The following standard G-Code commands are available if a
diff --git a/klippy/extras/sdcard_loop.py b/klippy/extras/sdcard_loop.py
new file mode 100644
index 00000000..3cba979f
--- /dev/null
+++ b/klippy/extras/sdcard_loop.py
@@ -0,0 +1,73 @@
+# Sdcard file looping support
+#
+# Copyright (C) 2021 Jason S. McMullan <jason.mcmullan@gmail.com>
+#
+# This file may be distributed under the terms of the GNU GPLv3 license.
+
+import logging
+
+class SDCardLoop:
+ def __init__(self, config):
+ printer = config.get_printer()
+ self.sdcard = printer.load_object(config, "virtual_sdcard")
+ self.gcode = printer.lookup_object('gcode')
+ self.gcode.register_command(
+ "SDCARD_LOOP_BEGIN", self.cmd_SDCARD_LOOP_BEGIN,
+ desc=self.cmd_SDCARD_LOOP_BEGIN_help)
+ self.gcode.register_command(
+ "SDCARD_LOOP_END", self.cmd_SDCARD_LOOP_END,
+ desc=self.cmd_SDCARD_LOOP_END_help)
+ self.gcode.register_command(
+ "SDCARD_LOOP_DESIST", self.cmd_SDCARD_LOOP_DESIST,
+ desc=self.cmd_SDCARD_LOOP_DESIST_help)
+ self.loop_stack = []
+ cmd_SDCARD_LOOP_BEGIN_help = "Begins a looped section in the SD file."
+ def cmd_SDCARD_LOOP_BEGIN(self, gcmd):
+ count = gcmd.get_int("COUNT", minval=0)
+ if not self.loop_begin(count):
+ raise gcmd.error("Only permitted in SD file.")
+ cmd_SDCARD_LOOP_END_help = "Ends a looped section in the SD file."
+ def cmd_SDCARD_LOOP_END(self, gcmd):
+ if not self.loop_end():
+ raise gcmd.error("Only permitted in SD file.")
+ cmd_SDCARD_LOOP_DESIST_help = "Stops iterating the current loop stack."
+ def cmd_SDCARD_LOOP_DESIST(self, gcmd):
+ if not self.loop_desist():
+ raise gcmd.error("Only permitted outside of a SD file.")
+ def loop_begin(self, count):
+ if not self.sdcard.is_cmd_from_sd():
+ # Can only run inside of an SD file
+ return False
+ self.loop_stack.append((count, self.sdcard.get_file_position()))
+ return True
+ def loop_end(self):
+ if not self.sdcard.is_cmd_from_sd():
+ # Can only run inside of an SD file
+ return False
+ # If the stack is empty, no need to skip back
+ if len(self.loop_stack) == 0:
+ return True
+ # Get iteration count and return position
+ count, position = self.loop_stack.pop()
+ if count == 0: # Infinite loop
+ self.sdcard.set_file_position(position)
+ self.loop_stack.append((0, position))
+ elif count == 1: # Last repeat
+ # Nothing to do
+ pass
+ else:
+ # At the next opportunity, seek back to the start of the loop
+ self.sdcard.set_file_position(position)
+ # Decrement the count by 1, and add the position back to the stack
+ self.loop_stack.append((count - 1, position))
+ return True
+ def loop_desist(self):
+ if self.sdcard.is_cmd_from_sd():
+ # Can only run outside of an SD file
+ return False
+ logging.info("Desisting existing SD loops")
+ self.loop_stack = []
+ return True
+
+def load_config(config):
+ return SDCardLoop(config)
diff --git a/klippy/extras/virtual_sdcard.py b/klippy/extras/virtual_sdcard.py
index ccce3afe..66fc5ade 100644
--- a/klippy/extras/virtual_sdcard.py
+++ b/klippy/extras/virtual_sdcard.py
@@ -21,6 +21,7 @@ class VirtualSD:
# Work timer
self.reactor = printer.get_reactor()
self.must_pause_work = self.cmd_from_sd = False
+ self.next_file_position = 0
self.work_timer = None
# Register commands
self.gcode = printer.lookup_object('gcode')
@@ -124,7 +125,7 @@ class VirtualSD:
if filename[0] == '/':
filename = filename[1:]
self._load_file(gcmd, filename, check_subdirs=True)
- self.cmd_M24(gcmd)
+ self.do_resume()
def cmd_M20(self, gcmd):
# List SD card
files = self.get_file_list()
@@ -191,6 +192,12 @@ class VirtualSD:
return
gcmd.respond_raw("SD printing byte %d/%d"
% (self.file_position, self.file_size))
+ def get_file_position(self):
+ return self.next_file_position
+ def set_file_position(self, pos):
+ self.next_file_position = pos
+ def is_cmd_from_sd(self):
+ return self.cmd_from_sd
# Background work timer
def work_handler(self, eventtime):
logging.info("Starting SD card print (position %d)", self.file_position)
@@ -232,8 +239,11 @@ class VirtualSD:
continue
# Dispatch command
self.cmd_from_sd = True
+ line = lines.pop()
+ next_file_position = self.file_position + len(line) + 1
+ self.next_file_position = next_file_position
try:
- self.gcode.run_script(lines[-1])
+ self.gcode.run_script(line)
except self.gcode.error as e:
self.print_stats.note_error(str(e))
break
@@ -241,7 +251,17 @@ class VirtualSD:
logging.exception("virtual_sdcard dispatch")
break
self.cmd_from_sd = False
- self.file_position += len(lines.pop()) + 1
+ self.file_position = self.next_file_position
+ # Do we need to skip around?
+ if self.next_file_position != next_file_position:
+ try:
+ self.current_file.seek(self.file_position)
+ except:
+ logging.exception("virtual_sdcard seek")
+ self.work_timer = None
+ return self.reactor.NEVER
+ lines = []
+ partial_input = ""
logging.info("Exiting SD card print (position %d)", self.file_position)
self.work_timer = None
self.cmd_from_sd = False
diff --git a/test/klippy/sdcard_loop.cfg b/test/klippy/sdcard_loop.cfg
new file mode 100644
index 00000000..27844512
--- /dev/null
+++ b/test/klippy/sdcard_loop.cfg
@@ -0,0 +1,89 @@
+# Test config for sdcard_loop
+[virtual_sdcard]
+path: test/klippy/sdcard_loop
+
+[display_status]
+
+# Override to support unlimited belt size
+# (homing Z simply resets its virtual position to 0.0)
+[homing_override]
+axes: xyz
+set_position_x: 0
+set_position_y: 0
+set_position_z: 0
+gcode:
+ G92 X0 Y0 Z0
+
+
+[stepper_x]
+step_pin: ar54
+dir_pin: ar55
+enable_pin: !ar38
+step_distance: .0125
+endstop_pin: ^ar3
+position_endstop: 0
+position_max: 200
+homing_speed: 50
+
+[stepper_y]
+step_pin: ar60
+dir_pin: !ar61
+enable_pin: !ar56
+step_distance: .0125
+endstop_pin: ^ar14
+position_endstop: 0
+position_max: 200
+homing_speed: 50
+
+[stepper_z]
+step_pin: ar46
+dir_pin: ar48
+enable_pin: !ar62
+step_distance: .0025
+endstop_pin: ^ar18
+position_endstop: 0.5
+position_max: 200000000
+
+[extruder]
+step_pin: ar26
+dir_pin: ar28
+enable_pin: !ar24
+step_distance: .004242
+nozzle_diameter: 0.500
+filament_diameter: 3.500
+heater_pin: ar10
+sensor_type: EPCOS 100K B57560G104F
+sensor_pin: analog13
+control: pid
+pid_Kp: 22.2
+pid_Ki: 1.08
+pid_Kd: 114
+min_temp: 0
+max_temp: 210
+
+[heater_bed]
+heater_pin: ar8
+sensor_type: EPCOS 100K B57560G104F
+sensor_pin: analog14
+control: watermark
+min_temp: 0
+max_temp: 110
+
+[mcu]
+serial: /dev/ttyACM0
+pin_map: arduino
+
+[printer]
+kinematics: cartesian
+max_velocity: 300
+max_accel: 3000
+max_z_velocity: 5
+max_z_accel: 100
+
+[sdcard_loop]
+
+[gcode_macro M808]
+gcode:
+ {% if params.K is not defined and params.L is defined %}SDCARD_LOOP_BEGIN COUNT={params.L|int}{% endif %}
+ {% if params.K is not defined and params.L is not defined %}SDCARD_LOOP_END{% endif %}
+ {% if params.K is defined and params.L is not defined %}SDCARD_LOOP_DESIST{% endif %}
diff --git a/test/klippy/sdcard_loop.test b/test/klippy/sdcard_loop.test
new file mode 100644
index 00000000..dd9db434
--- /dev/null
+++ b/test/klippy/sdcard_loop.test
@@ -0,0 +1,9 @@
+; Virtual SD card unit tests
+
+DICTIONARY atmega2560.dict
+CONFIG sdcard_loop.cfg
+
+G28
+SDCARD_LOOP_DESIST
+; Verify long-name functions
+SDCARD_PRINT_FILE FILENAME=big.gcode
diff --git a/test/klippy/sdcard_loop/big.gcode b/test/klippy/sdcard_loop/big.gcode
new file mode 100644
index 00000000..f5e52041
--- /dev/null
+++ b/test/klippy/sdcard_loop/big.gcode
@@ -0,0 +1,350 @@
+; Large example, to test M808 looping with `partial_data`
+M808 L20
+; Looping
+G92 E0
+G91
+G1 E1 ; This is line 0
+G1 E1 ; This is line 1
+G1 E1 ; This is line 2
+G1 E1 ; This is line 3
+G1 E1 ; This is line 4
+G1 E1 ; This is line 5
+G1 E1 ; This is line 6
+G1 E1 ; This is line 7
+G1 E1 ; This is line 8
+G1 E1 ; This is line 9
+G1 E1 ; This is line 10
+G1 E1 ; This is line 11
+G1 E1 ; This is line 12
+G1 E1 ; This is line 13
+G1 E1 ; This is line 14
+G1 E1 ; This is line 15
+G1 E1 ; This is line 16
+G1 E1 ; This is line 17
+G1 E1 ; This is line 18
+G1 E1 ; This is line 19
+G1 E1 ; This is line 20
+G1 E1 ; This is line 21
+G1 E1 ; This is line 22
+G1 E1 ; This is line 23
+G1 E1 ; This is line 24
+G1 E1 ; This is line 25
+G1 E1 ; This is line 26
+G1 E1 ; This is line 27
+G1 E1 ; This is line 28
+G1 E1 ; This is line 29
+G1 E1 ; This is line 30
+G1 E1 ; This is line 31
+G1 E1 ; This is line 32
+G1 E1 ; This is line 33
+G1 E1 ; This is line 34
+G1 E1 ; This is line 35
+G1 E1 ; This is line 36
+G1 E1 ; This is line 37
+G1 E1 ; This is line 38
+G1 E1 ; This is line 39
+G1 E1 ; This is line 40
+G1 E1 ; This is line 41
+G1 E1 ; This is line 42
+G1 E1 ; This is line 43
+G1 E1 ; This is line 44
+G1 E1 ; This is line 45
+G1 E1 ; This is line 46
+G1 E1 ; This is line 47
+G1 E1 ; This is line 48
+G1 E1 ; This is line 49
+G1 E1 ; This is line 50
+G1 E1 ; This is line 51
+G1 E1 ; This is line 52
+G1 E1 ; This is line 53
+G1 E1 ; This is line 54
+G1 E1 ; This is line 55
+G1 E1 ; This is line 56
+G1 E1 ; This is line 57
+G1 E1 ; This is line 58
+G1 E1 ; This is line 59
+G1 E1 ; This is line 60
+G1 E1 ; This is line 61
+G1 E1 ; This is line 62
+G1 E1 ; This is line 63
+G1 E1 ; This is line 64
+G1 E1 ; This is line 65
+G1 E1 ; This is line 66
+G1 E1 ; This is line 67
+G1 E1 ; This is line 68
+G1 E1 ; This is line 69
+G1 E1 ; This is line 70
+G1 E1 ; This is line 71
+G1 E1 ; This is line 72
+G1 E1 ; This is line 73
+G1 E1 ; This is line 74
+G1 E1 ; This is line 75
+G1 E1 ; This is line 76
+G1 E1 ; This is line 77
+G1 E1 ; This is line 78
+G1 E1 ; This is line 79
+G1 E1 ; This is line 80
+G1 E1 ; This is line 81
+G1 E1 ; This is line 82
+G1 E1 ; This is line 83
+G1 E1 ; This is line 84
+G1 E1 ; This is line 85
+G1 E1 ; This is line 86
+G1 E1 ; This is line 87
+G1 E1 ; This is line 88
+G1 E1 ; This is line 89
+G1 E1 ; This is line 90
+G1 E1 ; This is line 91
+G1 E1 ; This is line 92
+G1 E1 ; This is line 93
+G1 E1 ; This is line 94
+G1 E1 ; This is line 95
+G1 E1 ; This is line 96
+G1 E1 ; This is line 97
+G1 E1 ; This is line 98
+G1 E1 ; This is line 99
+G1 E1 ; This is line 100
+G1 E1 ; This is line 101
+G1 E1 ; This is line 102
+G1 E1 ; This is line 103
+G1 E1 ; This is line 104
+G1 E1 ; This is line 105
+G1 E1 ; This is line 106
+G1 E1 ; This is line 107
+G1 E1 ; This is line 108
+G1 E1 ; This is line 109
+G1 E1 ; This is line 110
+G1 E1 ; This is line 111
+G1 E1 ; This is line 112
+G1 E1 ; This is line 113
+G1 E1 ; This is line 114
+G1 E1 ; This is line 115
+G1 E1 ; This is line 116
+G1 E1 ; This is line 117
+G1 E1 ; This is line 118
+G1 E1 ; This is line 119
+G1 E1 ; This is line 120
+G1 E1 ; This is line 121
+G1 E1 ; This is line 122
+G1 E1 ; This is line 123
+G1 E1 ; This is line 124
+G1 E1 ; This is line 125
+G1 E1 ; This is line 126
+G1 E1 ; This is line 127
+G1 E1 ; This is line 128
+G1 E1 ; This is line 129
+G1 E1 ; This is line 130
+G1 E1 ; This is line 131
+G1 E1 ; This is line 132
+G1 E1 ; This is line 133
+G1 E1 ; This is line 134
+G1 E1 ; This is line 135
+G1 E1 ; This is line 136
+G1 E1 ; This is line 137
+G1 E1 ; This is line 138
+G1 E1 ; This is line 139
+G1 E1 ; This is line 140
+G1 E1 ; This is line 141
+G1 E1 ; This is line 142
+G1 E1 ; This is line 143
+G1 E1 ; This is line 144
+G1 E1 ; This is line 145
+G1 E1 ; This is line 146
+G1 E1 ; This is line 147
+G1 E1 ; This is line 148
+G1 E1 ; This is line 149
+G1 E1 ; This is line 150
+G1 E1 ; This is line 151
+G1 E1 ; This is line 152
+G1 E1 ; This is line 153
+G1 E1 ; This is line 154
+G1 E1 ; This is line 155
+G1 E1 ; This is line 156
+G1 E1 ; This is line 157
+G1 E1 ; This is line 158
+G1 E1 ; This is line 159
+G1 E1 ; This is line 160
+G1 E1 ; This is line 161
+G1 E1 ; This is line 162
+G1 E1 ; This is line 163
+G1 E1 ; This is line 164
+G1 E1 ; This is line 165
+G1 E1 ; This is line 166
+G1 E1 ; This is line 167
+G1 E1 ; This is line 168
+G1 E1 ; This is line 169
+G1 E1 ; This is line 170
+G1 E1 ; This is line 171
+G1 E1 ; This is line 172
+G1 E1 ; This is line 173
+G1 E1 ; This is line 174
+G1 E1 ; This is line 175
+G1 E1 ; This is line 176
+G1 E1 ; This is line 177
+G1 E1 ; This is line 178
+G1 E1 ; This is line 179
+G1 E1 ; This is line 180
+G1 E1 ; This is line 181
+G1 E1 ; This is line 182
+G1 E1 ; This is line 183
+G1 E1 ; This is line 184
+G1 E1 ; This is line 185
+G1 E1 ; This is line 186
+G1 E1 ; This is line 187
+G1 E1 ; This is line 188
+G1 E1 ; This is line 189
+G1 E1 ; This is line 190
+G1 E1 ; This is line 191
+G1 E1 ; This is line 192
+G1 E1 ; This is line 193
+G1 E1 ; This is line 194
+G1 E1 ; This is line 195
+G1 E1 ; This is line 196
+G1 E1 ; This is line 197
+G1 E1 ; This is line 198
+G1 E1 ; This is line 199
+G1 E1 ; This is line 200
+G1 E1 ; This is line 201
+G1 E1 ; This is line 202
+G1 E1 ; This is line 203
+G1 E1 ; This is line 204
+G1 E1 ; This is line 205
+G1 E1 ; This is line 206
+G1 E1 ; This is line 207
+G1 E1 ; This is line 208
+G1 E1 ; This is line 209
+G1 E1 ; This is line 210
+G1 E1 ; This is line 211
+G1 E1 ; This is line 212
+G1 E1 ; This is line 213
+G1 E1 ; This is line 214
+G1 E1 ; This is line 215
+G1 E1 ; This is line 216
+G1 E1 ; This is line 217
+G1 E1 ; This is line 218
+G1 E1 ; This is line 219
+G1 E1 ; This is line 220
+G1 E1 ; This is line 221
+G1 E1 ; This is line 222
+G1 E1 ; This is line 223
+G1 E1 ; This is line 224
+G1 E1 ; This is line 225
+G1 E1 ; This is line 226
+G1 E1 ; This is line 227
+G1 E1 ; This is line 228
+G1 E1 ; This is line 229
+G1 E1 ; This is line 230
+G1 E1 ; This is line 231
+G1 E1 ; This is line 232
+G1 E1 ; This is line 233
+G1 E1 ; This is line 234
+G1 E1 ; This is line 235
+G1 E1 ; This is line 236
+G1 E1 ; This is line 237
+G1 E1 ; This is line 238
+G1 E1 ; This is line 239
+G1 E1 ; This is line 240
+G1 E1 ; This is line 241
+G1 E1 ; This is line 242
+G1 E1 ; This is line 243
+G1 E1 ; This is line 244
+G1 E1 ; This is line 245
+G1 E1 ; This is line 246
+G1 E1 ; This is line 247
+G1 E1 ; This is line 248
+G1 E1 ; This is line 249
+G1 E1 ; This is line 250
+G1 E1 ; This is line 251
+G1 E1 ; This is line 252
+G1 E1 ; This is line 253
+G1 E1 ; This is line 254
+G1 E1 ; This is line 255
+G1 E1 ; This is line 256
+G1 E1 ; This is line 257
+G1 E1 ; This is line 258
+G1 E1 ; This is line 259
+G1 E1 ; This is line 260
+G1 E1 ; This is line 261
+G1 E1 ; This is line 262
+G1 E1 ; This is line 263
+G1 E1 ; This is line 264
+G1 E1 ; This is line 265
+G1 E1 ; This is line 266
+G1 E1 ; This is line 267
+G1 E1 ; This is line 268
+G1 E1 ; This is line 269
+G1 E1 ; This is line 270
+G1 E1 ; This is line 271
+G1 E1 ; This is line 272
+G1 E1 ; This is line 273
+G1 E1 ; This is line 274
+G1 E1 ; This is line 275
+G1 E1 ; This is line 276
+G1 E1 ; This is line 277
+G1 E1 ; This is line 278
+G1 E1 ; This is line 279
+G1 E1 ; This is line 280
+G1 E1 ; This is line 281
+G1 E1 ; This is line 282
+G1 E1 ; This is line 283
+G1 E1 ; This is line 284
+G1 E1 ; This is line 285
+G1 E1 ; This is line 286
+G1 E1 ; This is line 287
+G1 E1 ; This is line 288
+G1 E1 ; This is line 289
+G1 E1 ; This is line 290
+G1 E1 ; This is line 291
+G1 E1 ; This is line 292
+G1 E1 ; This is line 293
+G1 E1 ; This is line 294
+G1 E1 ; This is line 295
+G1 E1 ; This is line 296
+G1 E1 ; This is line 297
+G1 E1 ; This is line 298
+G1 E1 ; This is line 299
+G1 E1 ; This is line 300
+G1 E1 ; This is line 301
+G1 E1 ; This is line 302
+G1 E1 ; This is line 303
+G1 E1 ; This is line 304
+G1 E1 ; This is line 305
+G1 E1 ; This is line 306
+G1 E1 ; This is line 307
+G1 E1 ; This is line 308
+G1 E1 ; This is line 309
+G1 E1 ; This is line 310
+G1 E1 ; This is line 311
+G1 E1 ; This is line 312
+G1 E1 ; This is line 313
+G1 E1 ; This is line 314
+G1 E1 ; This is line 315
+G1 E1 ; This is line 316
+G1 E1 ; This is line 317
+G1 E1 ; This is line 318
+G1 E1 ; This is line 319
+G1 E1 ; This is line 320
+G1 E1 ; This is line 321
+G1 E1 ; This is line 322
+G1 E1 ; This is line 323
+G1 E1 ; This is line 324
+G1 E1 ; This is line 325
+G1 E1 ; This is line 326
+G1 E1 ; This is line 327
+G1 E1 ; This is line 328
+G1 E1 ; This is line 329
+G1 E1 ; This one breaks
+G90
+M808
+G1 E330 ; Should pass
+SDCARD_LOOP_BEGIN COUNT=5
+; Sub-loop
+G1 X100 Y100
+G91
+M808 L3
+; Inner-loop
+G1 Z1
+M808
+G90
+SDCARD_LOOP_END
+; Done