From f66e6a423fee3fa8c08cf275997666eb56bfc363 Mon Sep 17 00:00:00 2001 From: evanchodora Date: Thu, 26 Apr 2018 23:23:50 -0400 Subject: [PATCH] Updated comments and intro blocks --- Slicer.py | 29 ++++++++++++++++++--------- orient.py | 6 +----- slice.py | 59 +++++++++++++++++++++++++++++++++---------------------- 3 files changed, 57 insertions(+), 37 deletions(-) diff --git a/Slicer.py b/Slicer.py index b460d06..f7d9b27 100644 --- a/Slicer.py +++ b/Slicer.py @@ -12,7 +12,12 @@ from drawlines import draw_lines ''' -Program designed to open and view ASCII STL files +Program designed to open and view ASCII STL files and then slice them for 3D printing operations. Supports variable +slice heights and spacing between infill grid lines. Geometry moved to fit within a 8x8x6 inch print bed as large as +possible according to the part orientation. +Output files consist of an SVG file for each slice of the model showing the model outlines and the infill pattern and a +CSV file that lists the coordinates of the print head at different times and whether or not the extruder is on during +the move to that position. Evan Chodora, 2018 https://github.com/evanchodora/stl-slicer @@ -32,11 +37,12 @@ def __init__(self): # Function to plot the initial object after loading def plot(self, loc): + # Orient the object to the origin and scale to fit the print bed dimensions self.model.geometry = orient.to_origin(self.model.geometry) self.model.geometry = orient.fit_bed(self.model.geometry, xdim.get(), ydim.get(), zdim.get()) - # Apply selected perspective with appropriate settings of fz, phi, and theta + # Apply isometric perspective to the geometry plot_geometry, camera = gtransform.perspective(self.model.geometry) - # Draw lines between points and clip to viewing window based on window height and width + # Draw lines between points of the geometry faces plot_geometry = draw_lines(plot_geometry, self.model.normal, camera, view.get()) # Clear pixel array to white and then change each pixel color based on the XY pixel map @@ -115,6 +121,10 @@ def slice_geometry(self): # Create printer head path CSV file (main path and infill pattern) path.headpath(contour, fillx, filly, z) + # Calculate time vector describing the head motion and add to the CSV file + speed = 1 # Print head speed (inch/sec) + path.time_calc(speed) + # Info box to give information when the slicer is completed messagebox.showinfo('Slicing Complete!', 'The slicer has completed slicing the model successfully! \n\n' @@ -176,7 +186,7 @@ def file_select(): DrawObject.plot(file_select.stlobject, screen) # Run initial object plot function for the class -# Class to create a perspective settings popup dialog box for user input +# Class to create a slicer settings popup dialog box for user input class SettingsDialog: def __init__(self, parent): top = self.top = Toplevel(parent) # Use Tkinter top for a separate popup GUI @@ -270,10 +280,10 @@ def __init__(self, parent): self.NameLabel = Label(top, text='Slicer Settings').place(x=50, rely=.1, anchor="c") self.zLabel = Label(top, text='Slice Height (in)').place(x=55, rely=.3, anchor="c") self.InfillLabel = Label(top, text='Infill Spacing (in)').place(x=55, rely=.6, anchor="c") - self.zBox = Entry(top) # Fz entry box + self.zBox = Entry(top) # Z spacing entry box self.zBox.place(x=165, rely=.3, anchor="c", width=100) self.zBox.insert(0, slice_size.get()/25.4) # Prefill with Slice Height variable value (inches) - self.InfillBox = Entry(top) # Phi entry box + self.InfillBox = Entry(top) # Infill spacing entry box self.InfillBox.place(x=165, rely=.6, anchor="c", width=100) self.InfillBox.insert(0, infill_space.get()/25.4) # Prefill with infill grid spacing variable value (inches) @@ -411,6 +421,7 @@ def output_popup(): pygame.display.flip() # Code to load a base64 version of the coordinate axes to display on the GUI for the print bed coordinate system +# Load and place on the bottom right of the window below the controls for user reference coords = \ "iVBORw0KGgoAAAANSUhEUgAAAHgAAABiCAYAAACbKRcvAAAABHNCS\ VQICAgIfAhkiAAAAAlwSFlzAAAHsgAAB7IBq3xA6wAAABl0RVh0U2\ @@ -461,17 +472,17 @@ def output_popup(): view = StringVar() view.set('wire') -# Dimensions of the print bed in mm +# Default dimensions of the print bed in mm xdim = DoubleVar() xdim.set(8*25.4) ydim = DoubleVar() ydim.set(6*25.4) zdim = DoubleVar() zdim.set(8*25.4) -# Slice step size in mm +# Default slice step size in mm slice_size = DoubleVar() slice_size.set(0.5*25.4) -# Infill grid spacing in mm +# Default infill grid spacing in mm infill_space = DoubleVar() infill_space.set(0.5*25.4) diff --git a/orient.py b/orient.py index 20d4491..0698417 100644 --- a/orient.py +++ b/orient.py @@ -3,8 +3,7 @@ ''' Code to orient the initial geometry centered upon the geometric origin -Then scales the object to fit within a window of the width and height supplied based on an isometric perspective -and a supplied screen width and height in pixels +Then scales the object to fit within the viewing window and a supplied print bed size Evan Chodora, 2018 https://github.com/evanchodora/stl-slicer @@ -34,7 +33,4 @@ def fit_bed(geometry, xdim, ydim, zdim): scale = min(xdim/max_size[0], ydim/max_size[1], zdim/max_size[2]) geometry = gtransform.scale(geometry, 1 / scale) # Apply global scaling with appropriate factor - # max_size = np.max(geometry, axis=0) # Max X,Y,Z values of the object - # geometry = gtransform.translate(geometry, xdim/2, max_size[1], zdim/2) # Translate object into print bed center - return geometry diff --git a/slice.py b/slice.py index fd2925b..e75f612 100644 --- a/slice.py +++ b/slice.py @@ -4,6 +4,12 @@ ''' Codes to slice geometry at a given value Z (height above the print bed) +Functions: + - geom_to_bed_coords: moves geometry from origin for screen plotting to bed surface (Z=0) + - interpolation: interpolates between 2 points in 3D space based on a specific Z value between the points + - compute_points_on_z: converts each STL face that is cut by the Z slice to a pair of points to build an outer contour + - build_contours: converts the previously calculated discontinous point pairs into sets of continous contours + - infill: calculates the start and stop points for grid infill lines filling in the previously calculated contours Evan Chodora, 2018 https://github.com/evanchodora/stl-slicer @@ -14,14 +20,14 @@ def geom_to_bed_coords(geometry, xdim, ydim, zdim): # Rescale object geometry to the size of the print bed volume and translate from the origin to the center of the # print bed. The object plotted on the screen is larger for visual representation and centered at the origin in - # order to conduct object rotations + # order to conduct object rotations naturally max_size = np.max(geometry, axis=0) # Max X,Y,Z values of the object # Scale geometry to fit within the bed dimensions (convert from viewing size to actual) scale = min(xdim/(2*max_size[0]), ydim/(2*max_size[1]), zdim/(2*max_size[2])) geometry = gtransform.scale(geometry, 1/scale) # Apply global scaling with appropriate factor max_size = np.max(geometry, axis=0) # Max X,Y,Z values of the object - geometry = gtransform.translate(geometry, xdim/2, max_size[1], zdim/2) # Translate object into print bed center + geometry = gtransform.translate(geometry, xdim/2, max_size[1], zdim/2) # Translate object onto print bed surface return geometry @@ -39,21 +45,21 @@ def interpolation(p1, p2, slice_z): def compute_points_on_z(geometry, z, xdim, ydim, zdim): # Compute the points on the z slice plane geometry = geom_to_bed_coords(geometry, xdim, ydim, zdim) # Position and size geometry correctly - geometry = np.around(geometry, 5) + geometry = np.around(geometry, 5) # Round geometry data num_faces = int((geometry.shape[0]) / 3) # Every 3 points represents a single face (length/3) geometry = geometry[:, 0:3] # Specifically pull the X,Y,Z coordinates - ignore H points = [] # Initialize array of point pairs - tol = 0.005 + tol = 0.005 # Tolerance criteria for determining whether 2 points are unique (0.005 mm = 5 micron) for f in range(0, num_faces): # Loop every each face in the geometry set pairs = [] # Initialize/clear point pair set for each face xy = [geometry[3*(f+1)-3].tolist(), geometry[3*(f+1)-2].tolist(), geometry[3*(f+1)-1].tolist()] - # Store the 3 lines that make up face "f" using lists of points - # Remember: Screen Plotting = (X, Y, Z) and Printing Geometry = (Z, X, Y) + # Store the 3 points that make up face "f" using lists of points + # Remember: Screen Plotting = (X, Y, Z) and Printing Geometry = (Z, X, Y) so order needs to be adjusted here line = [[xy[0][2], xy[0][0], xy[0][1]], [xy[1][2], xy[1][0], xy[1][1]], [xy[2][2], xy[2][0], xy[2][1]]] - # If a line segment bounds the z slice plane: + # If a line segment bounds the Z slice plane: if (line[1][2] < z < line[0][2]) or (line[0][2] < z < line[1][2]): p1 = [line[0][0], line[0][1], line[0][2]] p2 = [line[1][0], line[1][1], line[1][2]] @@ -69,7 +75,7 @@ def compute_points_on_z(geometry, z, xdim, ydim, zdim): p2 = [line[2][0], line[2][1], line[2][2]] pairs.append(interpolation(p1, p2, z)) - # + # If a point on the face exactly matches the Z slice plane if line[0][2] == z: pairs.append([line[0][0], line[0][1]]) elif line[1][2] == z: @@ -77,9 +83,10 @@ def compute_points_on_z(geometry, z, xdim, ydim, zdim): elif line[2][2] == z: pairs.append([line[2][0], line[2][1]]) - # Only need to keep point pairs (1 and 3 are not needed - handled by other vertices in the object) + # Only need to keep point pairs (sets of 1 and 3 are not needed - these will be inherently handled by a point + # pair from a face somehwere else in the object geometry) if len(pairs) == 2: - # Reject points if they are the same (within tolerance) - not a path pair for printing + # Reject points if they are the same (within tolerance) - too close together, not a path pair for printing if abs(pairs[0][0] - pairs[1][0]) < tol and abs(pairs[0][1] - pairs[1][1]) < tol: pass else: @@ -87,7 +94,7 @@ def compute_points_on_z(geometry, z, xdim, ydim, zdim): points = [item for sublist in points for item in sublist] # Flatten list sets into an array edge_points = np.asarray(points).reshape((-1, 4)) # Reshape and convert to X1,Y1,X2,Y2 numpy array - edge_points = np.around(edge_points, 5) + edge_points = np.around(edge_points, 5) # Round these points return edge_points @@ -99,33 +106,36 @@ def build_contours(edge_points): tol = 0.005 # Tolerance criteria for matching the next point in the contour points_left = edge_points # Initialize points that are remaining = original points tail = [] - loop_cnt = 1 # Count number of times searhced over the remaining points for geometry error handling + loop_cnt = 1 # Count number of times searched over the remaining points for geometry error handling while len(points_left) != 0: # Loop over the point pairs and the index in the data array j = 1 - if not contours: # First point pair needs to be added to the contour data if variable is empty + if not contours: # First point pair needs to always be added to the contour data if variable is empty pair = points_left[0, :] contours.append([pair[0], pair[1], pair[2], pair[3], contour_num]) tail = [pair[2], pair[3]] # Tail is the second point of the line pair points_left = np.delete(points_left, 0, axis=0) # Remove the point pair from the array of points while j <= len(points_left): pair = points_left[j-1, :] + # If either point in that pair matches the tail from the previous head-tail pair: if abs(pair[0]-tail[0]) < tol and abs(pair[1]-tail[1]) < tol: contours.append([pair[0], pair[1], pair[2], pair[3], contour_num]) - points_left = np.delete(points_left, j-1, axis=0) + points_left = np.delete(points_left, j-1, axis=0) # Remove the point pair from the array of points tail = [pair[2], pair[3]] # Tail is the second point of the line pair - loop_cnt = 1 + loop_cnt = 1 # Reset search loop counter break if abs(pair[2] - tail[0]) < tol and abs(pair[3] - tail[1]) < tol: contours.append([pair[2], pair[3], pair[0], pair[1], contour_num]) - points_left = np.delete(points_left, j-1, axis=0) + points_left = np.delete(points_left, j-1, axis=0) # Remove the point pair from the array of points tail = [pair[0], pair[1]] # Tail is the second point of the line pair - loop_cnt = 1 + loop_cnt = 1 # Reset search loop counter break j = j + 1 - loop_cnt = loop_cnt + 1 + loop_cnt = loop_cnt + 1 # Increment loop search counter + # If there are still points left and either the last point in the contour equals the first (completed loop) or + # have unsuccessfully searched over all the points twice to find a match then create a new contour loop if (len(points_left) != 0 and abs(contours[0][0]-contours[-1][2]) < tol and abs(contours[0][1]-contours[-1][3]) < tol) or loop_cnt > 2*len(edge_points): - contour_num = contour_num + 1 + contour_num = contour_num + 1 # Increment contour loop number pair = points_left[0, :] contours.append([pair[0], pair[1], pair[2], pair[3], contour_num]) tail = [pair[2], pair[3]] # Tail is the second point of the line pair @@ -137,22 +147,25 @@ def build_contours(edge_points): def infill(pairs, direct, spacing): # Function to compute the line infill spacing for the 3D printing - # direction: X=0, Y=1 + # direction (direct): X-axis = 0 and Y-axis = 1 fill = [] if len(pairs) != 0: - # Calculate max and min dimensions of the sliced points + # Calculate max and min dimensions of the sliced points (in either X or Y) min_pos = min(np.min(pairs[:, direct]), np.min(pairs[:, direct+2])) max_pos = max(np.max(pairs[:, direct]), np.max(pairs[:, direct+2])) num_passes = int((max_pos - min_pos)/spacing) # Number of infill lines to cover the object + # Loop over the number of infill lines needed for fill_pass in range(num_passes+1): loc = min_pos + fill_pass*spacing # Increment fill pass position by the infill spacing variable pts = [] + # Loop over each segment in the list of countour point pairs for segment in pairs: + # If the line falls on either side of the current fill pass line position if segment[direct] < loc < segment[direct+2] or segment[direct+2] < loc < segment[direct]: - m = (segment[3]-segment[1])/(segment[2]-segment[0]) + m = (segment[3]-segment[1])/(segment[2]-segment[0]) # Calculate the slope of the line # Fill lines at x-locations if direct == 0: pts.append(m * (loc - segment[direct]) + segment[direct+1]) # y = m(x_loc-x1)+y1 @@ -160,7 +173,7 @@ def infill(pairs, direct, spacing): else: # Check if the slope is infinite (#/0) if isinf(m): - pts.append(segment[direct+1]) # Intercept at either x-location of the segment + pts.append(segment[direct+1]) # Intercept at either x-location of the segment (pick 2nd) else: pts.append((loc-segment[direct])/m + segment[direct-1]) # x = (y_loc-y1)/m + x1 pts.sort() # Sort points in order to construct infill path lines