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]]