-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathASMBL_parser.py
More file actions
349 lines (275 loc) · 13.4 KB
/
Copy pathASMBL_parser.py
File metadata and controls
349 lines (275 loc) · 13.4 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
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
import sys
import subprocess
import os
import re
from math import (
inf,
ceil,
floor,
)
from . import utils
from .additive_gcode import AdditiveGcodeLayer
from .cam_gcode import (
CamGcodeLine,
CamGcodeSegment,
CamGcodeLayer,
)
class Parser:
""" Main parsing class. """
def __init__(self, config, progress=None):
self.config = config
self.progress = progress # progress bar for Fusion add-in
self.offset = (config['Printer']['bed_centre_x'],
config['Printer']['bed_centre_y'],
config['PrintSettings']['raft_height'] - config['CamSettings']['layer_dropdown']
)
self.last_additive_tool = None
self.last_subtractive_tool = None
self.main()
def main(self):
progress = self.progress
print('Opening files...')
if progress:
progress.message = 'Opening files'
progress.progressValue += 1
self.open_files(self.config)
# Fusion 360 currently only exports absolute extrusion gcode, this needs to be converted
# This method will not convert gcode if it is already relative
print('Converting additive gcode to relative positioning...')
if progress:
progress.message = 'Converting additive gcode to relative positioning'
progress.progressValue += 1
self.gcode_add = utils.convert_relative(self.gcode_add)
print('Spliting additive gcode layers...')
if progress:
progress.message = 'Spliting additive gcode layers'
progress.progressValue += 1
self.gcode_add_layers = self.split_additive_layers(self.gcode_add)
print('Spliting subtractive gcode layers...')
if progress:
progress.message = 'Spliting subtractive gcode layers'
progress.progressValue += 1
operations = self.split_cam_operations(self.gcode_sub)
print('Ordering subtractive gcode layers...')
if progress:
progress.message = 'Ordering subtractive gcode layers'
progress.progressValue += 1
self.cam_layers = self.order_cam_operations_by_layer(operations)
print('Merging gcode layers...')
if progress:
progress.message = 'Merging gcode layers'
progress.progressValue += 1
self.merged_gcode = self.merge_gcode_layers(self.gcode_add_layers, self.cam_layers)
print('Creating gcode script...')
if progress:
progress.message = 'Creating gcode script'
progress.progressValue += 1
self.create_gcode_script(self.merged_gcode)
def open_files(self, config):
""" Open the additive and subtractive gcode files in `config` """
gcode_add_file = open(config['InputFiles']['additive_gcode'], 'r')
self.gcode_add = gcode_add_file.read()
gcode_sub_file = open(config['InputFiles']['subtractive_gcode'], 'r')
self.gcode_sub = gcode_sub_file.read()
def split_additive_layers(self, gcode_add):
""" Takes Simplify3D gcode and splits in by layer """
tmp_list = re.split('(; layer)', gcode_add)
gcode_add_layers = []
initialise_layer = AdditiveGcodeLayer(
tmp_list.pop(0),
name="initialise",
layer_height=0,
) # slicer settings & initialise
self.set_last_additive_tool(initialise_layer)
# initialise_layer.comment_all_gcode()
gcode_add_layers.append(initialise_layer)
for i in range(ceil(len(tmp_list)/2)):
layer = tmp_list[2*i] + tmp_list[2*i+1]
name = layer.split(',')[0][2:]
if 2*i + 1 == len(tmp_list) - 1:
gcode_add_layers.append(AdditiveGcodeLayer(
layer, 'end', inf))
continue
gcode_add_layers.append(AdditiveGcodeLayer(layer, name))
return gcode_add_layers
def assign_cam_line_type(self, unlabelled_lines):
""" extract type information from string, returns a list of CamGcodeLine's """
lines = []
line_type = None
for line in unlabelled_lines:
if line.startswith('(type: '):
line_type = line[7:].strip(')')
elif line.startswith('('):
continue
else:
lines.append(CamGcodeLine(line, self.offset, line_type))
return lines
def group_cam_lines(self, lines):
"""
Group consequetive lines of the same type into segments
Returns a list of segments
"""
segments = []
segment_lines = [lines[0]]
for i, line in enumerate(lines[1:]):
if line.type != lines[i].type:
segment_index = len(segments)
segments.append(CamGcodeSegment(segment_index, segment_lines, lines[i].type))
segment_lines = [line]
else:
segment_lines.append(line)
segment_index = len(segments)
segments.append(CamGcodeSegment(segment_index, segment_lines, lines[-1].type))
return segments
def add_lead_in_out(self, segments, cutting_group):
"""
Add lead in to start of group of cutting segments, and lead out to end if they exist.
This is required to ensure the cutter does not miss any stock for certain toolpaths
"""
start_index = cutting_group[0].index
pre_index = start_index - 1 if start_index - 1 >= 0 else start_index
if segments[pre_index].type in ('lead in', 'plunge'):
start_index = pre_index
# warn user if a bad linking setting are detected
if segments[pre_index].type in ('helix_ramp', 'profile_ramp', 'ramp'):
print('CAM Linking may be set incorrectly')
end_index = cutting_group[-1].index
post_index = end_index + 1 if end_index + 1 <= len(segments) - 1 else end_index
if segments[post_index].type == 'lead out':
end_index = post_index
return segments[start_index:end_index+1]
def group_cam_segments(self, segments, name, strategy, tool):
"""
Group all cutting segments with a continuous and equal cutting height, including all intermediary segments.
Lead-ins and lead-outs will be added to the start and end respectively if they exist.
Consequetive non planar segments are also grouped together.
Returns a list of layers, each to be merged as a whole unit.
"""
cutting_segments = [segment for segment in segments if segment.type == 'cutting']
cam_layers = []
cutting_group = [cutting_segments[0]]
cutting_height = cutting_segments[0].height
for cutting_segment in cutting_segments[1:]:
# TODO logic needs fixing to deal with non planar and planar segments in same operation
if cutting_segment.height == cutting_height or cutting_segment.planar is False or len(cutting_segment.lines) == 1:
cutting_group.append(cutting_segment)
cutting_height = cutting_segment.height
else:
cutting_height = cutting_segment.height
layer_group = self.add_lead_in_out(segments, cutting_group)
cam_layers.append(CamGcodeLayer(layer_group, name, strategy, tool))
cutting_group = [cutting_segment]
layer_group = self.add_lead_in_out(segments, cutting_group)
cam_layers.append(CamGcodeLayer(layer_group, name, strategy, tool))
return cam_layers
def split_cam_operations(self, gcode_sub):
""" Takes fusion360 CAM gcode and splits the operations by execution height """
tmp_operation_list = gcode_sub.split('\n\n')
operations = []
for i, operation in enumerate(tmp_operation_list):
unlabelled_lines = operation.split('\n')
name = unlabelled_lines.pop(0)
strategy = unlabelled_lines.pop(0)[11:].strip(')')
tool = unlabelled_lines.pop(0)
unlabelled_lines = [line for line in unlabelled_lines if line != '']
lines = self.assign_cam_line_type(unlabelled_lines)
segments = self.group_cam_lines(lines)
operation_layers = self.group_cam_segments(segments, name, strategy, tool)
operations.append(operation_layers)
return operations
def assign_cam_layer_height(self, cam_layer, later_cam_layers, layer_overlap):
""" Calculate the additive layer height that should be printed to before the CAM layer happens """
later_planar_layers = [layer for layer in later_cam_layers if layer.planar]
if (not cam_layer.planar) or (len(later_planar_layers) == 0):
cutting_height = cam_layer.cutting_height
else:
cutting_height = min([layer.cutting_height for layer in later_planar_layers])
later_additive = [layer for layer in self.gcode_add_layers[:-1]
if layer.layer_height > cutting_height]
if (layer_overlap == 0) or (len(later_additive) == 0):
cam_layer_height = cutting_height
elif len(later_additive) >= layer_overlap:
cam_layer_height = later_additive[layer_overlap - 1].layer_height
else:
cam_layer_height = later_additive[-1].layer_height
if cam_layer_height == inf:
raise ValueError("CAM op height can't be 'inf'")
cam_layer.layer_height = cam_layer_height
return
def order_cam_operations_by_layer(self, operations):
""" Takes a list of cam operations and calculates the layer that they should be executed """
unordered_cam_layers = []
for operation in operations:
for cam_layer in operation:
unordered_cam_layers.append(cam_layer)
ordered_cam_layers = sorted(unordered_cam_layers, key=lambda x: x.cutting_height)
layer_overlap = self.config['CamSettings']['layer_overlap']
# TODO assign layer height per layer in each operation independently.
# There is an issue if you have sparse CAM currently
for i, cam_layer in enumerate(ordered_cam_layers):
later_cam_layers = [
layer for layer in ordered_cam_layers if layer.cutting_height > cam_layer.cutting_height]
self.assign_cam_layer_height(cam_layer, later_cam_layers, layer_overlap)
return ordered_cam_layers
def add_retracts(self, cam_layer, clearance_height=5):
"""
Adds redamentary retracts between cam layers.
Required since some of the retracts are removed by `group_cam_segments`
"""
first_line = cam_layer.segments[0].lines[0]
last_line = cam_layer.segments[-1].lines[-1]
offset = (0, 0, clearance_height)
pre_retract = utils.offset_gcode(first_line.gcode, offset)
post_retract = utils.offset_gcode(last_line.gcode, offset)
cam_layer.gcode = '; retract\n' + pre_retract + '\n' + cam_layer.gcode + '; retract\n' + post_retract + '\n'
def merge_gcode_layers(self, gcode_add, cam_layers):
""" Takes the individual CAM instructions and merges them into the additive file from Simplify3D """
for cam_layer in cam_layers:
self.add_retracts(cam_layer)
merged_gcode = gcode_add + cam_layers
merged_gcode.sort(key=lambda x: x.layer_height)
return merged_gcode
def create_gcode_script(self, gcode):
""" Converts list of layers into a single string with appropriate tool changes """
self.merged_gcode_script = '; ASMBL gcode created by https://github.com/AndyEveritt/ASMBL\n'
prev_layer = gcode[0]
for layer in gcode:
self.set_last_additive_tool(prev_layer)
self.tool_change(layer, prev_layer)
prev_layer = layer
self.merged_gcode_script += layer.gcode
def set_last_additive_tool(self, layer):
""" Finds the last used tool in a layer and saves it in memory """
if isinstance(layer, AdditiveGcodeLayer):
process_list = layer.gcode.split('\nT')
if len(process_list) > 1:
self.last_additive_tool = 'T' + process_list[-1].split('\n')[0]
def tool_change(self, layer, prev_layer):
""" Adds any required tool changes between 2 layers """
if type(layer) == AdditiveGcodeLayer:
if layer.name == 'initialise' or prev_layer.name == 'initialise':
return # no need to add a tool change
first_gcode = layer.gcode.split('\n')[1]
if first_gcode[0] != 'T':
self.merged_gcode_script += self.last_additive_tool + '\n'
elif type(layer) == CamGcodeLayer:
self.merged_gcode_script += layer.tool + '\n'
def create_output_file(self, gcode, folder_path="output/", relative_path=True):
""" Saves the file to the output folder """
file_path = folder_path + self.config['OutputSettings']['filename'] + ".gcode"
file_path = os.path.expanduser(file_path)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, "w") as f:
f.write(gcode)
f.close()
try:
utils.open_file(file_path)
except FileNotFoundError:
pass
if __name__ == "__main__":
gcode_add_file = open("gcode/cyclodial_gear/additive.gcode", "r")
gcode_add = gcode_add_file.read()
gcode_sub_file = open("gcode/cyclodial_gear/cam.nc", "r")
gcode_sub = gcode_sub_file.read()
parser = Parser(gcode_add, gcode_sub)
parser.create_output_file(parser.merged_gcode_script)