Files
gcode_parser/gcodeinterpreter.py

211 lines
9.0 KiB
Python

import math
import re
class GcodeInterpreter:
"""
A basic G-code interpreter that converts G-code commands into a list of coordinates.
Supports G00, G01, G02, and G03 commands.
Attributes:
current_position (list): The current position in 3D space [X, Y, Z].
feed_rate (float): The current feed rate.
coordinates (list): The list of calculated coordinates.
segment_multiplier (float): Multiplier for distance to determine number of segments.
max_segments (int): Maximum number of segments allowed.
"""
def __init__(self, initial_position=[0, 0, 0], initial_feed_rate=0, segment_multiplier=2, max_segments=50):
"""
Initializes the GcodeInterpreter with the initial position, feed rate, segment multiplier, and maximum segments.
Args:
initial_position (list, optional): The initial position [X, Y, Z]. Defaults to [0, 0, 0].
initial_feed_rate (float, optional): The initial feed rate. Defaults to 0.
segment_multiplier (float, optional): Multiplier for distance to determine number of segments. Defaults to 10.0
max_segments (int, optional): Maximum number of segments allowed. Defaults to 100.
"""
self.current_position = initial_position[:] # Use a copy to avoid modifying the original
self.feed_rate = initial_feed_rate
self.coordinates = [initial_position[:]] # Store the initial position
self.segment_multiplier = segment_multiplier # Added segment multiplier
self.max_segments = max_segments # Added max segments
def parse_file(self, filename):
"""
Parses a G-code file and processes each line.
Args:
filename (str): The path to the G-code file.
Returns:
GcodeInterpreter: The instance of the GcodeInterpreter with processed coordinates.
"""
with open(filename, 'r') as file:
for line in file:
self.parse_line(line)
return self
def parse_line(self, line):
"""
Parses a single line of G-code.
Args:
line (str): The G-code line to parse.
"""
line = line.strip()
if not line or line.startswith('%') or line.startswith('('): # Skip empty lines and lines starting with '%'
return
# Use regular expression for more robust parsing
parts = re.findall(r'([A-Za-z])([-+]?\d*\.?\d*)', line) # Find all letter-number pairs
if not parts:
return # Skip lines without any recognized G-code commands
command = parts[0][0].upper() + parts[0][1]
args = {}
for part in parts:
if part[0].upper() != command:
try:
args[part[0].upper()] = float(part[1]) if part[1] else 0.0 # convert the number part to float
except ValueError:
print(f"Warning: Could not convert value '{part[1]}' to float for argument {part[0]}. Skipping.")
args[part[0].upper()] = 0.0
self.process_command(command, args)
def process_command(self, command, args):
"""
Processes a G-code command and calls the appropriate function.
Args:
command (str): The G-code command (e.g., 'G00', 'G01', 'G02', 'G03').
args (dict): A dictionary of arguments for the command (e.g., {'X': 10, 'Y': 20}).
"""
if not command: # Add this check
return
if command in ('G00', 'G01'):
self.process_g00_g01(command, args)
elif command == 'G02':
self.process_g02_g03(command, args, clockwise=True)
elif command == 'G03':
self.process_g02_g03(command, args, clockwise=False)
elif command == 'G04':
self.process_g04(args)
elif command == 'F':
self.feed_rate = args.get('F', self.feed_rate) # Keep the current feed rate if not provided
# Add other G-code commands as needed (e.g., G02, G03, G28, etc.)
else:
print(f"Unsupported G-code command: {command}")
def process_g00_g01(self, command, args):
"""
Processes G00 (Rapid Linear Move) and G01 (Controlled Linear Move) commands.
Args:
command (str): The G-code command ('G00' or 'G01').
args (dict): A dictionary of arguments for the command (e.g., {'X': 10, 'Y': 20, 'Z': 5}).
"""
new_position = [args.get('X', self.current_position[0]),
args.get('Y', self.current_position[1]),
args.get('Z', self.current_position[2])]
# Calculate the distance between the current position and the new position
distance = math.sqrt(
(new_position[0] - self.current_position[0]) ** 2 +
(new_position[1] - self.current_position[1]) ** 2 +
(new_position[2] - self.current_position[2]) ** 2
)
# Number of segments. Increase for smoother lines, but use a reasonable maximum.
segments = int(distance * self.segment_multiplier) + 1
segments = min(segments, self.max_segments) # Limit the maximum number of segments
for i in range(segments + 1):
x = self.current_position[0] + (new_position[0] - self.current_position[0]) * i / segments
y = self.current_position[1] + (new_position[1] - self.current_position[1]) * i / segments
z = self.current_position[2] + (new_position[2] - self.current_position[2]) * i / segments
self.current_position = [x, y, z]
self.coordinates.append([x, y, z])
def process_g02_g03(self, command, args, clockwise=True):
"""
Processes G02 (Clockwise Circular Interpolation) and G03 (Counter-Clockwise Circular Interpolation) commands.
Args:
command (str): The G-code command ('G02' or 'G03').
args (dict): A dictionary of arguments for the command.
clockwise (bool): True for G02 (clockwise), False for G03 (counter-clockwise).
"""
new_position = [args.get('X', self.current_position[0]),
args.get('Y', self.current_position[1]),
args.get('Z', self.current_position[2])]
center = [self.current_position[0] + args.get('I', 0),
self.current_position[1] + args.get('J', 0),
self.current_position[2] + args.get('K', 0)]
radius = math.sqrt((self.current_position[0] - center[0]) ** 2 +
(self.current_position[1] - center[1]) ** 2 +
(self.current_position[2] - center[2]) ** 2)
# Handle the case where the radius is zero
if radius == 0:
print(f"Warning: Radius is zero for {command}. No interpolation performed.")
return
# Calculate the angle between the current point and the target point
start_angle = math.atan2(self.current_position[1] - center[1], self.current_position[0] - center[0])
end_angle = math.atan2(new_position[1] - center[1], new_position[0] - center[0])
# Ensure angles are between 0 and 2*pi
if start_angle < 0:
start_angle += 2 * math.pi
if end_angle < 0:
end_angle += 2 * math.pi
# Calculate the total angle
total_angle = end_angle - start_angle
if clockwise:
if total_angle > 0:
total_angle -= 2 * math.pi
else:
if total_angle < 0:
total_angle += 2 * math.pi
# Number of segments. Increase for smoother curves.
arc_length = radius * abs(total_angle)
segments = int(arc_length * self.segment_multiplier) + 1 # Dynamic segments, at least 1.
segments = min(segments, self.max_segments) # Limit max segments
angle_increment = total_angle / segments
for i in range(segments + 1):
angle = start_angle + i * angle_increment
x = center[0] + radius * math.cos(angle)
y = center[1] + radius * math.sin(angle)
z = (self.current_position[2] + (new_position[2] - self.current_position[2]) * i / segments)
self.current_position = [x, y, z]
self.coordinates.append([x, y, z])
def process_g04(self, args):
"""Processes G04 (Dwell) command. For now, just prints a message.
Args:
args (dict): A dictionary of arguments, containing 'P' for the dwell time in milliseconds
"""
dwell_time = args.get('P', 0)
print(f"G04 Dwell for {dwell_time} milliseconds")
# In a real implementation, you would pause execution here. For this example, we just print.
pass
def get_coordinates(self):
"""
Returns the list of calculated coordinates.
Returns:
list: The list of coordinates.
"""
return self.coordinates
def reset(self):
"""Resets the interpreter to its initial state."""
self.current_position = [0, 0, 0]
self.feed_rate = 0
self.coordinates = [[0,0,0]]