Skip to content

Commit

Permalink
Updated comments and intro blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
evanchodora committed Apr 27, 2018
1 parent 9f58c83 commit f66e6a4
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 37 deletions.
29 changes: 20 additions & 9 deletions Slicer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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\
Expand Down Expand Up @@ -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)

Expand Down
6 changes: 1 addition & 5 deletions orient.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
59 changes: 36 additions & 23 deletions slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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]]
Expand All @@ -69,25 +75,26 @@ 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:
pairs.append([line[1][0], line[1][1]])
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:
points.append(pairs)

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

Expand All @@ -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
Expand All @@ -137,30 +147,33 @@ 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
# Fill lines at y-locations
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
Expand Down

0 comments on commit f66e6a4

Please sign in to comment.