|
# G-code replicator |
|
# Written by tana_ash. May 2026. |
|
# Licensed under the MIT License. |
|
|
|
import sys |
|
import re |
|
import itertools |
|
|
|
filename = sys.argv[1] |
|
count_x = int(sys.argv[2]) |
|
count_y = int(sys.argv[3]) |
|
spacing_x = int(sys.argv[4]) |
|
spacing_y = int(sys.argv[5]) |
|
move_z = int(sys.argv[6]) |
|
|
|
exclude_object_define_p = re.compile(r'^EXCLUDE_OBJECT_DEFINE NAME=(?P<name>[\w.]+) CENTER=(?P<center>[0-9.]+,[0-9.]+)( POLYGON=(?P<polygon>\[(\[[0-9.]+,[0-9]+\])(,\[[0-9.]+,[0-9.]+\])*\]))?', re.IGNORECASE) |
|
exclude_object_start_p = re.compile(r'^EXCLUDE_OBJECT_START NAME=(?P<name>[\w.]+)', re.IGNORECASE) |
|
exclude_object_end_p = re.compile(r'^EXCLUDE_OBJECT_END', re.IGNORECASE) |
|
g90_p = re.compile(r'^G90', re.IGNORECASE) |
|
g91_p = re.compile(r'^G91', re.IGNORECASE) |
|
|
|
object_defs = dict() |
|
current_object_name = None |
|
stored_gcode = [] |
|
relative = False |
|
last_shift_x = 0 |
|
last_shift_y = 0 |
|
|
|
for line in open(filename).readlines(): |
|
line = line.rstrip() |
|
|
|
match = exclude_object_define_p.match(line) |
|
if match: |
|
name = match.group('name') |
|
center = [float(coord) for coord in match.group('center').split(",")] |
|
polygon = match.group('polygon') |
|
object_defs[name] = (center, polygon) |
|
print(f"Object {name} defined", file=sys.stderr) |
|
|
|
for i in range(count_x * count_y): |
|
new_name = name + f"_{i}" |
|
cmd = f"EXCLUDE_OBJECT_DEFINE NAME={new_name} CENTER={center[0]},{center[1]}" |
|
# TODO: polygon coordinate conversion |
|
if polygon: |
|
cmd += f" POLYGON={polygon}" |
|
print(cmd) |
|
|
|
continue |
|
|
|
match = exclude_object_start_p.match(line) |
|
if match: |
|
name = match.group('name') |
|
if name in object_defs: |
|
current_object_name = name |
|
else: |
|
print(f"Object {name} not found", file=sys.stderr) |
|
|
|
continue |
|
|
|
match = exclude_object_end_p.match(line) |
|
if match: |
|
if current_object_name is None: |
|
print("Unexpected EXCLUDE_OBJECT_END", file=sys.stderr) |
|
|
|
(center_x, center_y), _ = object_defs[current_object_name] |
|
|
|
for i, j in itertools.product(range(count_x), range(count_y)): |
|
shift_x = spacing_x * (i - (count_x - 1) / 2) |
|
shift_y = spacing_y * (j - (count_x - 1) / 2) |
|
|
|
print(f"EXCLUDE_OBJECT_START NAME={current_object_name}_{count_y * i + j}") |
|
|
|
# Retract |
|
print("G10") |
|
# Raise nozzle |
|
print("G91") |
|
print(f"G0 Z{move_z}") |
|
# Move to the current object center |
|
print("G90") |
|
print(f"G0 X{center_x} Y{center_y}") |
|
# Move to the new object center |
|
print(f"G91") |
|
print(f"G0 X{shift_x - last_shift_x} Y{shift_y - last_shift_y}") |
|
# Shift coordinate |
|
print(f"G92 X{center_x} Y{center_y}") |
|
# Lower nozzle |
|
#print("G91") |
|
#print(f"G0 Z{-move_z}") |
|
# Restore relative/absolute mode |
|
if relative: |
|
print("G91") |
|
else: |
|
print("G90") |
|
# Unretract |
|
print("G11") |
|
|
|
last_shift_x = shift_x |
|
last_shift_y = shift_y |
|
|
|
for stored_line in stored_gcode: |
|
print(stored_line) |
|
|
|
print(f"EXCLUDE_OBJECT_END") |
|
|
|
current_object_name = None |
|
stored_gcode = [] |
|
|
|
continue |
|
|
|
match = g90_p.match(line) |
|
if match: |
|
print("Changed to absolute mode", file=sys.stderr) |
|
relative = False |
|
|
|
match = g91_p.match(line) |
|
if match: |
|
print("Changed to relative mode", file=sys.stderr) |
|
relative = True |
|
|
|
if current_object_name: |
|
stored_gcode.append(line) |
|
continue |
|
|
|
print(line) |