nicoaspra commited on
Commit
0664784
·
1 Parent(s): 9e98882

initial commit

Browse files
.gitattributes CHANGED
@@ -1,3 +1,12 @@
 
 
 
 
 
 
 
 
 
1
  *.7z filter=lfs diff=lfs merge=lfs -text
2
  *.arrow filter=lfs diff=lfs merge=lfs -text
3
  *.bin filter=lfs diff=lfs merge=lfs -text
@@ -33,3 +42,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
1
+ venv/
2
+ .env
3
+ __pycache__/
4
+ error_log.txt
5
+ .cache
6
+ tcc_ceds_music.csv
7
+ .pyc
8
+
9
+
10
  *.7z filter=lfs diff=lfs merge=lfs -text
11
  *.arrow filter=lfs diff=lfs merge=lfs -text
12
  *.bin filter=lfs diff=lfs merge=lfs -text
 
42
  *.zip filter=lfs diff=lfs merge=lfs -text
43
  *.zst filter=lfs diff=lfs merge=lfs -text
44
  *tfevents* filter=lfs diff=lfs merge=lfs -text
45
+
46
+
47
+
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: green
5
  colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.6.0
8
- app_file: app.py
9
  pinned: false
10
  short_description: verify and analyze your G-codes
11
  ---
 
5
  colorTo: pink
6
  sdk: gradio
7
  sdk_version: 5.6.0
8
+ app_file: gcode_analyzer_ui.py
9
  pinned: false
10
  short_description: verify and analyze your G-codes
11
  ---
__pycache__/python_gcode_checker.cpython-313.pyc ADDED
Binary file (18.2 kB). View file
 
__pycache__/settings_report_details.cpython-313.pyc ADDED
Binary file (4.34 kB). View file
 
