forked from spegelius/filaswitch
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathgcode_file.py
More file actions
326 lines (277 loc) · 12.3 KB
/
gcode_file.py
File metadata and controls
326 lines (277 loc) · 12.3 KB
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
import os
from gcode import GCode
from layer import Layer, FirstLayer, ACT_PASS, ACT_INFILL, ACT_SWITCH
from switch_tower import SwitchTower
gcode = GCode()
SLICER_CURA = "Cura"
SLICER_KISSLICER = "KISSlicer"
SLICER_SIMPLIFY3D = "Simplify3d"
SLICER_SLIC3R = "Slic3r"
SLICER_PRUSA_SLIC3R = "PrusaSlic3r"
class GCodeFile:
slicer_type = None
def __init__(self, logger, hw_config, tower_position, purge_lines):
"""
G-code file base class. Not to be used directly
:param logger: Logger object
:param hw_config: system configuration (PEEK, PTFE, E3Dv6)
:param tower_position: purge tower postion setting
:param purge_lines: amount of post purge lines
"""
self.log = logger
self.settings = {}
self.gcode_file = None
self.material = None
self.extruders = {}
self.switch_tower = None
self.layers = []
self.filtered_layers = []
# material switch z heights
self.layer_height = None
self.last_switch_heights = {}
self.last_switch_height = 0
self.hw_config = hw_config
self.log.info("HW config: %s" % self.hw_config)
self.tower_position = tower_position
self.purge_lines = int(purge_lines)
if self.purge_lines > 15:
self.purge_lines = 15
self.tools = [0]
self.travel_xy_speed = None
self.travel_z_speed = None
self.outer_perimeter_speed = None
self.first_layer_speed = None
# machine limits. Populate these values in slicer specific implementations
self.machine_type = None
self.stroke_x = None
self.stroke_y = None
self.origin_offset_x = None
self.origin_offset_y = None
self.z_offset = 0
# max slots needed
self.max_slots = None
def parse_header(self):
"""
Parse header of gcode file, if any.
Implement in slicer specific code
"""
raise NotImplemented
def parse_print_settings(self):
""" Parse print settings """
is_tool_change = False
for cmd, comment, index in self.layers[0].read_lines():
if comment and comment.strip() == b"START SCRIPT END":
self.layers[0].start_gcode_end = index
break
for layer in self.layers:
for cmd, comment, index in layer.read_lines():
if comment and comment.strip() == b"TOOL CHANGE":
is_tool_change = True
elif cmd and is_tool_change and gcode.is_tool_change(cmd) is not None:
# add unique tools to list
if gcode.last_match not in self.tools:
self.tools.append(gcode.last_match)
self.last_switch_heights[gcode.last_match] = layer.z
is_tool_change = False
if not self.layers[0].start_gcode_end:
raise ValueError("Cannot find 'START SCRIPT END'-comment. Please add it to your Slicer's config")
if self.last_switch_heights:
self.last_switch_height = max(self.last_switch_heights.items())[1]
def open_file(self, gcode_file):
""" Read given g-code file into list """
self.gcode_file = gcode_file
# open file
try:
gf = open(gcode_file, 'rb')
except Exception as e:
self.log.error("Cannot open file %s" % gcode_file)
self.log.debug(str(e))
return 1
# remove extra EOL and empty lines
lines = [l.strip() for l in gf.readlines() if l.strip()]
gf.close()
self.parse_layers(lines)
def read_all_lines(self):
"""
Read lines from all layers
:return: list of lines
"""
for layer in self.layers:
for cmd, comment in layer.lines:
yield gcode.format_to_string(cmd, comment)
def save_new_file(self):
"""
Save g-code lines into new file
:return: new file path
"""
#self.remove_comments()
_dir, f_name = os.path.split(self.gcode_file)
name, ext = os.path.splitext(f_name)
new_file = os.path.join(_dir, name + "_fs" + ext)
try:
with open(new_file, "wb") as nf:
result = b"\r\n".join(self.read_all_lines())
nf.write(result)
return new_file
except Exception as e:
self.log.error("Could not save file, error: %s" % e)
return 1
def get_extruders(self):
""" Implement this in slicer specific implementation"""
raise NotImplemented
def check_layer_change(self, line, current_layer):
"""
Implement this in slicer specific implementation
:param line: g-code line to check
:param current_layer: current layer object
:return: old or updated layer data
"""
raise NotImplemented
def find_tower_position(self):
"""
Find proper position for the switch tower
:return:
"""
self.switch_tower = SwitchTower(self.log, self.hw_config, self.tower_position, self.max_slots, self.z_offset,
self.purge_lines)
x = []
y = []
for layer in self.layers:
for cmd, _ in layer.lines:
if not cmd:
continue
if gcode.is_extrusion_move(cmd) or gcode.is_extrusion_speed_move(cmd):
x.append(gcode.last_match[0])
y.append(gcode.last_match[1])
x_max = max(x)
y_max = max(y)
x_min = min(x)
y_min = min(y)
self.log.debug("Xmax: %s, Ymax: %s, Xmin: %s, Ymin: %s" % (x_max, y_max, x_min, y_min))
self.switch_tower.find_tower_position(x_max, x_min, y_max, y_min, self.machine_type, self.stroke_x,
self.stroke_y, self.origin_offset_x, self.origin_offset_y)
def add_switch_raft(self):
"""
Add tower raft gcode lines after start gcode
:return:
"""
# TODO: check for retraction
index = self.layers[0].start_gcode_end + 1
for cmd, comment in self.switch_tower.get_raft_lines(self.layers[0], self.extruders[0], False,
self.travel_xy_speed, self.travel_z_speed):
index += self.layers[0].insert_line(index, cmd, comment)
self.layers[0].start_gcode_end = index
def add_tool_change_gcode(self):
"""
Go through the g-code and add tool change g-code where needed.
For layers that don't have tool change, add g-code for sparse infill.
:return:
"""
e_pos = 0
z_hop = 0
active_e = self.extruders[0]
# flag to indicate if prime is needed after purge tower g-code
prime_needed = False
z_move_needed = False
is_tool_change = False
last_z = 0
def update_retract_position(pos, new_pos):
"""
Update E position value. In case of negative value we want to have
cumulative status to understand how much retraction is done. In case of positive
value we don't care, so 0 i ok.
:param pos: current position
:param new_pos: new position
:return: updated position
"""
pos += new_pos
if pos > -0.00001:
pos = 0
return pos
for layer in self.filtered_layers:
index = 0
#print("layer", layer.num, e_pos)
while True:
try:
# when z height changes, check that tower height isn't too low versus layer
if layer.num != 1 and layer.z > last_z and layer.z < self.last_switch_height:
for cmd, comment in self.switch_tower.check_infill(layer, e_pos, active_e,
z_hop, self.travel_z_speed,
self.travel_xy_speed):
index += layer.insert_line(index, cmd, comment)
last_z = layer.z
# add infill the the beginning of the layer if not a tool change layer
if layer.action == ACT_INFILL and index == 0 and layer.num != 1 and layer.z < self.last_switch_height:
# update purge tower with sparse infill
for cmd, comment in self.switch_tower.get_infill_lines(layer, e_pos, active_e, z_hop,
self.travel_z_speed,
self.travel_xy_speed):
index += layer.insert_line(index, cmd, comment)
cmd, comment = layer.lines[index]
if comment and comment.strip() == b"TOOL CHANGE":
is_tool_change = True
if not cmd:
# need command
index += 1
continue
if gcode.is_z_move(cmd):
# store current z position and z-hop
current_z, z_speed = gcode.last_match
z_hop = current_z - layer.z
z_move_needed = False
elif is_tool_change and layer.action == ACT_SWITCH and gcode.is_tool_change(cmd) is not None:
# add tool change g-code
new_e = self.extruders[gcode.last_match]
layer.delete_line(index)
for cmd, comment in self.switch_tower.get_tower_lines(layer, e_pos, active_e,
new_e, z_hop, self.travel_z_speed,
self.travel_xy_speed):
index += layer.insert_line(index, cmd, comment)
prime_needed = True
active_e = new_e
# always full retract after purge tower
e_pos = -new_e.retract
is_tool_change = False
z_move_needed = True
continue
elif gcode.is_extruder_move(cmd):
if prime_needed and gcode.last_match[0] < 0:
# remove retracts after adding tower
layer.delete_line(index)
index -= 1
else:
# store extruder position
e_pos = update_retract_position(e_pos, gcode.last_match[0])
elif gcode.is_extrusion_move(cmd) or gcode.is_extrusion_speed_move(cmd):
# store extruder position and add prime if needed
if prime_needed:
# reset prime flag when printing starts after tower
prime_needed = False
if e_pos < 0:
prime_change_len = -(e_pos + active_e.retract + 0.05)
index += layer.insert_line(index,
*active_e.get_prime_gcode(change=prime_change_len))
e_pos = 0
e_pos = update_retract_position(e_pos, gcode.last_match[2])
if z_move_needed:
index += layer.insert_line(index, gcode.gen_z_move(layer.z, self.travel_z_speed))
z_move_needed = False
except IndexError:
break
index += 1
def parse_layers(self, lines):
"""
Go through the g-code and find layer start points.
Store each layer to list.
:return:
"""
raise NotImplemented
def filter_layers(self):
"""
Implement this in slicer specific implementation
:return: none
"""
raise NotImplemented
def process(self, gcode_file):
""" Runs processing """
raise NotImplemented