nicoaspra
commited on
Commit
·
c77aacd
1
Parent(s):
25d0313
update checker: add check_required_gcodes_position
Browse files- gcode_analyzer_ui.py +17 -13
- python_gcode_checker.py +65 -6
gcode_analyzer_ui.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
| 1 |
# app.py
|
| 2 |
|
| 3 |
import streamlit as st
|
|
|
|
|
|
|
| 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 |
config_report = generate_detailed_report(gcode)
|
| 10 |
-
|
| 11 |
return errors, warnings, config_report
|
| 12 |
|
| 13 |
def format_issues(issues):
|
|
@@ -19,7 +20,16 @@ def format_issues(issues):
|
|
| 19 |
formatted.append(message) # No "Line 0" for general warnings/errors
|
| 20 |
return formatted
|
| 21 |
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
st.title("G-code Programming Assistant (v0.1)")
|
| 24 |
|
| 25 |
st.markdown("""
|
|
@@ -30,18 +40,14 @@ Welcome to the G-code Assistant! This tool helps you verify and analyze your G-c
|
|
| 30 |
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**.
|
| 31 |
""")
|
| 32 |
|
| 33 |
-
# Input for maximum depth of cut
|
| 34 |
depth_max = st.number_input("Maximum Depth of Cut", min_value=0.0, value=0.1, step=0.1, format="%.1f")
|
| 35 |
|
| 36 |
-
#
|
| 37 |
-
gcode_input =
|
| 38 |
|
| 39 |
-
# Button to analyze G-code
|
| 40 |
if st.button("Analyze G-code"):
|
| 41 |
-
# Run analysis
|
| 42 |
errors, warnings, config_report = analyze_gcode(gcode_input, depth_max)
|
| 43 |
|
| 44 |
-
# Display Errors with count in the subheader
|
| 45 |
st.subheader(f"Errors ({len(errors)})")
|
| 46 |
if errors:
|
| 47 |
for message in format_issues(errors):
|
|
@@ -49,7 +55,6 @@ if st.button("Analyze G-code"):
|
|
| 49 |
else:
|
| 50 |
st.success("No errors found.")
|
| 51 |
|
| 52 |
-
# Display Warnings with count in the subheader
|
| 53 |
st.subheader(f"Warnings ({len(warnings)})")
|
| 54 |
if warnings:
|
| 55 |
for message in format_issues(warnings):
|
|
@@ -57,10 +62,9 @@ if st.button("Analyze G-code"):
|
|
| 57 |
else:
|
| 58 |
st.info("No warnings found.")
|
| 59 |
|
| 60 |
-
# Display Configuration Settings
|
| 61 |
st.subheader("Configuration Settings")
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
st.markdown("---")
|
| 66 |
st.markdown("Developed by **Aspra, N.**")
|
|
|
|
| 1 |
# app.py
|
| 2 |
|
| 3 |
import streamlit as st
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from streamlit_ace import st_ace
|
| 6 |
from python_gcode_checker import run_checks
|
| 7 |
from settings_report_details import generate_detailed_report
|
| 8 |
|
| 9 |
def analyze_gcode(gcode, depth_max=0.1):
|
| 10 |
errors, warnings = run_checks(gcode, depth_max)
|
| 11 |
config_report = generate_detailed_report(gcode)
|
|
|
|
| 12 |
return errors, warnings, config_report
|
| 13 |
|
| 14 |
def format_issues(issues):
|
|
|
|
| 20 |
formatted.append(message) # No "Line 0" for general warnings/errors
|
| 21 |
return formatted
|
| 22 |
|
| 23 |
+
def parse_config_report(config_report):
|
| 24 |
+
config_lines = config_report.strip().split('\n')
|
| 25 |
+
config_data = {"Setting": [], "Value": []}
|
| 26 |
+
for line in config_lines:
|
| 27 |
+
if ':' in line:
|
| 28 |
+
key, value = line.split(':', 1)
|
| 29 |
+
config_data["Setting"].append(key.strip())
|
| 30 |
+
config_data["Value"].append(value.strip())
|
| 31 |
+
return pd.DataFrame(config_data)
|
| 32 |
+
|
| 33 |
st.title("G-code Programming Assistant (v0.1)")
|
| 34 |
|
| 35 |
st.markdown("""
|
|
|
|
| 40 |
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**.
|
| 41 |
""")
|
| 42 |
|
|
|
|
| 43 |
depth_max = st.number_input("Maximum Depth of Cut", min_value=0.0, value=0.1, step=0.1, format="%.1f")
|
| 44 |
|
| 45 |
+
# Use Ace editor for G-code input with line numbering
|
| 46 |
+
gcode_input = st_ace(language='text', theme='cobalt', placeholder="Enter G-code here...", font_size=14, height=300)
|
| 47 |
|
|
|
|
| 48 |
if st.button("Analyze G-code"):
|
|
|
|
| 49 |
errors, warnings, config_report = analyze_gcode(gcode_input, depth_max)
|
| 50 |
|
|
|
|
| 51 |
st.subheader(f"Errors ({len(errors)})")
|
| 52 |
if errors:
|
| 53 |
for message in format_issues(errors):
|
|
|
|
| 55 |
else:
|
| 56 |
st.success("No errors found.")
|
| 57 |
|
|
|
|
| 58 |
st.subheader(f"Warnings ({len(warnings)})")
|
| 59 |
if warnings:
|
| 60 |
for message in format_issues(warnings):
|
|
|
|
| 62 |
else:
|
| 63 |
st.info("No warnings found.")
|
| 64 |
|
|
|
|
| 65 |
st.subheader("Configuration Settings")
|
| 66 |
+
config_df = parse_config_report(config_report)
|
| 67 |
+
st.table(config_df)
|
| 68 |
+
|
| 69 |
st.markdown("---")
|
| 70 |
st.markdown("Developed by **Aspra, N.**")
|
python_gcode_checker.py
CHANGED
|
@@ -54,10 +54,10 @@ def check_required_gcodes(lines_with_numbers):
|
|
| 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
|
|
@@ -72,6 +72,7 @@ def check_required_gcodes(lines_with_numbers):
|
|
| 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))
|
|
@@ -86,6 +87,60 @@ def check_required_gcodes(lines_with_numbers):
|
|
| 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.
|
|
@@ -378,8 +433,9 @@ def run_checks(gcode, depth_max=0.1):
|
|
| 378 |
# Preprocess G-code to remove comments and get cleaned lines with original line numbers
|
| 379 |
lines_with_numbers = preprocess_gcode(gcode)
|
| 380 |
|
| 381 |
-
#
|
| 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)
|
|
@@ -390,6 +446,7 @@ def run_checks(gcode, depth_max=0.1):
|
|
| 390 |
# Combine all issues
|
| 391 |
all_issues = (
|
| 392 |
required_gcode_issues
|
|
|
|
| 393 |
+ spindle_issues
|
| 394 |
+ feed_rate_issues
|
| 395 |
+ depth_issues
|
|
@@ -415,10 +472,12 @@ if __name__ == "__main__":
|
|
| 415 |
# Example usage
|
| 416 |
gcode_sample = """
|
| 417 |
%
|
| 418 |
-
G21 G90 G54
|
| 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)
|
|
|
|
| 54 |
Returns a list of errors with individual entries for each missing group.
|
| 55 |
"""
|
| 56 |
required_groups = {
|
| 57 |
+
"units": {"G20", "G21"}, # Metric or Imperial Units
|
| 58 |
+
"mode": {"G90", "G91"}, # Absolute or Incremental Mode
|
| 59 |
+
"work_coordinates": {"G54", "G55", "G56", "G57", "G58", "G59"}, # Work Offsets
|
| 60 |
+
"plane": {"G17", "G18", "G19"}, # Selected Plane
|
| 61 |
}
|
| 62 |
|
| 63 |
# Create a set to track found codes and their line numbers
|
|
|
|
| 72 |
|
| 73 |
# Check for presence of required codes
|
| 74 |
for category, codes in required_groups.items():
|
| 75 |
+
# Only flag as missing if both options in a group are absent
|
| 76 |
found = any(code in found_codes for code in codes)
|
| 77 |
if not found:
|
| 78 |
missing_codes = "/".join(sorted(codes))
|
|
|
|
| 87 |
|
| 88 |
return missing_group_errors
|
| 89 |
|
| 90 |
+
def check_required_gcodes_position(lines_with_numbers):
|
| 91 |
+
"""
|
| 92 |
+
Ensures required G-codes appear before movement commands.
|
| 93 |
+
Flags changes in critical settings (e.g., units) after movement commands.
|
| 94 |
+
"""
|
| 95 |
+
issues = []
|
| 96 |
+
movement_seen = False
|
| 97 |
+
required_groups = {
|
| 98 |
+
"units": {"G20", "G21"},
|
| 99 |
+
"mode": {"G90", "G91"},
|
| 100 |
+
"work_coordinates": {"G54", "G55", "G56", "G57", "G58", "G59"},
|
| 101 |
+
"plane": {"G17", "G18", "G19"},
|
| 102 |
+
}
|
| 103 |
+
critical_gcodes = {
|
| 104 |
+
"units": {"G20", "G21"},
|
| 105 |
+
"plane": {"G17", "G18", "G19"},
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
# Track codes found before movement commands
|
| 109 |
+
codes_before_movement = set()
|
| 110 |
+
|
| 111 |
+
for original_line_number, line in lines_with_numbers:
|
| 112 |
+
tokens = line.split()
|
| 113 |
+
|
| 114 |
+
# Check if movement commands are encountered
|
| 115 |
+
if not movement_seen and any(cmd in tokens for cmd in {"G00", "G01", "G02", "G03"}):
|
| 116 |
+
movement_seen = True
|
| 117 |
+
|
| 118 |
+
if not movement_seen:
|
| 119 |
+
# Collect required G-codes found before movement
|
| 120 |
+
codes_before_movement.update(tokens)
|
| 121 |
+
else:
|
| 122 |
+
# After movement commands have been seen, check for critical G-codes
|
| 123 |
+
for token in tokens:
|
| 124 |
+
for category, codes in critical_gcodes.items():
|
| 125 |
+
if token in codes:
|
| 126 |
+
issues.append((original_line_number, f"(Warning) {token} appears after movement commands. Ensure this change is intentional -> {line.strip()}"))
|
| 127 |
+
|
| 128 |
+
# Check for missing required G-codes before movement commands
|
| 129 |
+
missing_groups = []
|
| 130 |
+
for category, codes in required_groups.items():
|
| 131 |
+
if not any(code in codes_before_movement for code in codes):
|
| 132 |
+
missing_codes = "/".join(sorted(codes))
|
| 133 |
+
missing_groups.append(f"({category}) {missing_codes}")
|
| 134 |
+
|
| 135 |
+
if missing_groups:
|
| 136 |
+
first_movement_line = next(
|
| 137 |
+
(line_num for line_num, line in lines_with_numbers if any(cmd in line for cmd in {"G00", "G01", "G02", "G03"})),
|
| 138 |
+
1
|
| 139 |
+
)
|
| 140 |
+
issues.append((first_movement_line, f"(Error) Missing required G-codes before first movement: {', '.join(missing_groups)}"))
|
| 141 |
+
|
| 142 |
+
return issues
|
| 143 |
+
|
| 144 |
def check_end_gcode(lines_with_numbers):
|
| 145 |
"""
|
| 146 |
Checks that M30 is the last G-code command.
|
|
|
|
| 433 |
# Preprocess G-code to remove comments and get cleaned lines with original line numbers
|
| 434 |
lines_with_numbers = preprocess_gcode(gcode)
|
| 435 |
|
| 436 |
+
# Collect issues from all checks
|
| 437 |
required_gcode_issues = check_required_gcodes(lines_with_numbers)
|
| 438 |
+
required_gcode_position_issues = check_required_gcodes_position(lines_with_numbers)
|
| 439 |
spindle_issues = check_spindle(lines_with_numbers)
|
| 440 |
feed_rate_issues = check_feed_rate(lines_with_numbers)
|
| 441 |
depth_issues = check_depth_of_cut(lines_with_numbers, depth_max)
|
|
|
|
| 446 |
# Combine all issues
|
| 447 |
all_issues = (
|
| 448 |
required_gcode_issues
|
| 449 |
+
+ required_gcode_position_issues
|
| 450 |
+ spindle_issues
|
| 451 |
+ feed_rate_issues
|
| 452 |
+ depth_issues
|
|
|
|
| 472 |
# Example usage
|
| 473 |
gcode_sample = """
|
| 474 |
%
|
| 475 |
+
G21 G90 G17 G54
|
| 476 |
G00 X0 Y0 Z5.0
|
| 477 |
M03 S1000
|
| 478 |
G01 Z-0.1 F100 ; Plunge using rapid movement (should be G01)
|
| 479 |
+
G54
|
| 480 |
+
G01 Z-0.1
|
| 481 |
G01 X10 Y10
|
| 482 |
G01 X20 Y20
|
| 483 |
G00 Z5.0 ; Retract using rapid movement (allowed since Z > 0)
|