-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgcode_parser.py
297 lines (229 loc) · 9.78 KB
/
gcode_parser.py
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
#!/usr/bin/env python3
"""
File: gcode_parser.py
Description:
This module provides functionality for parsing G-code commands from files or streams.
It contains two main classes:
1. StatefulParams:
- A helper class that maintains a stateful dictionary of parameters.
- Useful for storing and retrieving parameter values in a consistent way.
2. GCodeParser:
- The primary class responsible for parsing G-code commands.
- Supports registering callback functions for specific commands (e.g., "G0", "G1").
- Provides two failure modes: "error" (raises an exception if an unregistered command is encountered)
and "ignore" (skips unregistered commands).
Usage Guide:
1. Create an instance of GCodeParser:
parser = GCodeParser(failure_mode="error")
The failure_mode parameter determines how the parser behaves when encountering an
unregistered command.
2. Register callback functions for commands:
def g0_callback(command, params):
print(f"G0: Command: {command}, Params: {params}")
parser.register_callback("G0", g0_callback)
Callbacks are functions that receive the command and a dictionary of parameters.
3. Parse G-code lines:
with open("test.gcode", "r") as f:
lines = f.readlines()
parser.parse_lines(lines)
Each line in the G-code file is processed to extract the command and its parameters,
and the corresponding callback is invoked.
4. Handling different command formats:
The parser normalizes commands so that, for example, "G01" and "G1" are treated equivalently.
Note:
G-code commands usually start with a letter (e.g., G or M) followed by numerical values.
The parser splits the command from its parameters based on spaces and then further processes
each part to extract keys and values.
"""
class StatefulParams:
"""
A class for managing a collection of parameters in a stateful manner.
Attributes:
params (dict): Dictionary holding parameter keys and their corresponding values.
"""
def __init__(self):
"""
Initialize a new instance of StatefulParams with an empty parameter dictionary.
"""
self.params = {}
def set_param(self, key, value):
"""
Set a parameter in the dictionary.
Args:
key (str): The parameter key.
value (Any): The value associated with the key.
"""
self.params[key] = value
def get_param(self, key):
"""
Retrieve the value of a specified parameter.
Args:
key (str): The parameter key to retrieve.
Returns:
Any: The value associated with the provided key.
Raises:
KeyError: If the key is not found in the parameters.
"""
return self.params[key]
def remove_param(self, key):
"""
Remove a parameter from the dictionary.
Args:
key (str): The parameter key to remove.
Raises:
KeyError: If the key does not exist in the parameters.
"""
del self.params[key]
class GCodeParser:
"""
A parser for processing G-code command lines with support for user-defined callbacks.
Attributes:
callbacks (dict): A mapping from normalized G-code commands to callback functions.
failure_mode (str): Determines behavior when encountering an unregistered command.
Valid values are "error" (raises an exception) or "ignore" (skips).
"""
def __init__(self, failure_mode="error"):
"""
Initialize the GCodeParser.
Args:
failure_mode (str, optional): Mode for handling unregistered commands. Must be either "error" or "ignore".
Defaults to "error".
Raises:
ValueError: If failure_mode is not "error" or "ignore".
"""
# Initialize a dictionary for command callbacks.
self.callbacks = {}
# Validate failure_mode parameter.
if failure_mode != "error" and failure_mode != "ignore":
raise ValueError("failure_mode must be either 'error' or 'ignore'")
self.failure_mode = failure_mode
def register_callback(self, command, callback):
"""
Register a callback function for a specific G-code command.
Args:
command (str): The G-code command for which the callback should be registered.
Must start with either "M" or "G".
callback (callable): The function to execute when the command is encountered.
It should accept two parameters: command (str) and params (dict).
Raises:
ValueError: If the command does not start with "M" or "G".
"""
# Clean and normalize the command.
command = self._clean_command(command)
# Validate the command prefix.
if command[0] != "M" and command[0] != "G":
raise ValueError("Command must be either 'M' or 'G'")
# Register the callback for the cleaned command.
self.callbacks[command] = callback
def parse_line(self, line):
"""
Parse a single line of G-code and execute the corresponding callback if registered.
Args:
line (str): A line of G-code containing a command and its parameters.
Raises:
ValueError: If the command is unregistered and failure_mode is "error".
"""
# Extract the command and parameters from the line.
command = self._extract_command(line)
params = self._extract_params(line)
# Check if the command has a registered callback.
if command in self.callbacks:
# Execute the registered callback with the command and parameters.
self.callbacks[command](command, params)
else:
# Handle unregistered commands based on failure_mode.
if self.failure_mode == "error":
raise ValueError(f"Command {command} not registered")
else:
# In "ignore" mode, simply do nothing.
pass
def parse_lines(self, lines):
"""
Parse multiple lines of G-code.
Args:
lines (iterable): An iterable of G-code lines (e.g., a list of strings).
Each line is processed individually using parse_line.
"""
for line in lines:
self.parse_line(line)
def _clean_command(self, command):
"""
Clean and normalize a G-code command string.
This method strips extra whitespace, converts the command to uppercase,
and normalizes commands such as "G01" to "G1" by collapsing the numerical part.
Args:
command (str): The raw command string.
Returns:
str: The normalized command string.
"""
command = command.strip().upper()
# Normalize commands like "G01" to "G1" (i.e., collapse "0" in the middle if present)
if len(command) == 3 and command[1] == "0":
command = command[0] + command[2]
return command
def _extract_command(self, line):
"""
Extract the G-code command from a line.
Args:
line (str): A line of G-code containing a command and its parameters.
Returns:
str: The normalized command extracted from the line.
"""
# Split the line by spaces and clean the first segment as the command.
command = self._clean_command(line.split(" ")[0])
return command
def _extract_params(self, line):
"""
Extract the parameters from a G-code line.
Args:
line (str): A line of G-code containing a command and its parameters.
Returns:
dict: A dictionary mapping parameter keys to their values.
Details:
- The line is split by spaces.
- The first token is assumed to be the command.
- Remaining tokens are parameters in the form "X123", "Y456", etc.
- Each parameter is split into a key (first character) and a value (the remainder).
"""
# Split the line into tokens; first token is the command.
params = line.split(" ")[1:]
param_dict = {}
# Process each parameter token.
for param in params:
param = param.strip().upper()
if not param:
continue
# Assume the first character is the key and the rest is the value.
key = param[0]
value = param[1:]
param_dict[key] = value
return param_dict
# Example usage
if __name__ == "__main__":
# Create a parser instance with error failure mode.
test_parser = GCodeParser()
# Define callback functions for specific commands.
def g0_callback(command, params):
"""
Callback function for the G0 command.
Args:
command (str): The G-code command (expected to be "G0").
params (dict): A dictionary of parameters for the command.
"""
print(f"G0: Command: {command}, Params: {params}")
def g1_callback(command, params):
"""
Callback function for the G1 command.
Args:
command (str): The G-code command (expected to be "G1").
params (dict): A dictionary of parameters for the command.
"""
print(f"G1: Command: {command}, Params: {params}")
# Register the callbacks with the parser.
test_parser.register_callback("G0", g0_callback)
test_parser.register_callback("G1", g1_callback)
# Open a sample G-code file and read its lines.
with open("test.gcode", "r") as f:
lines = f.readlines()
# Parse each line in the file.
test_parser.parse_lines(lines)