Skip to content

Instantly share code, notes, and snippets.

@klattimer
Created September 12, 2025 09:03
Show Gist options
  • Select an option

  • Save klattimer/8fb8e3264a7343c7dd6a486a49913f55 to your computer and use it in GitHub Desktop.

Select an option

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