gcode_analyzer_ui.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # gcode_analyzer_ui.py
2
+
3
+ import gradio as gr
4
+ from python_gcode_checker import run_checks
5
+ from settings_report_details import generate_detailed_report
6
+
7
+ def analyze_gcode(gcode, depth_max=0.1):
8
+ errors, warnings = run_checks(gcode, depth_max)
9
+ error_count = len(errors)
10
+ warning_count = len(warnings)
11
+ config_report = generate_detailed_report(gcode)
12
+ return f"Errors: {error_count}\n\n{format_issues(errors)}", f"Warnings: {warning_count}\n\n{format_issues(warnings)}", config_report
13
+
14
+ def format_issues(issues):
15
+ formatted = []
16
+ for line_num, message in issues:
17
+ if line_num > 0:
18
+ formatted.append(f"Line {line_num}: {message}")
19
+ else:
20
+ formatted.append(message)
21
+ return "\n".join(formatted)
22
+
23
+ with gr.Blocks() as app:
24
+ # Customized introduction and instructions for users
25
+ gr.Markdown("""
26
+ ### G-code Programming Assistant (v0.1)
27
+
28
+ Welcome to the G-code Assistant! This tool is solely for educational purposes, helping you verify and analyze your G-code for potential errors and warnings before actually running it on your machine.
29
+
30
+ **Note:** This tool is limited to simple carving operations on a CNC milling machine.
31
+
32
+ This is a beta version, and you're free to use it for checking your G-codes. If you encounter any issues or unexpected behavior, please contact the developer at **nico.aspra@bicol-u.edu.ph**.
33
+ """)
34
+
35
+ # Depth input at the top
36
+ depth_input = gr.Number(value=0.1, label="Maximum Depth of Cut", precision=1)
37
+
38
+ # G-code input textbox
39
+ gcode_input = gr.Textbox(lines=20, label="Input G-code", placeholder="Enter G-code here...")
40
+
41
+ # Analyze button positioned immediately after G-code input
42
+ analyze_button = gr.Button("Analyze G-code")
43
+
44
+ # Output sections displayed below the Analyze button
45
+ with gr.Row():
46
+ output_errors = gr.Textbox(label="Errors", interactive=False)
47
+ output_warnings = gr.Textbox(label="Warnings", interactive=False)
48
+ output_config = gr.Textbox(label="Configuration Settings", interactive=False)
49
+
50
+ # Define the button click functionality
51
+ analyze_button.click(
52
+ fn=analyze_gcode,
53
+ inputs=[gcode_input, depth_input],
54
+ outputs=[output_errors, output_warnings, output_config]
55
+ )
56
+
57
+ gr.Markdown("---")
58
+ gr.Markdown("Developed by **Aspra, N.**")
59
+
60
+ app.launch()
python_gcode_checker.py ADDED
@@ -0,0 +1,444 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ from decimal import Decimal, getcontext
3
+ import decimal
4
+
5
+ # Define interpolation and movement commands
6
+ interpolation_commands = {"G01", "G02", "G03"}
7
+ movement_commands = {"G00"}
8
+
9
+ # Define a pattern to recognize common G-code commands
10
+ gcode_pattern = re.compile(
11
+ r"(G\d+|M\d+|X[-+]?\d*\.?\d+|Y[-+]?\d*\.?\d+|"
12
+ r"Z[-+]?\d*\.?\d+|I[-+]?\d*\.?\d+|J[-+]?\d*\.?\d+|"
13
+ r"F[-+]?\d*\.?\d+|S[-+]?\d*\.?\d+)"
14
+ )
15
+
16
+ def standardize_codes(line):
17
+ """
18
+ Standardizes M-codes and G-codes to two digits by adding a leading zero if necessary.
19
+ """
20
+ line = re.sub(r"\b(M|G)(\d)\b", r"\g<1>0\2", line)
21
+ return line
22
+
23
+ def remove_comments(line):
24
+ """
25
+ Removes comments from a G-code line. Supports both ';' and '()' style comments.
26
+ """
27
+ # Remove anything after a ';'
28
+ line = line.split(';')[0]
29
+ # Remove anything inside parentheses '()'
30
+ line = re.sub(r'\(.*?\)', '', line)
31
+ return line.strip()
32
+
33
+ def preprocess_gcode(gcode):
34
+ """
35
+ Removes comments from the G-code and returns a list of tuples (original_line_number, cleaned_line).
36
+ Includes all lines to maintain accurate line numbering.
37
+ """
38
+ cleaned_lines = []
39
+ lines = gcode.splitlines()
40
+
41
+ for idx, line in enumerate(lines):
42
+ original_line_number = idx + 1 # Line numbers start from 1
43
+ line = standardize_codes(line.strip())
44
+ # Remove comments
45
+ line_no_comments = remove_comments(line)
46
+ # Include all lines to maintain accurate line numbering
47
+ cleaned_lines.append((original_line_number, line_no_comments))
48
+
49
+ return cleaned_lines
50
+
51
+ def check_required_gcodes(lines_with_numbers):
52
+ """
53
+ Checks that the G-code contains required G-codes: G20/G21, G90/G91, G54-G59, and G17.
54
+ Returns a list of errors with individual entries for each missing group.
55
+ """
56
+ required_groups = {
57
+ "units": {"G20", "G21"},
58
+ "mode": {"G90", "G91"},
59
+ "work_coordinates": {"G54", "G55", "G56", "G57", "G58", "G59"},
60
+ "plane": {"G17", "G18", "G19"},
61
+ }
62
+
63
+ # Create a set to track found codes and their line numbers
64
+ found_codes = {}
65
+ for original_line_number, line in lines_with_numbers:
66
+ tokens = line.split()
67
+ for token in tokens:
68
+ found_codes.setdefault(token, original_line_number) # Record the line number where the code was found
69
+
70
+ # List to hold individual errors for each missing group
71
+ missing_group_errors = []
72
+
73
+ # Check for presence of required codes
74
+ for category, codes in required_groups.items():
75
+ found = any(code in found_codes for code in codes)
76
+ if not found:
77
+ missing_codes = "/".join(sorted(codes))
78
+ # Assume missing codes should be on the first line where G-codes start
79
+ for original_line_number, line in lines_with_numbers:
80
+ if gcode_pattern.search(line):
81
+ missing_group_errors.append((original_line_number, f"(Error) Missing required G-codes: ({category}) {missing_codes}"))
82
+ break
83
+ else:
84
+ # Default to line 1 if no G-code commands are found
85
+ missing_group_errors.append((1, f"(Error) Missing required G-codes: ({category}) {missing_codes}"))
86
+
87
+ return missing_group_errors
88
+
89
+ def check_end_gcode(lines_with_numbers):
90
+ """
91
+ Checks that M30 is the last G-code command.
92
+ Allows blank lines or '%' symbols after M30.
93
+ """
94
+ found_m30 = False
95
+
96
+ # Collect errors with line numbers
97
+ errors = []
98
+
99
+ for idx, (original_line_number, line) in enumerate(lines_with_numbers):
100
+ if not line.strip() or line.strip() == "%":
101
+ continue # Skip empty lines or lines with only '%'
102
+
103
+ if "M30" in line:
104
+ if found_m30:
105
+ errors.append((original_line_number, "(Error) M30 must be the last G-code command in the G-code."))
106
+ found_m30 = True
107
+ continue # Continue to check if any G-code commands appear after M30
108
+
109
+ # After M30, no other G-code commands should appear
110
+ if found_m30 and gcode_pattern.search(line):
111
+ errors.append((original_line_number, f"(Error) No G-code commands should appear after M30. Found '{line.strip()}'."))
112
+
113
+ if not found_m30:
114
+ if lines_with_numbers:
115
+ last_line_number = lines_with_numbers[-1][0]
116
+ else:
117
+ last_line_number = 1
118
+ errors.append((last_line_number, "(Error) M30 is missing from the G-code."))
119
+
120
+ return errors
121
+
122
+ def check_spindle(lines_with_numbers):
123
+ """
124
+ Checks spindle-related issues in the G-code.
125
+ """
126
+ issues = []
127
+ spindle_on = False
128
+ spindle_started = False
129
+
130
+ for idx, (original_line_number, line) in enumerate(lines_with_numbers):
131
+ # Skip processing lines that are empty or contain only '%'
132
+ if not line.strip() or line.strip() == "%":
133
+ continue
134
+
135
+ tokens = line.split()
136
+
137
+ # Check for valid G-code commands
138
+ if not gcode_pattern.search(line):
139
+ issues.append((original_line_number, f"(Error) Invalid G-code command or syntax error -> {line.strip()}"))
140
+
141
+ # Check for spindle on
142
+ if "M03" in tokens or "M04" in tokens:
143
+ # Check if spindle is already on
144
+ if spindle_on:
145
+ issues.append((original_line_number, "(Warning) Spindle is already on."))
146
+
147
+ # Check if spindle speed is specified with 'S' command
148
+ s_value_present = any(token.startswith("S") for token in tokens)
149
+ if not s_value_present:
150
+ issues.append((original_line_number, "(Error) Spindle speed (S value) is missing when turning on the spindle with M03/M04."))
151
+
152
+ spindle_on = True
153
+ spindle_started = True
154
+
155
+ # Check for spindle off
156
+ if "M05" in tokens:
157
+ spindle_on = False
158
+
159
+ # Check if movement commands are given without spindle on
160
+ if any(cmd in tokens for cmd in interpolation_commands):
161
+ if not spindle_on:
162
+ issues.append((original_line_number, f"(Error) Move command without spindle on -> {line.strip()}"))
163
+
164
+ # Check if spindle was turned off before M30
165
+ if spindle_on:
166
+ last_line_number = lines_with_numbers[-1][0]
167
+ issues.append((last_line_number, "(Error) Spindle was not turned off (M05) before the end of the program."))
168
+
169
+ # Check if spindle was never turned on
170
+ if not spindle_started:
171
+ issues.append((0, "(Error) Spindle was never turned on in the G-code."))
172
+
173
+ return issues
174
+
175
+ def check_feed_rate(lines_with_numbers):
176
+ """
177
+ Checks feed rate related issues in the G-code.
178
+ """
179
+ issues = []
180
+ last_feed_rate = None
181
+ interpolation_command_seen = False
182
+
183
+ for idx, (original_line_number, line) in enumerate(lines_with_numbers):
184
+ # Skip processing lines that are empty or contain only '%'
185
+ if not line.strip() or line.strip() == "%":
186
+ continue
187
+
188
+ tokens = line.split()
189
+ commands = set(tokens)
190
+ feed_rates = [token for token in tokens if token.startswith("F")]
191
+
192
+ # Check if feed rate is beside non-interpolation commands
193
+ if feed_rates and not any(cmd in interpolation_commands for cmd in commands):
194
+ issues.append((original_line_number, f"(Warning) Feed rate specified without interpolation command -> {line.strip()}"))
195
+
196
+ # Check for interpolation commands
197
+ if any(cmd in commands for cmd in interpolation_commands):
198
+ if not interpolation_command_seen:
199
+ interpolation_command_seen = True
200
+ if not feed_rates and last_feed_rate is None:
201
+ issues.append((original_line_number, f"(Error) First interpolation command must have a feed rate -> {line.strip()}"))
202
+ else:
203
+ # Set initial feed rate
204
+ if feed_rates:
205
+ last_feed_rate = feed_rates[-1]
206
+ else:
207
+ # Check if feed rate is specified
208
+ if feed_rates:
209
+ current_feed_rate = feed_rates[-1]
210
+ if current_feed_rate == last_feed_rate:
211
+ issues.append((original_line_number, f"(Warning) Feed rate {current_feed_rate} is already set; no need to specify again."))
212
+ else:
213
+ last_feed_rate = current_feed_rate
214
+
215
+ return issues
216
+
217
+ def check_depth_of_cut(lines_with_numbers, depth_max=0.1):
218
+ """
219
+ Checks that all cutting moves on the Z-axis have a uniform depth and do not exceed the maximum depth.
220
+ """
221
+ getcontext().prec = 6 # Set precision as needed
222
+ depth_max = Decimal(str(depth_max))
223
+ issues = []
224
+
225
+ positioning_mode = "G90" # Default to absolute positioning
226
+ current_z = Decimal('0.0')
227
+ depths = set()
228
+ z_negative_seen = False
229
+
230
+ for idx, (original_line_number, line) in enumerate(lines_with_numbers):
231
+ # Skip processing lines that are empty or contain only '%'
232
+ if not line.strip() or line.strip() == "%":
233
+ continue
234
+
235
+ tokens = line.split()
236
+
237
+ if "G90" in tokens:
238
+ positioning_mode = "G90"
239
+ elif "G91" in tokens:
240
+ positioning_mode = "G91"
241
+
242
+ if any(cmd in tokens for cmd in interpolation_commands.union(movement_commands)):
243
+ z_values = [token for token in tokens if token.startswith("Z")]
244
+ if z_values:
245
+ try:
246
+ z_value = Decimal(z_values[-1][1:])
247
+ except (ValueError, decimal.InvalidOperation):
248
+ issues.append((original_line_number, f"(Error) Invalid Z value -> {line.strip()}"))
249
+ continue
250
+
251
+ if positioning_mode == "G90":
252
+ new_z = z_value
253
+ elif positioning_mode == "G91":
254
+ new_z = current_z + z_value
255
+
256
+ if new_z < Decimal('0.0'):
257
+ z_negative_seen = True
258
+ depth = abs(new_z)
259
+ depth = depth.quantize(Decimal('0.0001')).normalize() # Round and remove trailing zeros
260
+ depths.add(depth)
261
+
262
+ if depth > depth_max:
263
+ issues.append((original_line_number, f"(Error) Depth of cut {depth} exceeds maximum allowed depth of {depth_max.normalize()} -> {line.strip()}"))
264
+
265
+ current_z = new_z
266
+
267
+ if z_negative_seen:
268
+ if len(depths) > 1:
269
+ depth_values = ', '.join(str(d.normalize()) for d in sorted(depths))
270
+ issues.append((0, f"(Warning) Inconsistent depths of cut detected: {depth_values}"))
271
+ else:
272
+ issues.append((0, "(Error) No cutting moves detected on the Z-axis."))
273
+
274
+ return issues
275
+
276
+ def check_interpolation_depth(lines_with_numbers):
277
+ """
278
+ Checks that all interpolation commands moving in X or Y are executed at a negative Z depth (i.e., cutting).
279
+ Does not report errors for interpolation commands used for plunging or retracting (Z-axis movements only).
280
+ """
281
+ getcontext().prec = 6 # Set precision as needed
282
+ issues = []
283
+
284
+ positioning_mode = "G90" # Default to absolute positioning
285
+ current_z = Decimal('0.0')
286
+
287
+ for idx, (original_line_number, line) in enumerate(lines_with_numbers):
288
+ # Skip processing lines that are empty or contain only '%'
289
+ if not line.strip() or line.strip() == "%":
290
+ continue
291
+
292
+ tokens = line.split()
293
+
294
+ # Update positioning mode if G90 or G91 is found
295
+ if "G90" in tokens:
296
+ positioning_mode = "G90"
297
+ elif "G91" in tokens:
298
+ positioning_mode = "G91"
299
+
300
+ # Check for Z-axis movement
301
+ z_values = [token for token in tokens if token.startswith("Z")]
302
+ if z_values:
303
+ try:
304
+ z_value = Decimal(z_values[-1][1:])
305
+ except (ValueError, decimal.InvalidOperation):
306
+ issues.append((original_line_number, f"(Error) Invalid Z value -> {line.strip()}"))
307
+ continue
308
+
309
+ # Calculate the new Z position based on positioning mode
310
+ if positioning_mode == "G90":
311
+ current_z = z_value
312
+ elif positioning_mode == "G91":
313
+ current_z += z_value
314
+
315
+ # Check for interpolation commands
316
+ if any(cmd in tokens for cmd in interpolation_commands):
317
+ # Check if the command includes X or Y movement
318
+ has_xy_movement = any(token.startswith(('X', 'Y')) for token in tokens)
319
+ if has_xy_movement and current_z >= Decimal('0.0'):
320
+ issues.append((original_line_number, f"(Warning) Interpolation command with XY movement executed without cutting depth (Z={current_z}) -> {line.strip()}"))
321
+
322
+ return issues
323
+
324
+ def check_plunge_retract_moves(lines_with_numbers):
325
+ """
326
+ Checks that plunging and retracting moves along the Z-axis use G01 instead of G00.
327
+ Reports an error if G00 is used for Z-axis movements to Z positions less than or equal to zero.
328
+ """
329
+ issues = []
330
+ positioning_mode = "G90" # Default to absolute positioning
331
+ current_z = None # Keep track of the current Z position
332
+
333
+ for idx, (original_line_number, line) in enumerate(lines_with_numbers):
334
+ # Skip processing lines that are empty or contain only '%'
335
+ if not line.strip() or line.strip() == "%":
336
+ continue
337
+
338
+ tokens = line.split()
339
+
340
+ # Update positioning mode if G90 or G91 is found
341
+ if "G90" in tokens:
342
+ positioning_mode = "G90"
343
+ elif "G91" in tokens:
344
+ positioning_mode = "G91"
345
+
346
+ # Check for Z-axis movement
347
+ z_values = [token for token in tokens if token.startswith("Z")]
348
+ if z_values:
349
+ try:
350
+ z_value = Decimal(z_values[-1][1:])
351
+ except (ValueError, decimal.InvalidOperation):
352
+ issues.append((original_line_number, f"(Error) Invalid Z value -> {line.strip()}"))
353
+ continue
354
+
355
+ # Calculate the new Z position based on positioning mode
356
+ if current_z is None:
357
+ current_z = z_value
358
+ else:
359
+ if positioning_mode == "G90":
360
+ current_z = z_value
361
+ elif positioning_mode == "G91":
362
+ current_z += z_value
363
+
364
+ # Check for G00 commands moving to Z ≤ 0
365
+ # Check for G00 commands moving to Z ≤ 0
366
+ if "G00" in tokens and current_z <= Decimal('0.0'):
367
+ issues.append((original_line_number, f"(Error) G00 used for plunging to Z={current_z}. Use G01 to safely approach the workpiece -> {line.strip()}"))
368
+
369
+ return issues
370
+
371
+ def run_checks(gcode, depth_max=0.1):
372
+ """
373
+ Runs all checks and returns a tuple containing lists of errors and warnings.
374
+ """
375
+ errors = []
376
+ warnings = []
377
+
378
+ # Preprocess G-code to remove comments and get cleaned lines with original line numbers
379
+ lines_with_numbers = preprocess_gcode(gcode)
380
+
381
+ # Run all checks
382
+ required_gcode_issues = check_required_gcodes(lines_with_numbers)
383
+ spindle_issues = check_spindle(lines_with_numbers)
384
+ feed_rate_issues = check_feed_rate(lines_with_numbers)
385
+ depth_issues = check_depth_of_cut(lines_with_numbers, depth_max)
386
+ end_gcode_issues = check_end_gcode(lines_with_numbers)
387
+ interpolation_depth_issues = check_interpolation_depth(lines_with_numbers)
388
+ plunge_retract_issues = check_plunge_retract_moves(lines_with_numbers)
389
+
390
+ # Combine all issues
391
+ all_issues = (
392
+ required_gcode_issues
393
+ + spindle_issues
394
+ + feed_rate_issues
395
+ + depth_issues
396
+ + end_gcode_issues
397
+ + interpolation_depth_issues
398
+ + plunge_retract_issues
399
+ )
400
+
401
+ # Separate issues into errors and warnings
402
+ for line_num, message in all_issues:
403
+ if "(Error)" in message:
404
+ errors.append((line_num, message))
405
+ elif "(Warning)" in message:
406
+ warnings.append((line_num, message))
407
+
408
+ # Sort issues by line number
409
+ errors.sort(key=lambda x: x[0])
410
+ warnings.sort(key=lambda x: x[0])
411
+
412
+ return errors, warnings
413
+
414
+ if __name__ == "__main__":
415
+ # Example usage
416
+ gcode_sample = """
417
+ %
418
+ G21 G90 G54 G17
419
+ G00 X0 Y0 Z5.0
420
+ M03 S1000
421
+ G01 Z-0.1 F100 ; Plunge using rapid movement (should be G01)
422
+ G01 X10 Y10
423
+ G01 X20 Y20
424
+ G00 Z5.0 ; Retract using rapid movement (allowed since Z > 0)
425
+ M05
426
+ M30
427
+ %
428
+ """
429
+
430
+ depth_max = 0.1 # Set the maximum allowed depth of cut
431
+ errors, warnings = run_checks(gcode_sample, depth_max)
432
+
433
+ # Prepare the output as a string
434
+ output_lines = []
435
+ if errors or warnings:
436
+ output_lines.append("Issues found in G-code:")
437
+ for line_num, message in errors + warnings:
438
+ if line_num > 0:
439
+ output_lines.append(f"Line {line_num}: {message}")
440
+ else:
441
+ output_lines.append(message)
442
+ print('\n'.join(output_lines))
443
+ else:
444
+ print("Your G-code looks good!")
settings_report_details.py ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # settings_report_details.py
2
+
3
+ import re
4
+ from decimal import Decimal
5
+
6
+ def analyze_settings(gcode):
7
+ # Initialize settings with lists to track multiple entries
8
+ settings = {
9
+ "Unit System": set(),
10
+ "Mode": set(),
11
+ "Selected Plane": set(),
12
+ "Work Coordinates": set(),
13
+ "Spindle Speed": set(),
14
+ "Spindle Rotation": set(),
15
+ "Feed Rate": set(),
16
+ "Maximum Depth": None, # Track only the maximum depth
17
+ }
18
+
19
+ lines = gcode.splitlines()
20
+ max_depth = None
21
+ unit_system = "mm/min" # Default to metric if not specified
22
+
23
+ for line in lines:
24
+ if "G20" in line:
25
+ settings["Unit System"].add("Imperial Units")
26
+ unit_system = "in/min"
27
+ elif "G21" in line:
28
+ settings["Unit System"].add("Metric Units")
29
+ unit_system = "mm/min"
30
+
31
+ if "G90" in line:
32
+ settings["Mode"].add("Absolute Programming")
33
+ elif "G91" in line:
34
+ settings["Mode"].add("Incremental Programming")
35
+
36
+ if "G17" in line:
37
+ settings["Selected Plane"].add("X-Y Plane")
38
+ elif "G18" in line:
39
+ settings["Selected Plane"].add("Z-X Plane")
40
+ elif "G19" in line:
41
+ settings["Selected Plane"].add("Z-Y Plane")
42
+
43
+ work_coords = {"G54", "G55", "G56", "G57", "G58", "G59"}
44
+ for coord in work_coords:
45
+ if coord in line:
46
+ settings["Work Coordinates"].add(coord)
47
+
48
+ if "M03" in line:
49
+ settings["Spindle Rotation"].add("Clockwise")
50
+ elif "M04" in line:
51
+ settings["Spindle Rotation"].add("Counterclockwise")
52
+
53
+ # Track spindle speeds, feed rates, and Z depths
54
+ spindle_speed = re.search(r"S(\d+)", line)
55
+ if spindle_speed:
56
+ settings["Spindle Speed"].add(spindle_speed.group(1) + " RPM")
57
+
58
+ feed_rate = re.search(r"F(\d+\.?\d*)", line)
59
+ if feed_rate:
60
+ settings["Feed Rate"].add(feed_rate.group(1) + f" {unit_system}")
61
+
62
+ z_value = re.search(r"Z(-?\d+\.?\d*)", line)
63
+ if z_value:
64
+ try:
65
+ depth = Decimal(z_value.group(1))
66
+ if max_depth is None or depth < max_depth:
67
+ max_depth = depth
68
+ except Exception as e:
69
+ print(f"Error processing Z depth in line: {line}, error: {e}")
70
+
71
+ # Assign the maximum depth after checking all lines
72
+ if max_depth is not None:
73
+ unit = "mm" if "Metric Units" in settings["Unit System"] else "in"
74
+ settings["Maximum Depth"] = f"{abs(max_depth)} {unit}"
75
+
76
+ # Convert all sets to sorted lists (to handle multiple entries)
77
+ for key, value in settings.items():
78
+ if isinstance(value, set):
79
+ settings[key] = sorted(value)
80
+
81
+ return settings
82
+
83
+ def generate_detailed_report(gcode):
84
+ settings = analyze_settings(gcode)
85
+ report = []
86
+ for key in ["Unit System", "Mode", "Selected Plane", "Work Coordinates", "Spindle Speed", "Spindle Rotation", "Feed Rate", "Maximum Depth"]:
87
+ if isinstance(settings[key], list):
88
+ # Join multiple values with commas
89
+ report.append(f"{key}: {', '.join(settings[key])}")
90
+ else:
91
+ report.append(f"{key}: {settings[key]}")
92
+ return "\n".join(report)
93
+
94
+ # Testing block to run if the script is executed directly
95
+ if __name__ == "__main__":
96
+ # Example G-code sample for testing with multiple values
97
+ gcode_sample = """
98
+ G21 G90 G54 G17
99
+ G0 X0 Y0 Z5.0
100
+ M03 S1000
101
+ G1 Z-0.1 F100
102
+ G1 X10 Y10 Z-0.1
103
+ M04 S1200
104
+ G91
105
+ G1 X20 Y20 F150
106
+ G1 Z-0.2
107
+ G90
108
+ G1 Z5.0
109
+ M05
110
+ M30
111
+ %
112
+ """
113
+
114
+ # Generate and print the detailed report for the test G-code
115
+ report = generate_detailed_report(gcode_sample)
116
+ print("G-code Detailed Report")
117
+ print("=" * 40)
118
+ print(report)