Repository navigation
Expand file tree
/
Copy pathgcode_parser.py
More file actions
212 lines (185 loc) · 8.31 KB
/
Copy pathgcode_parser.py
File metadata and controls
212 lines (185 loc) · 8.31 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
import numpy as np
import math
def parse_gcode_file(filepath):
"""
Parses a G-code file and extracts extrusion paths efficiently.
Returns:
paths: A list of dicts. Each dict represents a continuous extrusion path.
{'points': ndarray shape(N, 3), 'widths': ndarray shape(N,)}
"""
with open(filepath, 'r', encoding='utf-8', errors='replace') as f:
lines = f.readlines()
current_x = 0.0
current_y = 0.0
current_z = 0.0
current_e = 0.0
# Track continuous extrusions
all_paths = []
current_path_points = []
current_path_widths = []
is_extruding = False
relative_e = False # PrusaSlicer often uses M83 (relative E)
relative_pos = False # g-coordinator uses G91 (relative positioning) continuously
current_type = 'Normal'
for line in lines:
line_stripped = line.strip()
# Check for type comment
if line_stripped.startswith(';TYPE:'):
current_type = line_stripped.split(':')[1].strip()
continue
# G-coordinator and standard slicers output this before the actual model starts.
# This resets the type so that G-coordinator paths aren't skipped indefinitely as 'Custom'
if line_stripped.startswith(';LAYER_CHANGE'):
current_type = 'Normal'
continue
line = line.split(';')[0].strip()
if not line:
continue
parts = line.split()
if not parts:
continue
cmd = parts[0]
if cmd == 'M82':
relative_e = False
elif cmd == 'M83':
relative_e = True
elif cmd == 'G90':
relative_pos = False
elif cmd == 'G91':
relative_pos = True
elif cmd == 'G92':
# Reset axes
for p in parts[1:]:
if p.startswith('E'):
current_e = float(p[1:])
elif cmd in ('G0', 'G1', 'G2', 'G3'):
new_x, new_y, new_z, new_e_val = current_x, current_y, current_z, 0.0
has_move = False
has_e = False
i_val, j_val = 0.0, 0.0
has_arc = False
for p in parts[1:]:
if p.startswith('X'):
val = float(p[1:])
new_x = (current_x + val) if relative_pos else val
has_move = True
elif p.startswith('Y'):
val = float(p[1:])
new_y = (current_y + val) if relative_pos else val
has_move = True
elif p.startswith('Z'):
val = float(p[1:])
new_z = (current_z + val) if relative_pos else val
has_move = True
elif p.startswith('E'):
new_e_val = float(p[1:])
has_e = True
elif p.startswith('I'):
i_val = float(p[1:])
has_arc = True
elif p.startswith('J'):
j_val = float(p[1:])
has_arc = True
if not has_move and not has_arc:
if has_e:
if relative_e:
current_e += new_e_val
else:
current_e = new_e_val
continue
dist = 0.0
if cmd in ('G2', 'G3') and has_arc:
has_move = True
cx = current_x + i_val
cy = current_y + j_val
r = math.sqrt(i_val**2 + j_val**2)
# Check for full circle
if abs(new_x - current_x) < 0.0001 and abs(new_y - current_y) < 0.0001:
angle_diff = 2 * math.pi
start_angle = math.atan2(-j_val, -i_val)
if cmd == 'G2':
end_angle = start_angle - 2 * math.pi
else:
end_angle = start_angle + 2 * math.pi
else:
start_angle = math.atan2(-j_val, -i_val)
end_angle = math.atan2(new_y - cy, new_x - cx)
if cmd == 'G2': # CW
if end_angle >= start_angle:
end_angle -= 2 * math.pi
else: # CCW
if end_angle <= start_angle:
end_angle += 2 * math.pi
angle_diff = abs(end_angle - start_angle)
dist = max(r * angle_diff, 0.0001)
else:
dist = np.sqrt((new_x - current_x)**2 + (new_y - current_y)**2 + (new_z - current_z)**2)
e_diff = 0.0
if has_e:
if relative_e:
e_diff = new_e_val
current_e += new_e_val
else:
e_diff = new_e_val - current_e
current_e = new_e_val
# Volume = Area * dist = e_diff * cross_section_of_filament
# Width ~ K * e_diff / dist
if e_diff > 0.0001 and dist > 0.0001:
# 0.4n_0.2mm roughly: width = 0.4, cross section = 0.4 * 0.2 = 0.08
width = (e_diff * 2.405) / (dist * 0.2)
width = min(max(width, 0.2), 0.8)
if new_z >= 0.1 and current_type != 'Custom':
if not is_extruding:
if current_path_points:
all_paths.append({
'points': np.array(current_path_points),
'widths': np.array(current_path_widths)
})
current_path_points = [[current_x, current_y, current_z]]
current_path_widths = [width]
is_extruding = True
if cmd in ('G2', 'G3') and has_arc:
# Arc interpolation using precalculated start/end angles
segments = max(2, int(r * abs(end_angle - start_angle) / 0.5)) # roughly 0.5mm segments
for step in range(1, segments + 1):
t = step / segments
angle = start_angle + (end_angle - start_angle) * t
ix = cx + r * math.cos(angle)
iy = cy + r * math.sin(angle)
current_path_points.append([ix, iy, current_z])
current_path_widths.append(width)
else:
current_path_points.append([new_x, new_y, new_z])
current_path_widths.append(width)
else:
if is_extruding:
# End path if we entered a custom block or low Z area
if len(current_path_points) > 1:
all_paths.append({
'points': np.array(current_path_points),
'widths': np.array(current_path_widths)
})
current_path_points = []
current_path_widths = []
is_extruding = False
else:
# E diff <= 0 means retraction or travel move. Break the line.
if is_extruding:
if len(current_path_points) > 1:
all_paths.append({
'points': np.array(current_path_points),
'widths': np.array(current_path_widths)
})
current_path_points = []
current_path_widths = []
is_extruding = False
# Update current coordinates in all cases
current_x, current_y, current_z = new_x, new_y, new_z
# Add last path if exists
if is_extruding and len(current_path_points) > 1:
all_paths.append({
'points': np.array(current_path_points),
'widths': np.array(current_path_widths)
})
# Kept original coordinates to respect bed origin
return all_paths