Files
2026-11-06 08:12:29 +01:00

231 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python
import sys
import xml.etree.ElementTree as ET
import shapes as shapes_pkg
from shapes import point_generator
from config import *
import re
from math import isfinite
from datetime import datetime as dt
from optimise import optimise_path, get_total_distance
from utils import *
debug = False
# todo fix total distance algorithm
# todo can we guess the units?
# todo add manual scale option
# todo add z rise option
# todo why do i need to flip?
def get_shapes(path, auto_scale=True):
t1 = dt.now()
svg_shapes = set(['rect', 'circle', 'ellipse', 'line', 'polyline', 'polygon', 'path'])
shapes = []
tree = ET.parse(path)
root = tree.getroot()
pointRatio = 0.352778
width = root.get('width')
height = root.get('height')
if width is None or height is None:
viewbox = root.get('viewBox')
if viewbox:
_, _, width, height = viewbox.split()
if width is None or height is None:
print("Unable to get width and height for the svg")
sys.exit(1)
# width = float(re.sub("[^0-9]", "", width))
# height = float(re.sub("[^0-9]", "", height))
width = float(re.findall(r"[-+]?\d*\.\d+|\d+", width)[0])
height = float(re.findall(r"[-+]?\d*\.\d+|\d+", height)[0])
print("width / height ", width, height)
if units == "points":
width *= pointRatio
height *= pointRatio
for elem in root.iter():
try:
_, tag_suffix = elem.tag.split('}')
except ValueError:
continue
if tag_suffix in svg_shapes:
shape_class = getattr(shapes_pkg, tag_suffix)
shape_obj = shape_class(elem)
d = shape_obj.d_path()
m = shape_obj.transformation_matrix() # todo work out what d and m are
coords = []
if d: # begin shape processing
p = point_generator(d, m, smoothness) # tuples of x y coords
first = True
for x, y in p: # todo sort out this nightmare
if units == "points":
x *= pointRatio
y *= pointRatio
y = -y + height
if first:
#coords.append((x, -y + height))
coords.append((x, y))
else:
if not (x, y) == coords[-1]:
#coords.append((x, -y + height))
coords.append((x, y))
if first:
first = False
if coords:
shapes.append(coords)
if not (isfinite(bed_max_x) and isfinite(bed_max_y)
and bed_max_x > 0 and bed_max_y > 0):
raise ValueError("Bed dimensions must be finite and greater than zero")
points = [point for shape in shapes for point in shape]
if any(not (isfinite(x) and isfinite(y)) for x, y in points):
raise ValueError(f"{path}: drawing coordinates must be finite")
if auto_scale and points:
min_x = min(x for x, y in points)
max_x = max(x for x, y in points)
min_y = min(y for x, y in points)
max_y = max(y for x, y in points)
if min_x < 0 or max_x > bed_max_x or min_y < 0 or max_y > bed_max_y:
span_x = max_x - min_x
span_y = max_y - min_y
scale = min(1.0, bed_max_x / span_x if span_x else 1.0,
bed_max_y / span_y if span_y else 1.0)
print("auto fitting: scale", scale, "origin", (min_x, min_y))
# Bound floating-point roundoff at the fitted upper edges.
shapes = [[(min(bed_max_x, (x - min_x) * scale),
min(bed_max_y, (y - min_y) * scale))
for x, y in shape] for shape in shapes]
for shape in shapes:
for x, y in shape:
if not (0 <= x <= bed_max_x and 0 <= y <= bed_max_y):
raise ValueError(
f"{path}: drawing coordinate X{x:.6f} Y{y:.6f} mm "
f"is outside bed limits X[0, {bed_max_x}] "
f"Y[0, {bed_max_y}] mm"
)
timer(t1, "parsing gcode ")
return shapes
def g_string(x, y, z=False, prefix="G1", p=3):
if z is not False:
return f"{prefix} X{x:.{p}f} Y{y:.{p}f} Z{z:.{p}f}"
else:
return f"{prefix} X{x:.{p}f} Y{y:.{p}f}"
def shapes_2_gcode(shapes):
t1 = dt.now()
with open("header.txt") as h:
header = h.read()
commands = [f"{header}", preamble, f'F{feed_rate}']
for i in shapes:
commands += ['', shape_preamble, ""]
commands.append(g_string(i[0][0], i[0][1], zTravel, "G0"))
for j in i:
commands.append(g_string(j[0], j[1], zDraw))
commands.append(g_string(i[-1][0], i[-1][1], zTravel, "G0"))
commands += ["", shape_postamble, ""]
commands += [" ",postamble, ""]
commands += ["(home)", f"G0 Z{zTravel}", f"G0 X0 Y0"]
timer(t1, "shapes_2_gcode ")
return commands
def write_file(output, commands):
t1 = dt.now()
with open(output, 'w+') as output_file:
for i in commands:
output_file.write(i + "\n")
timer(t1, "writing file ")
def main(file_path, output):
sys.setrecursionlimit(max(sys.getrecursionlimit(), 100000))
shapes = get_shapes(file_path, auto_scale)
if not shapes:
raise ValueError(f"{file_path}: no drawable SVG shapes found")
if optimise:
pre_distance = get_total_distance(shapes)
new_order = optimise_path(shapes)
post_distance = get_total_distance(new_order)
print("unoptimised distance: ", pre_distance)
print("optimised distance ", post_distance)
if pre_distance:
print("factor: ", post_distance / pre_distance)
else:
print("factor: not applicable (no travel between shapes)")
commands = shapes_2_gcode(new_order)
else:
commands = shapes_2_gcode(shapes)
write_file(output, commands)
print("done")
if __name__ == "__main__":
# file_path = "./svg/lines.svg"
# file_path = "./svg/medium_example.svg"
file_path = "./svg/text.svg"
# file_path = "./svg/example.svg"
# file_path = "./svg/lines.svg"
output = "./gcode_optimised/1.gcode"
main(file_path, output)