Created
September 12, 2025 09:03
-
-
Save klattimer/8fb8e3264a7343c7dd6a486a49913f55 to your computer and use it in GitHub Desktop.
Fix up a LaserGRBL gcode file for the falcon 10W to cut it
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Fix the output of LaserGRBL (https://github.com/arkypita/LaserGRBL/tree/master/LaserGRBL) so that | |
| the Falcon 10W will actually cut the gcode. | |
| The bounds are calculated (required for falcon), a header is added, and the speed/power are adjustable. | |
| This code isn't intented to be used on engraving code, or anything that uses variable power or speed. | |
| """ | |
| import argparse | |
| import re | |
| template = """; Falcon G-code Preparation | |
| ; Number of passes: {passes} | |
| ; Laser power: {power} | |
| ; Movement speed: {speed} | |
| ; Dimensions: {width} x {height} | |
| ; Bounds: X({min_x}, {max_x}), Y({min_y}, {max_y}) | |
| G00 G17 G40 G21 G54 | |
| G90 | |
| M4 | |
| ; Cut @ {speed} mm/min, {power_pc}% power | |
| M8 | |
| {gcode} | |
| M5 | |
| M5 S0 | |
| M9 | |
| G1S0 | |
| M5 | |
| G90 | |
| ; return to user-defined finish pos | |
| G0 X0 Y0 | |
| M2 | |
| """ | |
| # Regex to find F followed by numbers at the end of a string | |
| f_pattern = re.compile(r'(F\d+)$') | |
| s_pattern = re.compile(r'(S\d+)$') | |
| def get_gcode_bounds(filename): | |
| """ | |
| Read a G-code file and find the minimum and maximum X, Y coordinates. | |
| Args: | |
| filename (str): Path to the G-code file | |
| Returns: | |
| tuple: (min_x, max_x, min_y, max_y) or None if no coordinates found | |
| """ | |
| min_x = min_y = float('inf') | |
| max_x = max_y = float('-inf') | |
| found_coords = False | |
| try: | |
| with open(filename, 'r') as file: | |
| for line in file: | |
| line = line.strip().upper() | |
| # Skip comments and empty lines | |
| if line.startswith(';') or line.startswith('(') or not line: | |
| continue | |
| x_pos = None | |
| y_pos = None | |
| # Parse X coordinate | |
| if 'X' in line: | |
| x_idx = line.find('X') | |
| x_str = '' | |
| for i in range(x_idx + 1, len(line)): | |
| if line[i].isdigit() or line[i] in '.-': | |
| x_str += line[i] | |
| else: | |
| break | |
| if x_str: | |
| try: | |
| x_pos = float(x_str) | |
| except ValueError: | |
| pass | |
| # Parse Y coordinate | |
| if 'Y' in line: | |
| y_idx = line.find('Y') | |
| y_str = '' | |
| for i in range(y_idx + 1, len(line)): | |
| if line[i].isdigit() or line[i] in '.-': | |
| y_str += line[i] | |
| else: | |
| break | |
| if y_str: | |
| try: | |
| y_pos = float(y_str) | |
| except ValueError: | |
| pass | |
| # Update bounds if coordinates were found | |
| if x_pos is not None: | |
| min_x = min(min_x, x_pos) | |
| max_x = max(max_x, x_pos) | |
| found_coords = True | |
| if y_pos is not None: | |
| min_y = min(min_y, y_pos) | |
| max_y = max(max_y, y_pos) | |
| found_coords = True | |
| except FileNotFoundError: | |
| print(f"Error: File '{filename}' not found.") | |
| return None | |
| except Exception as e: | |
| print(f"Error reading file: {e}") | |
| return None | |
| if not found_coords: | |
| print("No X/Y coordinates found in the G-code file.") | |
| return None | |
| return (min_x, max_x, min_y, max_y) | |
| def generate_gcode(filename, passes, power, speed): | |
| bounds = get_gcode_bounds(filename) | |
| if bounds is None: | |
| print("Cannot generate G-code due to missing bounds.") | |
| return "", None, None, None | |
| width, height = bounds[1] - bounds[0], bounds[3] - bounds[2] | |
| gcode = "" | |
| try: | |
| with open(filename, 'r') as file: | |
| for line in file: | |
| line = line.rstrip() | |
| if f_pattern.search(line): | |
| line = f_pattern.sub(f'F{round(int(speed))}', line) # Replace existing F command | |
| if s_pattern.search(line): | |
| line = s_pattern.sub(f'S{int(round(power))}', line) | |
| gcode += line + '\n' | |
| except FileNotFoundError: | |
| print(f"Error: File '{filename}' not found.") | |
| return "", None, None, None | |
| except Exception as e: | |
| print(f"Error reading file: {e}") | |
| return "", None, None, None | |
| gcode = gcode * passes | |
| gcode = template.format( | |
| passes=int(round(passes)), | |
| power=int(round(power)), | |
| power_pc=int(round(power / 10)), | |
| speed=int(round(speed)), | |
| width=width, | |
| height=height, | |
| min_x=bounds[0], | |
| max_x=bounds[1], | |
| min_y=bounds[2], | |
| max_y=bounds[3], | |
| gcode=gcode | |
| ) | |
| return gcode, bounds, width, height | |
| # Example usage | |
| if __name__ == "__main__": | |
| parser = argparse.ArgumentParser(description='Fix GCode for Falcon 10W Laser cutter') | |
| parser.add_argument('filename', help='Path to the G-code file') | |
| parser.add_argument('--output', '-o', help='Output file path (optional)') | |
| parser.add_argument('--passes', type=int, default=1, help='Number of passes (default: 1)') | |
| parser.add_argument('--power', type=float, default=1000, help='Laser power') | |
| parser.add_argument('--speed', type=float, default=350, help='Movement speed') | |
| args = parser.parse_args() | |
| gcode, bounds, width, height = generate_gcode(args.filename, args.passes, args.power, args.speed) | |
| if gcode: | |
| if args.output: | |
| try: | |
| with open(args.output, 'w', newline='\n') as out_file: | |
| out_file.write(gcode) | |
| print(f"G-code written to {args.output}") | |
| except Exception as e: | |
| print(f"Error writing to file: {e}") | |
| else: | |
| print(f'Bounds: X({bounds[0]}, {bounds[1]}), Y({bounds[2]}, {bounds[3]})') | |
| print(f'Dimensions: {width} x {height}') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment