From fd5940d76626ebad719e79ab21c188db993a0c72 Mon Sep 17 00:00:00 2001 From: MitchellHansen Date: Fri, 31 May 2019 15:44:42 -0700 Subject: [PATCH] Swapping over to dual motor --- .idea/Trac3r.iml | 11 ++++ .idea/misc.xml | 7 +++ .idea/modules.xml | 8 +++ .idea/vcs.xml | 6 ++ Renderer.py => GCodeRenderer.py | 31 +++------- ImageConverter.py | 50 ++++++++++++++++ Svg2GcodeConverter.py | 24 ++++---- main.py | 103 +++++++++++--------------------- 8 files changed, 137 insertions(+), 103 deletions(-) create mode 100644 .idea/Trac3r.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml rename Renderer.py => GCodeRenderer.py (73%) create mode 100644 ImageConverter.py diff --git a/.idea/Trac3r.iml b/.idea/Trac3r.iml new file mode 100644 index 0000000..6711606 --- /dev/null +++ b/.idea/Trac3r.iml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a4ec689 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..10d63ed --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Renderer.py b/GCodeRenderer.py similarity index 73% rename from Renderer.py rename to GCodeRenderer.py index ffd3678..3fb3062 100644 --- a/Renderer.py +++ b/GCodeRenderer.py @@ -1,34 +1,26 @@ import cairo, os, math - +# This renderer takes the generated GCODE and turns it into two images +# One is an SVG of the tool paths, the other a png image class Renderer(): def __init__(self, settings): self.settings = settings - self.svg_surface = cairo.SVGSurface("tmp/rendered-output-t.svg", self.settings.bed_actual_x, self.settings.bed_actual_y) + self.svg_surface = cairo.SVGSurface("tmp/rendered-output-t.svg", self.settings.canvas_x, self.settings.canvas_y) self.svg_context = cairo.Context(self.svg_surface) self.svg_context.scale(1, 1) self.svg_context.set_line_width(0.1) def clear_screen(self): - self.svg_context.rectangle(0, 0, self.settings.bed_actual_x, self.settings.bed_actual_y) + self.svg_context.rectangle(0, 0, self.settings.canvas_x, self.settings.canvas_y) self.svg_context.set_source_rgba(1, 1, 1, 1.0) self.svg_context.fill() self.svg_context.set_source_rgba(0, 0, 0, 1.0) self.svg_context.stroke() - self.svg_context.set_source_rgba(1, 0, 0, 1.0) - self.svg_context.line_to(self.settings.bed_min_x - self.settings.head_x_offset, self.settings.bed_min_y) - self.svg_context.line_to(self.settings.bed_max_x - self.settings.head_x_offset, self.settings.bed_min_y) - self.svg_context.line_to(self.settings.bed_max_x - self.settings.head_x_offset, self.settings.bed_max_y) - self.svg_context.line_to(self.settings.bed_min_x - self.settings.head_x_offset, self.settings.bed_max_y) - self.svg_context.line_to(self.settings.bed_min_x - self.settings.head_x_offset, self.settings.bed_min_y) - self.svg_context.stroke() - self.svg_context.set_source_rgba(0, 0, 0, 1.0) - # Render GCODE from the gcode-output.gcode output file that was generated in convert_gcode def render_gcode(self): @@ -60,14 +52,16 @@ class Renderer(): y = float(operand[1:]) if y > largest_y: largest_y = y if y < smallest_y: smallest_y = y - elif operand.startswith("Z{}".format(self.settings.touch_height + self.settings.raise_height)): + elif operand.startswith("Z{}".format(1)): # signify a lift if prev_x is not None and prev_y is not None and self.settings.lift_markers: - self.svg_context.arc(prev_x - self.settings.head_x_offset, prev_y, 0.5, 0, 2 * math.pi) + # draw a cirlce at the lift + self.svg_context.arc(prev_x, prev_y, 0.5, 0, 2 * math.pi) self.svg_context.stroke() + # And draw the lift number self.svg_context.set_source_rgba(1, 1, 1, 1.0) self.svg_context.select_font_face("Purisa", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) @@ -104,15 +98,6 @@ class Renderer(): if smallest_y < self.settings.bed_min_y: print("Y_UNDERFLOW") - self.svg_context.set_source_rgba(0, 0, 1, 1.0) - self.svg_context.line_to(smallest_x - self.settings.head_x_offset, smallest_y) - self.svg_context.line_to(largest_x - self.settings.head_x_offset, smallest_y) - self.svg_context.line_to(largest_x - self.settings.head_x_offset, largest_y) - self.svg_context.line_to(smallest_x - self.settings.head_x_offset, largest_y) - self.svg_context.line_to(smallest_x - self.settings.head_x_offset, smallest_y) - self.svg_context.stroke() - self.svg_context.set_source_rgba(0, 0, 0, 1.0) - self.save_surfaces() # self.init_surfaces() diff --git a/ImageConverter.py b/ImageConverter.py new file mode 100644 index 0000000..ba86cfb --- /dev/null +++ b/ImageConverter.py @@ -0,0 +1,50 @@ +import subprocess, os, time + +class ImageConverter: + + class ConverterSettings: + def __init__(self): + # mkbitmap settings + self.highpass_filter = 0 + self.blur = 0 + + # This function takes a file and runs it through mogrify, mkbitmap, and finally potrace. + # The flow of the intermediate files is + # input_file.extension : The input file + # input_file.bmp : The input file converted to bmp + # input_file-n.bmp : The bmp file after running through some filters + # input_file.svg : The output svg render + def convert_image(self, file_name, settings): + base_name = file_name.split(".")[0] + + print("Converting input file [{}]".format(file_name)) + + print("Running mogrify...") + start = time.time() + subprocess.call(["mogrify", "-format", "bmp", "input-images/{}".format(file_name)]) + print("Run took [{:.2f}] seconds".format(time.time() - start)) + + print("Running mkbitmap...") + start = time.time() + mkbitmap_args = ["mkbitmap", "input-images/{}.bmp".format(base_name), + "-o", "input-images/{}-n.pbm".format(base_name)] + if settings.highpass_filter > 0: + mkbitmap_args.append(["-f", settings.highpass_filter]) + + if settings.blur > 0: + mkbitmap_args.append(["-b", settings.blur]) + + subprocess.call(mkbitmap_args) + print("Run took [{:.2f}] seconds".format(time.time() - start)) + + print("Running potrace...") + start = time.time() + subprocess.call(["potrace", + # "-t", "0.1", + "-z", "white", + "-b", "svg", + "input-images/{}-n.pbm".format(base_name), + "--rotate", "0", + "-o", "tmp/conversion-output.svg", + ]) + print("Run took [{:.2f}] seconds\n".format(time.time() - start)) \ No newline at end of file diff --git a/Svg2GcodeConverter.py b/Svg2GcodeConverter.py index b560694..3d8ff6d 100644 --- a/Svg2GcodeConverter.py +++ b/Svg2GcodeConverter.py @@ -22,14 +22,14 @@ class Svg2GcodeConverter: G28 ; home all axes G0 F{1} ; Set the feed rate G1 Z{0} ; Move the pen to just above the paper - '''.format(self.settings.touch_height + self.settings.raise_height, self.settings.speed) + '''.format(1, self.settings.speed) self.gcode_end = ''' G1 Z{0} F7000 ; Raise the pen high up so we can fit a cap onto it M104 S0 ; Set the nozzle to 0 G28 X0 Y0 ; Home back to (0,0) for (x,y) M84 ; Turn off the motors - '''.format(75) + '''.format(1) # From an input svg file, convert the vector svg paths to gcode tool paths def convert_gcode(self): @@ -70,8 +70,8 @@ class Svg2GcodeConverter: max_x_dim = max(bounding_x_max, bounding_x_min) max_y_dim = max(bounding_y_max, bounding_y_min) - scale_x = (self.settings.bed_max_x - self.settings.bed_min_x) / max_x_dim - scale_y = (self.settings.bed_max_y - self.settings.bed_min_y) / max_y_dim + scale_x = self.settings.canvas_x / max_x_dim + scale_y = self.settings.canvas_y / max_y_dim scale = min(scale_x, scale_y) print("Scaling to : {:.5f}\n".format(scale)) @@ -91,11 +91,11 @@ class Svg2GcodeConverter: start = part.start end = part.end - start_x = start.real * scale + self.settings.offset_x - start_y = start.imag * scale + self.settings.offset_y + start_x = start.real * scale + start_y = start.imag * scale - end_x = end.real * scale + self.settings.offset_x - end_y = end.imag * scale + self.settings.offset_y + end_x = end.real * scale + end_y = end.imag * scale # Check to see if the endpoint of the last cycle continues and whether we need to lift the pen or not lift = True @@ -109,7 +109,7 @@ class Svg2GcodeConverter: previous_y = end.imag if lift: - gcode += "G1 Z{:.3f}\n".format(self.settings.raise_height + self.settings.touch_height) + gcode += "G1 Z{:.3f}\n".format(1) else: gcode += ";# NOT LIFTING [{}]\n".format(self.settings.lift_counter) @@ -128,16 +128,16 @@ class Svg2GcodeConverter: evals.append(curve.evaluate(i)) gcode += "G1 X{:.3f} Y{:.3f}\n".format(start_x, start_y) - gcode += "G1 Z{:.3f} \n".format(self.settings.touch_height) + gcode += "G1 Z{:.3f} \n".format(0) for i in evals: x = i[0][0] y = i[1][0] - gcode += "G1 X{:.3f} Y{:.3f}\n".format(x * scale + self.settings.offset_x, y * scale + self.settings.offset_y) + gcode += "G1 X{:.3f} Y{:.3f}\n".format(x * scale, y * scale) if isinstance(part, Line): gcode += "G1 X{:.3f} Y{:.3f}\n".format(start_x, start_y) - gcode += "G1 Z{:.3f} \n".format(self.settings.touch_height) + gcode += "G1 Z{:.3f} \n".format(0) gcode += "G1 X{:.3f} Y{:.3f}\n".format(end_x, end_y) gcode += self.gcode_end diff --git a/main.py b/main.py index a6e6d09..9b59d33 100644 --- a/main.py +++ b/main.py @@ -3,43 +3,43 @@ from tkinter import filedialog from tkinter.ttk import Notebook from PIL import Image, ImageTk -import subprocess, os, time +import os -from Renderer import Renderer +from GCodeRenderer import Renderer from Svg2GcodeConverter import Svg2GcodeConverter - +from ImageConverter import ImageConverter class Settings: def __init__(self): - # Height at which the pen touches and draws on the surface - self.touch_height = 12 - # How far to raise the pen tip to raise it off the page - self.raise_height = 2 - # The inherent offset from true 0 we have from the pen bracket - self.head_x_offset = 50 - # XY movement speed + # ============ HARDCODED VALUES =========== + + # Canvas size + self.canvas_x = 300 + self.canvas_y = 300 + + # The position of the pulley centers in relation to the top left and right of the canvas + self.left_pulley_xy_offset = (-40, 40) + self.right_pulley_xy_offset = (40, 40) + + # Diameter of the inner portion of the pulley in millimeters + self.pulley_diameter = 45 + + # Feed rates self.speed = 1000 + # Whether we render lift markers self.lift_markers = False - # X and Y offsets to place the image on A11 paper - self.offset_x = 70 + self.head_x_offset - self.offset_y = 20 + # ============ CALCULATED VALUES =========== - # Bed dimensions to fit A11 paper - self.bed_max_x = 300 - 70 + self.head_x_offset + 20 # 20 is to adjust for the misalignment of print bed - self.bed_min_x = self.offset_x - self.bed_max_y = 280 - self.bed_min_y = 20 + self.distance_between_centers = abs(self.left_pulley_xy_offset[0]) + self.canvas_x + self.right_pulley_xy_offset[0] - self.bed_actual_x = 300 - self.bed_actual_y = 300 - self.lift_counter = 0 +# Main GUI class and program entry point class Tracer(Tk): def update_highpass_value(self, value): @@ -48,6 +48,7 @@ class Tracer(Tk): def update_blur_value(self, value): self.blur = value + def __init__(self): super().__init__() @@ -58,15 +59,21 @@ class Tracer(Tk): if not os.path.exists("tmp"): os.makedirs("tmp") + # Settings for the printer are loaded, TODO: Customize for our dual motor printer self.settings = Settings() + # Image filename which we are converting self.filename = None + # GCODE -> SVG,PNG renderer self.cairo_renderer = Renderer(self.settings) + + # SVG -> GCODE converter self.gcode_converter = Svg2GcodeConverter(self.settings) - self.highpass_filter = 0 - self.blur = 0 + # FILE -> SVG converter + self.image_converter = ImageConverter() + self.image_converter_settings = ImageConverter.ConverterSettings() self.label = None self.pix = None @@ -95,17 +102,18 @@ class Tracer(Tk): self.lift_markers_checkbox.pack() self.highpass_slider = Scale(self.rightframe, command=self.update_highpass_value, resolution=0.1, to=15) - self.highpass_slider.set(self.highpass_filter) + self.highpass_slider.set(self.image_converter_settings.highpass_filter) self.highpass_slider.pack() self.blur_slider = Scale(self.rightframe, command=self.update_blur_value, resolution=0.1, to=5) - self.blur_slider.set(self.blur) + self.blur_slider.set(self.image_converter_settings.blur) self.blur_slider.pack() # Start TK self.mainloop() def file_select_callback(self): + filepath = filedialog.askopenfilename(initialdir=".", title="Select file", filetypes=(("jpeg files", "*.jpg"), ("all files", "*.*"))) @@ -120,7 +128,7 @@ class Tracer(Tk): self.render() def render(self): - self.convert_image(self.filename) + self.image_converter.convert_image(self.filename) self.gcode_converter.convert_gcode() self.cairo_renderer.clear_screen() @@ -150,48 +158,7 @@ class Tracer(Tk): self.label1.pack(expand=True, fill="both") - # This function takes a file and runs it through mogrify, mkbitmap, and finally potrace. - # The flow of the intermediate files is - # input_file.extension : The input file - # input_file.bmp : The input file converted to bmp - # input_file-n.bmp : The bmp file after running through some filters - # input_file.svg : The output svg render - def convert_image(self, file_name): - - base_name = file_name.split(".")[0] - - print("Converting input file [{}]".format(file_name)) - - print("Running mogrify...") - start = time.time() - subprocess.call(["mogrify", "-format", "bmp", "input-images/{}".format(file_name)]) - print("Run took [{:.2f}] seconds".format(time.time() - start)) - - print("Running mkbitmap...") - start = time.time() - mkbitmap_args = ["mkbitmap", "input-images/{}.bmp".format(base_name), - "-o", "input-images/{}-n.pbm".format(base_name)] - if self.highpass_filter > 0: - mkbitmap_args.append(["-f", self.highpass_filter]) - - if self.blur > 0: - mkbitmap_args.append(["-b", self.blur]) - - - subprocess.call(mkbitmap_args) - print("Run took [{:.2f}] seconds".format(time.time() - start)) - - print("Running potrace...") - start = time.time() - subprocess.call(["potrace", - #"-t", "0.1", - "-z", "white", - "-b", "svg", - "input-images/{}-n.pbm".format(base_name), - "--rotate", "0", - "-o", "tmp/conversion-output.svg", - ]) - print("Run took [{:.2f}] seconds\n".format(time.time() - start)) + if __name__ == "__main__":