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>
This commit is contained in:
Jason S. McMullan 2021-04-17 06:54:12 -04:00 committed by KevinOConnor
parent 4ea434796b
commit 913649de2e
8 changed files with 577 additions and 3 deletions

View File

@ -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 %}

View File

@ -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,

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 %}

View File

@ -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

View File

@ -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