forked from jschuh/klipper-macros
-
Notifications
You must be signed in to change notification settings - Fork 0
/
start_end.cfg
481 lines (445 loc) · 19.9 KB
/
start_end.cfg
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# Copyright (C) 2022 Justin Schuh <[email protected]>
#
# This file may be distributed under the terms of the GNU GPLv3 license.
[gcode_macro print_start]
description: Inserted by slicer at start of print. Rather than using this macro
directly you should configure your slicer as instructed in the readme.
Usage: PRINT_START BED=<temp> EXTRUDER=<temp> [CHAMBER=<temp>]
[MESH_MIN=<x,y>] [MESH_MAX=<x,y>] [LAYERS=<num>]
[NOZZLE_SIZE=<mm>]
gcode:
{action_respond_info(
"This file is using an old The PRINT_START format. This print will run "
"fine, but you should update your slicer config to take advantage of the "
"phased PRINT_START macros. The slicer documentation is here:\n"
"https://github.com/jschuh/klipper-macros\x23slicer-configuration"
)}
_PRINT_START_PHASE_INIT {rawparams}
_PRINT_START_PHASE_PREHEAT
_PRINT_START_PHASE_PROBING
_PRINT_START_PHASE_EXTRUDER
_PRINT_START_PHASE_PURGE
[gcode_macro _print_start_phase_init]
description: Inserted by slicer at start of print. Initializes PRINT_START
phases.
Usage: See PRINT_START.
gcode:
CHECK_KM_CONFIG # Need this in case startup errors were missed.
SET_GCODE_VARIABLE MACRO=_print_end_inner VARIABLE=cancelled VALUE="{False}"
_KM_APPLY_PRINT_OFFSET RESET=1
_PRINT_START_PHASE_CHECK PHASE=none
{% set km = printer["gcode_macro _km_globals"] %}
{% if not params.BED %}
{% set dummy = params.__setitem__('BED', params.BED_TEMP|int) %}
{% endif %}
{% if not params.EXTRUDER %}
{% set dummy = params.__setitem__('EXTRUDER', params.EXTRUDER_TEMP|int) %}
{% endif %}
# Stash all the params for use by the other phases.
PRINT_START_SET PRINT_START_PHASE="init" {% for k in params %}{' %s=\"%s\"' %
(k,params[k]|replace('\\','\\\\')|replace('\'','\\\'')|replace('\"','\\\"'))
}{% endfor %}
# Check and propogate the printable bounds.
_km_check_and_set_print_bounds
M107 # Turn off part cooling fan in case it was on.
CLEAR_PAUSE
# Kick off the longest preheats in the init.
M140 S{params.BED}
{% if params.CHAMBER %}M141 S{params.CHAMBER|int}{% endif %}
{% if printer.bed_mesh %}BED_MESH_CLEAR{% endif %}
# Load a saved mesh if configured.
{% if km.start_try_saved_surface_mesh and printer.bed_mesh %}
LOAD_SURFACE_MESH
{% endif %}
PRINT_START_SET PRINT_START_PHASE="preheat"
[gcode_macro _print_start_phase_preheat]
description: Inserted by slicer at start of print. Handles the bed and chamber
heating phases and ends when both are stabilized at their target temperatures.
Usage: See PRINT_START.
gcode:
_PRINT_START_PHASE_CHECK PHASE=preheat
{% set print = printer["gcode_macro print_start_set"].print %}
{% set BED = print.BED|int %}
{% set EXTRUDER = print.EXTRUDER|int %}
{% set CHAMBER = print.CHAMBER|default(0)|int %}
{% set LAYERS = print.LAYERS|default(0)|int %}
{% set km = printer["gcode_macro _km_globals"] %}
# The bed started at no more than 0.2C below and 1.0C above the target temp.
{% set bed_at_target = (BED + 0.4 - printer.heater_bed.temperature) |
abs <= 0.6 %}
PRINT_START_SET BED_AT_TARGET={1 if bed_at_target else 0}
{% set start_level_bed_at_temp = km.start_level_bed_at_temp and
not printer.bed_mesh.profile_name %}
{% set actions_at_temp = start_level_bed_at_temp or
km.start_quad_gantry_level_at_temp or
km.start_z_tilt_adjust_at_temp or
(km.start_home_z_at_temp and not bed_at_target) %}
{% set bed_overshoot = (BED + (km.start_bed_heat_overshoot if
(BED and not bed_at_target) else 0.0),
printer.configfile.settings.heater_bed.max_temp ) | min %}
INIT_LAYER_GCODE LAYERS="{LAYERS}"
{% if CHAMBER > 0.0 %}
M141 S{CHAMBER}
{% endif %}
# Start bed heating
M140 S{bed_overshoot}
{% if actions_at_temp %}
# If we're going to run a bed level we either have a probing temp specified
# or we heat the extruder part way to avoid oozing while probing.
M104 S{km.start_extruder_probing_temp if km.start_extruder_probing_temp > 0
else (km.start_extruder_preheat_scale * EXTRUDER)|round(0,'ceil')|int}
{% else %}
M104 S{EXTRUDER}
{% endif %}
# home all axes
_KM_PRINT_STATUS ACTION=CHANGE STATUS=homing RESET_STACK=1
G28
G90
{% if BED > 0.0 %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=bed_heating RESET_STACK=1
# Skip this if the bed was already at target when START_PRINT was called.
{% if not bed_at_target %}
PARK
# Overshoot the target a bit.
M190 S{bed_overshoot}
G4 P{km.start_bed_heat_delay / 2}
M190 R{BED} # Settle down after the overshoot.
G4 P{km.start_bed_heat_delay / 2}
{% endif %}
{% endif %}
{% if CHAMBER > 0.0 %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=chamber_heating RESET_STACK=1
_KM_PARK_IF_NEEDED HEATER="chamber" RANGE=ABOVE
M191 S{CHAMBER}
{% endif %}
{% if BED > 0.0 and bed_at_target%}
M190 R{BED} # Extra bed stabilization if we skipped it earlier.
{% endif %}
PRINT_START_SET PRINT_START_PHASE="probing"
[gcode_macro _print_start_phase_probing]
description: Inserted by slicer at start of print. Performs probing (including
mesh bed level) and other operations that should be run when the bed and
chamber are stabilized at their target temps.
Usage: See PRINT_START.
gcode:
_PRINT_START_PHASE_CHECK PHASE=probing
{% set print = printer["gcode_macro print_start_set"].print %}
{% set km = printer["gcode_macro _km_globals"] %}
{% set MESH_MIN = print.MESH_MIN|default(None) %}
{% set MESH_MAX = print.MESH_MAX|default(None) %}
# Randomize the placement of the print on the bed.
{% if km.start_random_placement_max > 0 and print.PRINT_MIN and MESH_MIN %}
{% set PRINT_MIN = print.PRINT_MIN.split(",")|map('float')|list %}
{% set PRINT_MAX = print.PRINT_MAX.split(",")|map('float')|list %}
{% set x_offset = (((km.print_max[0] - km.print_min[0] - PRINT_MAX[0] +
PRINT_MIN[0] - 2 * km.start_random_placement_padding)|int,
km.start_random_placement_max * 2)|min, 0)|max %}
{% set y_offset = (((km.print_max[1] - km.print_min[1] - PRINT_MAX[1] +
PRINT_MIN[1] - 2 * km.start_random_placement_padding)|int,
km.start_random_placement_max * 2)|min, 0)|max %}
{% if x_offset > 0 %}
{% set x_offset = range(x_offset)|random + km.print_min[0] -
PRINT_MIN[0] + km.start_random_placement_padding %}
{% endif %}
{% if y_offset > 0 %}
{% set y_offset = range(y_offset)|random + km.print_min[1] -
PRINT_MIN[1] + km.start_random_placement_padding %}
{% endif %}
# This MESH_MIN/MESH_MAX gets passed to BED_MESH_CALIBRATE below, but the
# rest of the macros rely on SET_GCODE_OFFSET performing the adjustment.
{% set MESH_MIN = MESH_MIN.split(",")|map('float')|list %}
{% set MESH_MAX = MESH_MAX.split(",")|map('float')|list %}
{% set MESH_MIN_NEW = (MESH_MIN[0] + x_offset, MESH_MIN[1] + y_offset) %}
{% set MESH_MAX_NEW = (MESH_MAX[0] + x_offset, MESH_MAX[1] + y_offset) %}
{action_respond_info(
"Relocating print origin from (%.3f,%.3f) "|format(*MESH_MIN) +
"to (%.3f,%.3f)"|format(*MESH_MIN_NEW))}
{% set MESH_MIN = (MESH_MIN_NEW[0], MESH_MIN_NEW[1])|join(',') %}
{% set MESH_MAX = (MESH_MAX_NEW[0], MESH_MAX_NEW[1])|join(',') %}
PRINT_START_SET PRINT_OFFSET={"%d,%d" % (x_offset,y_offset)}
{% endif %}
{% set EXTRUDER = print.EXTRUDER|int %}
{% set km = printer["gcode_macro _km_globals"] %}
{% set start_level_bed_at_temp = km.start_level_bed_at_temp and
not printer.bed_mesh.profile_name %}
{% set start_home_z_at_temp = km.start_home_z_at_temp and
not print.BED_AT_TARGET|int %}
{% set actions_at_temp = start_level_bed_at_temp or
km.start_quad_gantry_level_at_temp or
km.start_z_tilt_adjust_at_temp or
start_home_z_at_temp %}
{% if actions_at_temp %}
{% if km.start_extruder_probing_temp > 0 %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=extruder_heating RESET_STACK=1
_KM_PARK_IF_NEEDED HEATER={printer.toolhead.extruder} RANGE=2
M109 R{km.start_extruder_probing_temp}
{% else %}
M104 S{EXTRUDER} # set the final extruder target temperature
{% endif %}
{% if start_home_z_at_temp %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=homing RESET_STACK=1
G28 Z # Re-home only the Z axis now that the bed has stabilized.
{% endif %}
{% if km.start_z_tilt_adjust_at_temp %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=calibrating_z RESET_STACK=1
Z_TILT_ADJUST
{% endif %}
{% if km.start_quad_gantry_level_at_temp %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=leveling_gantry RESET_STACK=1
QUAD_GANTRY_LEVEL
{% endif %}
{% if start_level_bed_at_temp %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=meshing RESET_STACK=1
_km_mesh_if_needed {% if MESH_MIN %} MESH_MIN={MESH_MIN}{% endif
%}{% if MESH_MAX %} MESH_MAX={MESH_MAX}{% endif %}
{% endif %}
{% if km.start_extruder_probing_temp > 0 %}
M104 S{EXTRUDER} # set the final extruder target temperature
{% endif %}
G4
{% endif %}
PRINT_START_SET PRINT_START_PHASE="extruder"
[gcode_macro _print_start_phase_extruder]
description: Inserted by slicer at start of print. Preheats the extruder.
Usage: See PRINT_START.
gcode:
_PRINT_START_PHASE_CHECK PHASE=extruder
{% set print = printer["gcode_macro print_start_set"].print %}
{% set EXTRUDER = print.EXTRUDER|default(print.EXTRUDER_TEMP)|float %}
{% set km = printer["gcode_macro _km_globals"] %}
# Wait for extruder to reach temperature
{% if EXTRUDER > 0 %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=extruder_heating RESET_STACK=1
_KM_PARK_IF_NEEDED HEATER={printer.toolhead.extruder} RANGE=ABOVE
M109 S{EXTRUDER}
{% endif %}
PRINT_START_SET PRINT_START_PHASE="purge"
[gcode_macro _print_start_phase_purge]
description: Inserted by slicer at start of print. Generates purge lines.
Usage: See PRINT_START.
gcode:
_PRINT_START_PHASE_CHECK PHASE=purge
# Apply the offset for bed placement randomization.
_KM_APPLY_PRINT_OFFSET
# apply Z offset for bed surface (just in case it was reset).
_APPLY_BED_SURFACE_OFFSET
{% set print = printer["gcode_macro print_start_set"].print %}
{% set MESH_MIN = print.MESH_MIN|default(None) %}
{% set MESH_MAX = print.MESH_MAX|default(None) %}
{% set NOZZLE_SIZE = print.NOZZLE_SIZE|default(print.NOZZLE_SIZE)|
default(printer.configfile.settings.extruder.nozzle_diameter)|float %}
{% set km = printer["gcode_macro _km_globals"] %}
{% if km.start_purge_length > 0.0 and printer.extruder.can_extrude %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=purging RESET_STACK=1
DRAW_PURGE_LINE WIDTH="{NOZZLE_SIZE * 1.25}" HEIGHT="{NOZZLE_SIZE * 0.625
}"{% if MESH_MIN %} PRINT_MIN={MESH_MIN}{% endif
%}{% if MESH_MAX %} PRINT_MAX={MESH_MAX}{% endif %}
{% endif %}
PRINT_START_SET PRINT_START_PHASE="printing"
_KM_PRINT_STATUS ACTION=CHANGE STATUS=printing RESET_STACK=1
[gcode_macro _km_park_if_needed]
description: Parks the extruder if the current temperature of the supplied
heater is not within the specified target range.
Usage: _KM_PARK_IF_NEEDED HEATER=<heater> RANGE=[<percentage>|ABOVE|BELOW]
gcode:
# This needs to get called as its own macro to get the current temp evaluated.
{% set HEATER = params.HEATER if params.HEATER in
printer.heaters.available_heaters else
("heater_generic " ~ params.HEATER) %}
{% set RANGE = (params.RANGE|default(1))|string|upper %}
{% if printer[HEATER].target %}
{% if RANGE == "ABOVE" %}
{% if printer[HEATER].temperature < printer[HEATER].target %}
PARK
{% endif %}
{% elif RANGE == "BELOW" %}
{% if printer[HEATER].temperature > printer[HEATER].target %}
PARK
{% endif %}
{% elif (printer[HEATER].temperature - printer[HEATER].target)|abs >
(printer[HEATER].target * RANGE|float * 0.01)|abs %}
PARK
{% endif %}
{% endif %}
[gcode_macro _km_apply_print_offset]
variable_offset: []
gcode:
{% set print = printer["gcode_macro print_start_set"].print %}
{% if params.RESET|default(0)|int and offset and
not printer["gcode_macro _km_save_state"].is_ephemeral%}
{% set PRINT_OFFSET = [offset.pop(0) * -1, offset.pop() * -1] %}
{% elif print.PRINT_OFFSET and not offset and
not printer["gcode_macro _km_save_state"].is_ephemeral %}
{% set PRINT_OFFSET = print.PRINT_OFFSET.split(",")|map('float')|list %}
{% set dummy = offset.extend(PRINT_OFFSET) %}
{% endif %}
{% if PRINT_OFFSET %}
_KM_SET_GCODE_OFFSET_BASE {"X_ADJUST=%.2f Y_ADJUST=%.2f"|
format(*PRINT_OFFSET)}
{% endif %}
[gcode_macro _km_mesh_if_needed]
gcode:
# TODO: Instead of blindly using the loaded mesh we could probe a few key
# points on the saved grid. If those probes show that the saved grid is no
# longer in tolerance we could just run BED_MESH_CALIBRATE_FAST anyway.
{% if not printer.bed_mesh.profile_name %}
BED_MESH_CALIBRATE_FAST {rawparams}
{% endif %}
[gcode_macro print_start_set]
description: Inserted by slicer to set values used by PRINT_START.
Usage: PRINT_START_SET <VARIABLE>=<value>
variable_print: {}
gcode:
{%for k in params %}
{% set dummy = print.__setitem__(k|upper, params[k]) %}
{% endfor %}
[gcode_macro _print_start_phase_check]
gcode:
{% set print = printer["gcode_macro print_start_set"].print %}
{% set PRINT_START_PHASE = print.PRINT_START_PHASE|default("none") %}
{% if PRINT_START_PHASE != params.PHASE %}
# Reset the phases manually just to be sure.
SET_GCODE_VARIABLE MACRO=print_start_set VARIABLE=print VALUE="{{}}"
{% if params.PHASE != 'none' %}
{% set error = "PRINT_START phase error. Expected '%s' but found '%s'" %
(params.PHASE, PRINT_START_PHASE) %}
# Do the cancel manually if we're not confident it will fire.
{% if not 'virtual_sdcard' in printer or
not printer.virtual_sdcard.is_active or 'CANCEL_PRINT' not in
printer.configfile.settings.virtual_sdcard.on_error_gcode|
default("")|upper %}
{action_respond_info(error)}
CANCEL_PRINT
{% else %}
{action_raise_error(error)}
{% endif %}
{% else %}
{ action_respond_info("Expected phase '%s' but found '%s'. Resetting."
% (params.PHASE, PRINT_START_PHASE)) }
{% endif %}
{% endif %}
[gcode_macro print_end]
description: Inserted by slicer at end of print.
Usage: PRINT_END
gcode:
_KM_CHECK_IS_PRINTING
M400
_PRINT_END_INNER
{% set km = printer["gcode_macro _km_globals"] %}
{% if km.start_clear_adjustments_at_end != 0 %}
RESET_HEATER_SCALING
RESET_FAN_SCALING
M220 S100
M221 S100
{% endif %}
_RESET_LAYER_GCODE
_RESET_VELOCITY_LIMITS
TURN_OFF_HEATERS
M107; turn off fan
{% if printer.bed_mesh %}BED_MESH_CLEAR{% endif %}
# Park the toolhead and present the bed
{% if printer.toolhead.homed_axes|lower == "xyz" %}
PARK Y="{km.start_end_park_y}"
{% endif %}
M84 ; disable steppers
CLEAR_PAUSE
SET_GCODE_VARIABLE MACRO=print_start_set VARIABLE=print VALUE="{{}}"
_KM_PRINT_STATUS ACTION=CHANGE STATUS=ready RESET_STACK=1
[gcode_macro _print_end_inner]
variable_cancelled: False
gcode:
SET_GCODE_VARIABLE MACRO=_print_end_inner VARIABLE=cancelled VALUE="{False}"
{% set km = printer["gcode_macro _km_globals"] %}
{% set toolhead = printer.toolhead %}
{% set origin = printer.gcode_move.homing_origin%}
{% set max_x = km.print_max[0] - origin.x %}
{% set max_y = km.print_max[1] - origin.y %}
{% set max_z = toolhead.axis_maximum.z - origin.z %}
{% if not cancelled %}
_KM_PRINT_STATUS ACTION=CHANGE STATUS=completing
{% endif %}
{% if printer.extruder.can_extrude %}
# Wipe if we're not cancelling a paused print.
{% if not printer.pause_resume.is_paused and not cancelled and
toolhead.homed_axes|lower == "xyz" %}
{% set x_safe = (max_x - toolhead.position.x, 2.0)|min %}
{% set y_safe = (max_y - toolhead.position.y, 2.0)|min %}
{% set z_safe = (max_z - toolhead.position.z, 2.0)|min %}
G91
G0 Z{z_safe} E-1.0 F{km.travel_speed_z * 2} ; move nozzle up
G0 X{x_safe} Y{y_safe} E-1.0 F{km.travel_speed_xy} ; remove stringing
# Remove the offset now that we're done.
_KM_APPLY_PRINT_OFFSET RESET=1
{% endif %}
# Small retract to prevent ooze
G92 E0
G1 E-5.0 F3600
M400
{% endif %}
[gcode_macro _km_check_is_printing]
variable_debug_state: False # Disables print state check for debugging.
description: Throws an error if print is not currently in progress.
gcode:
{% if not debug_state and
printer.idle_timeout.state|string != "Printing" and
not (printer.virtual_sdcard|default({})).is_active|default(False) and
not printer.pause_resume.is_paused %}
{ action_raise_error("No active print.") }
{% endif %}
[gcode_macro _km_check_and_set_print_bounds]
description: Validates all print bounds and caches their values.
gcode:
{% set km = printer["gcode_macro _km_globals"] %}
{% set print = printer["gcode_macro print_start_set"].print %}
# Check the mesh bounds.
{% if print.MESH_MIN %}
{% set MESH_MIN = print.MESH_MIN.split(",")|map('float')|list %}
{% set MESH_MAX = print.MESH_MAX.split(",")|map('float')|list %}
{%if MESH_MIN[0] < km.print_min[0] or MESH_MIN[1] < km.print_min[1] %}
{action_raise_error("MESH_MIN %s is outside the printable bounds" %
(MESH_MIN|string, km.print_min|string))}
{%elif MESH_MAX[0] > km.print_max[0] or MESH_MAX[1] > km.print_max[1] %}
{action_raise_error("MESH_MAX %s is outside the printable bounds %s" %
(MESH_MAX|string, km.print_max|string))}
{% endif %}
{% endif %}
# Find all the model bounds (including any bounds passed in).
{% set points = [] %}
{% if print.MODEL_MIN or print.MODEL_MAX %}
{% set MODEL_MIN = print.MODEL_MIN.split(",")|map('float')|list %}
{% set MODEL_MAX = print.MODEL_MAX.split(",")|map('float')|list %}
{% set points = [MODEL_MIN, MODEL_MAX] %}
{% endif %}
{% if (printer.exclude_object|default({})).objects %}
{% set points = printer.exclude_object.objects|selectattr('polygon')|
map(attribute='polygon')|sum(start=points) %}
{% set points_len = points|length %}
{% if points_len >= 2 %}
{% set x_coords = (points|map(attribute=0)|sort|list)[0::points_len-1] %}
{% set y_coords = (points|map(attribute=1)|sort|list)[0::points_len-1] %}
{% set MODEL_MIN = (x_coords[0],y_coords[0])|map('float')|list %}
{% set MODEL_MAX = (x_coords[1],y_coords[1])|map('float')|list %}
PRINT_START_SET MODEL_MIN="{MODEL_MIN|join(',')
}" MODEL_MAX="{MODEL_MAX|join(',')}"
{% endif %}
{% endif %}
{% if MODEL_MIN %}
# Check the model bounds.
{% if MODEL_MIN[0] < km.print_min[0] or MODEL_MIN[1] < km.print_min[1] %}
{action_raise_error("MODEL_MIN %s is outside the printable bounds" %
(MODEL_MIN|string, km.print_min|string))}
{% elif MODEL_MAX[0] > km.print_max[0] or MODEL_MAX[1] > km.print_max[1] %}
{action_raise_error("MODEL_MAX %s is outside the printable bounds %s" %
(MODEL_MAX|string, km.print_max|string))}
{% endif %}
{% endif %}
# Set the PRINT_LIMITS
{% if MESH_MIN and MODEL_MIN %}
PRINT_START_SET PRINT_MIN="{((MODEL_MIN[0],MESH_MIN[0])|min,
(MODEL_MIN[1],MESH_MIN[1])|min)|join(',')
}" PRINT_MAX="{((MODEL_MAX[0],MESH_MAX[0])|max,
(MODEL_MAX[1],MESH_MAX[1])|max)|join(',')}"
{% else %}
PRINT_START_SET PRINT_MIN="{km.print_min|join(',')
}" PRINT_MAX="{km.print_max|join(',')}"
{% endif %}