commit 3b74e57e1d9f6f945ffedc7a63ebe1f0deef247f Author: MitchellHansen Date: Wed Jan 9 14:51:27 2019 -0800 init diff --git a/activate.sh b/activate.sh new file mode 100755 index 0000000..ebe8050 --- /dev/null +++ b/activate.sh @@ -0,0 +1,4 @@ +sudo apt install libcairo2-dev pkg-config +virtualenv -p python3 .env +source .env/bin/activate +pip3 install -r requirements.txt diff --git a/input-images/morty.bmp b/input-images/morty.bmp new file mode 100644 index 0000000..7a25171 Binary files /dev/null and b/input-images/morty.bmp differ diff --git a/main.py b/main.py new file mode 100644 index 0000000..1733ae6 --- /dev/null +++ b/main.py @@ -0,0 +1,212 @@ +import math + +import numpy as np + +touch_height = 20 +raise_height = 2 +head_x_offset = 50 +speed = 500 +lift_markers = True + +PREAMBLE = ''' +G1 Z20 +M107 +M190 S0 +M104 S0 +G28 ; home all axes +G0 F{1} +G1 Z{0} +G1 Z{0} +'''.format(touch_height + raise_height, speed) + +FINISH = """ +G1 Z{0} F7000 +M104 S0 +G28 X0 Y0 +M84 +""".format(75) + +import cairo, subprocess, bezier, os +from svgpathtools import svg2paths, Line, QuadraticBezier, CubicBezier + +# Setup the file structure +if not os.path.exists("output"): + os.makedirs("output") + +# Convert the bmp to a vector svg +file_name = "geom" + +subprocess.call(["mogrify", "-format", "bmp", "input-images/{}.svg".format(file_name)]) + +subprocess.call(["mkbitmap", "input-images/{}.bmp".format(file_name), "-x", + "-f", "15", + #"-b", "0", + "-o", "input-images/{}-n.bmp".format(file_name) + ]) + +subprocess.call(["potrace", + "-t", "20", + "-z", "white", + "-b", "svg", + "input-images/{}-n.bmp".format(file_name), + "--rotate", "90", + "-o", "tmp/conversion-output.svg", + ]) + +# read in the svg +paths, attributes = svg2paths("tmp/conversion-output.svg") + +gcode = "" +gcode += PREAMBLE + +started = False + +scale = 0.0045 +offset_x = 75 + head_x_offset +offset_y = 20 + + +# Walk through the paths and create the GCODE +for path in paths: + + previous_x = None + previous_y = None + + # rotated = path.rotated(90) + + for part in path: + + start = part.start + end = part.end + + start_x = start.real * scale + offset_x + start_y = start.imag * scale + offset_y + + end_x = end.real * scale + offset_x + end_y = end.imag * scale + offset_y + + # Check to see if the endpoint of the last cycle continues and wether we need to lift the pen or not + lift = True + if previous_x is not None and previous_y is not None: + if abs(start.real - previous_x) < 30 and abs(start.imag - previous_y) < 30: + lift = False + + # if the pen needs to lift, + # if lift: + previous_x = end.real + previous_y = end.imag + + if lift: + gcode += "G1 Z{}\n".format(raise_height + touch_height) + else: + gcode += "# NOT LIFTING\n" + + if isinstance(part, CubicBezier): + + nodes = np.asfortranarray([ + [start.real, part.control1.real, part.control2.real, end.real], + [start.imag, part.control1.imag, part.control2.imag, end.imag], + ]) + + curve = bezier.Curve.from_nodes(nodes) + + evals = [] + pos = np.linspace(0.1, 1, 10) + for i in pos: + evals.append(curve.evaluate(i)) + + + + gcode += "G1 X{} Y{}\n".format(start_x, start_y) + gcode += "G1 Z{} \n".format(touch_height) + + for i in evals: + x = i[0][0] + y = i[1][0] + gcode += "G1 X{} Y{}\n".format(x * scale + offset_x, y * scale + offset_y) + + + #gcode += "G1 X{} Y{}\n".format(end.real * scale + offset_x, end.imag * scale + offset_y) + + + + if isinstance(part, Line): + gcode += "G1 X{} Y{}\n".format(start_x, start_y) + gcode += "G1 Z{} \n".format(touch_height) + gcode += "G1 X{} Y{}\n".format(end_x, end_y) + + +gcode += FINISH + +output_gcode = open("output/gcode-output.gcode", "w") +output_gcode.write(gcode) +output_gcode.close() + +file = open("output/gcode-output.gcode", "r") + +x = None +y = None + +with cairo.SVGSurface("rendered-output.svg", 300, 300) as surface: + + context = cairo.Context(surface) + context.scale(1, 1) + context.set_line_width(0.4) + + largest_x = 0 + largest_y = 0 + smallest_x = 300 + smallest_y = 300 + + for line in file: + + split = line.split(" ") + command = split[0] + operands = split[1:] + + prev_x = x + prev_y = y + + if command == "G1": + for operand in operands: + if operand.startswith("X"): + x = float(operand[1:]) + if x > largest_x: largest_x = x + if x < smallest_x: smallest_x = x + elif operand.startswith("Y"): + y = float(operand[1:]) + if y > largest_y: largest_y = y + if y < smallest_y: smallest_y = y + elif operand.startswith("Z{}".format(touch_height + raise_height)): + + # signify a lift + if prev_x is not None and prev_y is not None and lift_markers: + context.arc(prev_x, prev_y, 0.5, 0, 2*math.pi) + context.stroke() + + prev_x = None + prev_y = None + x = None + y = None + + if (prev_x != x and prev_x is not None) or (prev_y != y and prev_y is not None): + context.line_to(prev_x, prev_y) + context.line_to(x, y) + context.stroke() + + + print("Largest X : " + str(largest_x)) + print("Largest Y : " + str(largest_y)) + print("Smallest X : " + str(smallest_x)) + print("Smallest Y : " + str(smallest_y)) + + if largest_x > 280: + print("X OVERFLOW") + if largest_y > 280: + print("Y OVERFLOW") + + if smallest_x < 125: + print("X_UNDERFLOW") + if smallest_y < 20: + print("Y_UNDERFLOW") + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a0e735d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,26 @@ +asn1crypto==0.24.0 +bezier==0.9.0 +certifi==2018.1.18 +cffi==1.11.5 +chardet==3.0.4 +cryptography==2.1.4 +cycler==0.10.0 +decorator==4.1.2 +idna==2.8 +matplotlib==2.1.1 +numpy==1.15.4 +Pillow==5.1.0 +pkg-resources==0.0.0 +pycairo==1.16.2 +pycparser==2.19 +pycrypto==2.6.1 +PyNaCl==1.1.2 +PyOpenGL==3.1.0 +pyparsing==2.2.0 +pyqtgraph==0.10.0 +python-dateutil==2.7.5 +pytz==2018.9 +scipy==0.19.1 +six==1.12.0 +svgpathtools==1.3.3 +svgwrite==1.2.1