# # cam.py # # usage: python cam.py [infile] [xoffset yoffset] [display size] [outfile] [undercut] # # input: # *.dxf: DXF (polylines) # *.cmp,*.sol,*.plc: Gerber # RS-274X format, with 0-width trace defining board boundary # *.drl: Excellon drill file, with tool defitions # output: # *.rml: Roland Modela RML mill # *.camm: Roland CAMM cutter # *.jpg,*.bmp: images # *.epi: Epilog lasercutter # *.g: G codes # toolpath modes: 1D path, contour, raster # keys: q to quit # # (C)BA Neil Gershenfeld # commercial sale licensed by MIT DATE = "11/9/03" from Tkinter import * from string import * from math import * from random import * import sys #, Image, ImageDraw - commented out until the tutorial will be fixed to include these # # window size in pixels # WINDOW = 500 # # numerical roundoff tolerance for testing intersections # EPS = 1e-20 # # hack: std dev of numerical noise to add to remove degeneracies # NOISE = 1e-6 # # default parameters # scale = 1.0 size = 2.0 xoff = 0.1 yoff = 0.1 boundary = [] toolpath = [] itoolpath = [] HUGE = 1e10 xmin = HUGE xmax = -HUGE ymin = HUGE ymax = -HUGE X = 0 Y = 1 INTERSECT = 2 SEG = 0 VERT = 1 A = 1 TYPE = 0 SIZE = 1 WIDTH = 1 HEIGHT = 2 NVERTS = 10 def coord(str,digits,fraction): # # parse Gerber coordinates # global gerbx, gerby xindex = find(str,"X") yindex = find(str,"Y") index = find(str,"D") if (xindex == -1): x = gerbx y = int(str[(yindex+1):index])*(10**(-fraction)) elif (yindex == -1): y = gerby x = int(str[(xindex+1):index])*(10**(-fraction)) else: x = int(str[(xindex+1):yindex])*(10**(-fraction)) y = int(str[(yindex+1):index])*(10**(-fraction)) gerbx = x gerby = y return [x,y] def read_Gerber(str): # # Gerber parser # segment = -1 xold = [] yold = [] line = 0 nlines = len(str) path = [] apertures = [] macros = [] N_macros = 0 for i in range(1000): apertures.append([]) while line < nlines: if (find(str[line],"%FS") != -1): # # format statement # index = find(str[line],"X") digits = int(str[line][index+1]) fraction = int(str[line][index+2]) line += 1 continue elif (find(str[line],"%AM") != -1): # # aperture macro # index = find(str[line],"%AM") index1 = find(str[line],"*") macros.append([]) macros[-1] = str[line][index+3:index1] N_macros += 1 line += 1 continue elif (find(str[line],"%ADD") != -1): # # aperture definition # index = find(str[line],"%ADD") parse = 0 if (find(str[line],"C,") != -1): # # circle # index = find(str[line],"C,") index1 = find(str[line],"*") aperture = int(str[line][4:index]) size = float(str[line][index+2:index1]) apertures[aperture] = ["C",size] print " read aperture",aperture,": circle diameter",size line += 1 continue elif (find(str[line],"O,") != -1): # # obround # index = find(str[line],"O,") aperture = int(str[line][4:index]) index1 = find(str[line],",",index) index2 = find(str[line],"X",index) index3 = find(str[line],"*",index) width = float(str[line][index1+1:index2]) height = float(str[line][index2+1:index3]) apertures[aperture] = ["O",width,height] print " read aperture",aperture,": obround",width,"x",height line += 1 continue elif (find(str[line],"R,") != -1): # # rectangle # index = find(str[line],"R,") aperture = int(str[line][4:index]) index1 = find(str[line],",",index) index2 = find(str[line],"X",index) index3 = find(str[line],"*",index) width = float(str[line][index1+1:index2]) height = float(str[line][index2+1:index3]) apertures[aperture] = ["R",width,height] print " read aperture",aperture,": rectangle",width,"x",height line += 1 continue for macro in range(N_macros): # # macros # index = find(str[line],macros[macro]+',') if (index != -1): # # hack: assume macros can be approximated by # a circle, and has a size parameter # aperture = int(str[line][4:index]) index1 = find(str[line],",",index) index2 = find(str[line],"*",index) size = float(str[line][index1+1:index2]) apertures[aperture] = ["C",size] print " read aperture",aperture,": macro (assuming circle) diameter",size parse = 1 continue if (parse == 0): print " aperture not implemented:",str[line] return elif (find(str[line],"D") == 0): # # change aperture # index = find(str[line],'*') aperture = int(str[line][1:index]) size = apertures[aperture][SIZE] line += 1 continue elif (find(str[line],"G54D") == 0): # # change aperture # index = find(str[line],'*') aperture = int(str[line][4:index]) size = apertures[aperture][SIZE] line += 1 continue elif (find(str[line],"D01*") != -1): # # pen down # [xnew,ynew] = coord(str[line],digits,fraction) line += 1 if (size > EPS): if ((abs(xnew-xold) > EPS) | (abs(ynew-yold) > EPS)): newpath = stroke(xold,yold,xnew,ynew,size) path.append(newpath) segment += 1 else: path[segment].append([xnew,ynew,[]]) xold = xnew yold = ynew continue elif (find(str[line],"D02*") != -1): # # pen up # [xold,yold] = coord(str[line],digits,fraction) if (size < EPS): path.append([]) segment += 1 path[segment].append([xold,yold,[]]) newpath = [] line += 1 continue elif (find(str[line],"D03*") != -1): # # flash # [xnew,ynew] = coord(str[line],digits,fraction) line += 1 if (apertures[aperture][TYPE] == "C"): # # circle # path.append([]) segment += 1 size = apertures[aperture][SIZE] for i in range(NVERTS): angle = i*2.0*pi/(NVERTS-1.0) x = xnew + (size/2.0)*cos(angle) y = ynew + (size/2.0)*sin(angle) path[segment].append([x,y,[]]) elif (apertures[aperture][TYPE] == "R"): # # rectangle # path.append([]) segment += 1 width = apertures[aperture][WIDTH] / 2.0 height = apertures[aperture][HEIGHT] / 2.0 path[segment].append([xnew-width,ynew-height,[]]) path[segment].append([xnew+width,ynew-height,[]]) path[segment].append([xnew+width,ynew+height,[]]) path[segment].append([xnew-width,ynew+height,[]]) path[segment].append([xnew-width,ynew-height,[]]) elif (apertures[aperture][TYPE] == "O"): # # obround # path.append([]) segment += 1 width = apertures[aperture][WIDTH] height = apertures[aperture][HEIGHT] if (width > height): for i in range(NVERTS/2): angle = i*pi/(NVERTS/2-1.0) + pi/2.0 x = xnew - (width-height)/2.0 + (height/2.0)*cos(angle) y = ynew + (height/2.0)*sin(angle) path[segment].append([x,y,[]]) for i in range(NVERTS/2): angle = i*pi/(NVERTS/2-1.0) - pi/2.0 x = xnew + (width-height)/2.0 + (height/2.0)*cos(angle) y = ynew + (height/2.0)*sin(angle) path[segment].append([x,y,[]]) else: for i in range(NVERTS/2): angle = i*pi/(NVERTS/2-1.0) + pi x = xnew + (width/2.0)*cos(angle) y = ynew - (height-width)/2.0 + (width/2.0)*sin(angle) path[segment].append([x,y,[]]) for i in range(NVERTS/2): angle = i*pi/(NVERTS/2-1.0) x = xnew + (width/2.0)*cos(angle) y = ynew + (height-width)/2.0 + (width/2.0)*sin(angle) path[segment].append([x,y,[]]) x = path[segment][-1][X] y = path[segment][-1][Y] path[segment].append([x,y,[]]) else: print " aperture",apertures[aperture][TYPE],"is not implemented" return xold = xnew yold = ynew continue else: print " not parsed:",str[line] line += 1 return path def read_Excellon(str): # # Excellon parser # segment = -1 line = 0 nlines = len(str) path = [] drills = [] header = TRUE for i in range(1000): drills.append([]) while line < nlines: if ((find(str[line],"T") != -1) & (find(str[line],"C") != -1) \ & (find(str[line],"F") != -1)): # # alternate drill definition style # index = find(str[line],"T") index1 = find(str[line],"C") index2 = find(str[line],"F") drill = int(str[line][1:index1]) print str[line][index1+1:index2] size = float(str[line][index1+1:index2]) drills[drill] = ["C",size] print " read drill",drill,"size:",size line += 1 continue if ((find(str[line],"T") != -1) & (find(str[line]," ") != -1) \ & (find(str[line],"in") != -1)): # # alternate drill definition style # index = find(str[line],"T") index1 = find(str[line]," ") index2 = find(str[line],"in") drill = int(str[line][1:index1]) print str[line][index1+1:index2] size = float(str[line][index1+1:index2]) drills[drill] = ["C",size] print " read drill",drill,"size:",size line += 1 continue elif ((find(str[line],"T") != -1) & (find(str[line],"C") != -1)): # # alternate drill definition style # index = find(str[line],"T") index1 = find(str[line],"C") drill = int(str[line][1:index1]) size = float(str[line][index1+1:-1]) drills[drill] = ["C",size] print " read drill",drill,"size:",size line += 1 continue elif (find(str[line],"T") == 0): # # change drill # index = find(str[line],'T') drill = int(str[line][index+1:-1]) size = drills[drill][SIZE] line += 1 continue elif (find(str[line],"X") != -1): # # drill location # index = find(str[line],"X") index1 = find(str[line],"Y") x0 = float(int(str[line][index+1:index1])/1000.0) y0 = float(int(str[line][index1+1:-1])/1000.0) line += 1 path.append([]) segment += 1 size = drills[drill][SIZE] for i in range(NVERTS): angle = -i*2.0*pi/(NVERTS-1.0) x = x0 + (size/2.0)*cos(angle) y = y0 + (size/2.0)*sin(angle) path[segment].append([x,y,[]]) continue else: print " not parsed:",str[line] line += 1 return path def read_DXF(str): # # DXF parser # segment = -1 path = [] xold = [] yold = [] line = 0 nlines = len(str) polyline = 0 vertex = 0 while line < nlines: if (str[line] == "POLYLINE\n"): segment += 1 polyline = 1 path.append([]) elif (str[line] == "VERTEX\n"): vertex = 1 elif ((strip(str[line]) == "10") & (vertex == 1) & (polyline == 1)): line += 1 x = float(str[line]) elif ((strip(str[line]) == "20") & (vertex == 1) & (polyline == 1)): line += 1 y = float(str[line]) if ((x != xold) | (y != yold)): # # add to path if not zero-length segment # path[segment].append([float(x),float(y),[]]) xold = x yold = y elif (str[line] == "SEQEND\n"): polyline = 0 vertex = 0 line += 1 return path def read(event): global boundary, toolpath, xmin, xmax, ymin, ymax # # read file # text = infile.get() file = open(text, 'r') str = file.readlines() if ((find(text,".cmp") != -1) | (find(text,".sol")!= -1) \ | (find(text,".plc")!= -1)): print "reading Gerber file",text boundary = read_Gerber(str) elif (find(text,".drl") != -1): print "reading Excellon file",text boundary = read_Excellon(str) elif (find(text,".dxf") != -1): print "reading DXF file",text boundary = read_DXF(str) else: print "unsupported file type" return file.close() toolpath = [] sum = 0 for segment in range(len(boundary)): sum += len(boundary[segment]) for vertex in range(len(boundary[segment])): boundary[segment][vertex][X] += gauss(0,NOISE) boundary[segment][vertex][Y] += gauss(0,NOISE) x = boundary[segment][vertex][X] y = boundary[segment][vertex][Y] if (y < ymin): ymin = y if (y > ymax): ymax = y if (x < xmin): xmin = x if (x > xmax): xmax = x boundary[segment][-1][X] = boundary[segment][0][X] boundary[segment][-1][Y] = boundary[segment][0][Y] print " found",len(boundary),"polygons,",sum,"vertices" print " added",NOISE,"perturbation" print " xmin: %0.3g "%xmin,"xmax: %0.3g "%xmax,"ymin: %0.3g "%ymin,"ymax: %0.3g "%ymax plot(event) def stroke(x0,y0,x1,y1,width): # # stroke segment with width # #print "stroke:",x0,y0,x1,y1,width dx = x1 - x0 dy = y1 - y0 d = sqrt(dx*dx + dy*dy) dxpar = dx / d dypar = dy / d dxperp = dypar dyperp = -dxpar dx = -dxperp * width/2.0 dy = -dyperp * width/2.0 angle = pi/(NVERTS/2-1.0) c = cos(angle) s = sin(angle) newpath = [] for i in range(NVERTS/2): newpath.append([x0+dx,y0+dy,0]) [dx,dy] = [c*dx-s*dy, s*dx+c*dy] dx = dxperp * width/2.0 dy = dyperp * width/2.0 for i in range(NVERTS/2): newpath.append([x1+dx,y1+dy,0]) [dx,dy] = [c*dx-s*dy, s*dx+c*dy] x0 = newpath[0][X] y0 = newpath[0][Y] newpath.append([x0,y0,0]) return newpath def plot(event): global boundary, toolpath # # scale and plot boundary and toolpath # size = float(ssize.get()) scale = float(sscale.get()) xoff = float(sxoff.get()) yoff = float(syoff.get()) vert = ivert.get() c.delete("plot_boundary") for seg in range(len(boundary)): path_plot = [] for vertex in range (len(boundary[seg])): xplot = int((boundary[seg][vertex][X]*scale + xoff)*WINDOW/size) path_plot.append(xplot) yplot = WINDOW - int((boundary[seg][vertex][Y]*scale + yoff)*WINDOW/size) path_plot.append(yplot) if (vert == 1): c.create_text(xplot,yplot,text=str(seg)+':'+str(vertex),tag="plot_boundary") c.create_line(path_plot,tag="plot_boundary") c.delete("plot_path") for seg in range(len(toolpath)): path_plot = [] for vertex in range (len(toolpath[seg])): xplot = int((toolpath[seg][vertex][X]*scale + xoff)*WINDOW/size) path_plot.append(xplot) yplot = WINDOW - int((toolpath[seg][vertex][Y]*scale + yoff)*WINDOW/size) path_plot.append(yplot) if (vert == 1): c.create_text(xplot,yplot,text=str(seg)+':'+str(vertex),tag="plot_path") c.create_line(path_plot,tag="plot_path",fill="red") def plot_delete(event): global toolpath # # scale and plot boundary, delete toolpath # toolpath = [] print "delete" plot(event) def intersect(path,seg0,vert0,sega,verta): # # test and return edge intersection # if ((seg0 == sega) & (vert0 == 0) & (verta == (len(path[sega])-2))): #print " return (0-end)" return [[],[]] x0 = path[seg0][vert0][X] y0 = path[seg0][vert0][Y] x1 = path[seg0][vert0+1][X] y1 = path[seg0][vert0+1][Y] dx01 = x1 - x0 dy01 = y1 - y0 d01 = sqrt(dx01*dx01 + dy01*dy01) if (d01 == 0): # # zero-length segment, return no intersection # #print "zero-length segment" return [[],[]] dxpar01 = dx01 / d01 dypar01 = dy01 / d01 dxperp01 = dypar01 dyperp01 = -dxpar01 xa = path[sega][verta][X] ya = path[sega][verta][Y] xb = path[sega][verta+1][X] yb = path[sega][verta+1][Y] dx0a = xa - x0 dy0a = ya - y0 dpar0a = dx0a*dxpar01 + dy0a*dypar01 dperp0a = dx0a*dxperp01 + dy0a*dyperp01 dx0b = xb - x0 dy0b = yb - y0 dpar0b = dx0b*dxpar01 + dy0b*dypar01 dperp0b = dx0b*dxperp01 + dy0b*dyperp01 #if (dperp0a*dperp0b > EPS): if (((dperp0a > EPS) & (dperp0b > EPS)) | \ ((dperp0a < -EPS) & (dperp0b < -EPS))): # # vertices on same side, return no intersection # #print " same side" return [[],[]] elif ((abs(dperp0a) < EPS) & (abs(dperp0b) < EPS)): # # edges colinear, return no intersection # #d0a = (xa-x0)*dxpar01 + (ya-y0)*dypar01 #d0b = (xb-x0)*dxpar01 + (yb-y0)*dypar01 #print " colinear" return [[],[]] # # calculation distance to intersection # d = (dpar0a*abs(dperp0b)+dpar0b*abs(dperp0a))/(abs(dperp0a)+abs(dperp0b)) if ((d < -EPS) | (d > (d01+EPS))): # # intersection outside segment, return no intersection # #print " found intersection outside segment" return [[],[]] else: # # intersection in segment, return intersection # #print " found intersection in segment s0 v0 sa va",seg0,vert0,sega,verta xloc = x0 + dxpar01*d yloc = y0 + dypar01*d return [xloc,yloc] def union(i,path,intersections,sign): # # return edge to exit intersection i for a union # #print "union: intersection",i,"in",intersections seg0 = intersections[i][0][SEG] #print "seg0",seg0 vert0 = intersections[i][0][VERT] x0 = path[seg0][vert0][X] y0 = path[seg0][vert0][Y] if (vert0 < (len(path[seg0])-1)): vert1 = vert0 + 1 else: vert1 = 0 x1 = path[seg0][vert1][X] y1 = path[seg0][vert1][Y] dx01 = x1-x0 dy01 = y1-y0 sega = intersections[i][A][SEG] verta = intersections[i][A][VERT] xa = path[sega][verta][X] ya = path[sega][verta][Y] if (verta < (len(path[sega])-1)): vertb = verta + 1 else: vertb = 0 xb = path[sega][vertb][X] yb = path[sega][vertb][Y] dxab = xb-xa dyab = yb-ya dot = dxab*dy01 - dyab*dx01 #print " dot",dot if (abs(dot) <= EPS): print " colinear" seg = [] vert= [] elif (dot > EPS): seg = intersections[i][(1-sign)/2][SEG] vert = intersections[i][(1-sign)/2][VERT] else: seg = intersections[i][(1+sign)/2][SEG] vert = intersections[i][(1+sign)/2][VERT] return [seg,vert] def insert(path,x,y,seg,vert,intersection): # # insert a vertex at x,y in seg,vert, if needed # d0 = (path[seg][vert][X]-x)**2 + (path[seg][vert][Y]-y)**2 d1 = (path[seg][vert+1][X]-x)**2 + (path[seg][vert+1][Y]-y)**2 #print "check insert seg",seg,"vert",vert,"intersection",intersection if ((d0 > EPS) & (d1 > EPS)): #print " added intersection vertex",vert+1 path[seg].insert((vert+1),[x,y,intersection]) return 1 elif (d0 < EPS): if (path[seg][vert][INTERSECT] == []): path[seg][vert][INTERSECT] = intersection #print " added d0",vert return 0 elif (d1 < EPS): if (path[seg][vert+1][INTERSECT] == []): path[seg][vert+1][INTERSECT] = intersection #print " added d1",vert+1 return 0 else: #print " shouldn't happen: d0",d0,"d1",d1 return 0 def add_intersections(path): # # add vertices at path intersections # intersection = 0 # # loop over first edge # for seg0 in range(len(path)): status.set(" segment "+str(seg0)+"/"+str(len(path)-1)+" ") outframe.update() vert0 = 0 N0 = len(path[seg0])-1 while (vert0 < N0): # # loop over second edge # vert1 = vert0 + 2 while (vert1 < N0): # # check for path self-intersection # [xloc,yloc] = intersect(path,seg0,vert0,seg0,vert1) if (xloc != []): # # found intersection, insert vertices # n0 = insert(path,xloc,yloc,seg0,vert0,intersection) N0 += n0 vert1 += n0 n1 = insert(path,xloc,yloc,seg0,vert1,intersection) N0 += n1 vert1 += n1 if ((n0 > 0) | (n1 > 0)): intersection += 1 vert1 += 1 for sega in range((seg0+1),len(path)): # # check for intersection with other parts # outframe.update() verta = 0 Na = len(path[sega])-1 while (verta < Na): [xloc,yloc] = intersect(path,seg0,vert0,sega,verta) if (xloc != []): # # found intersection, insert vertices # n0 = insert(path,xloc,yloc,seg0,vert0,intersection) N0 += n0 vert1 += n0 na = insert(path,xloc,yloc,sega,verta,intersection) Na += na verta += na if ((n0 > 0) | (na > 0)): intersection += 1 verta += 1 vert0 += 1 # # make vertex table and segment list of intersections # status.set(namedate) outframe.update() intersections = [] for i in range(intersection): intersections.append([]) for seg in range(len(path)): for vert in range(len(path[seg])): intersection = path[seg][vert][INTERSECT] if (intersection != []): intersections[intersection].append([seg,vert]) #print ' found',len(intersections),'intersection(s)' seg_intersections = [] for i in range(len(path)): seg_intersections.append([]) for i in range(len(intersections)): if (len(intersections[i]) != 2): print " shouldn't happen: i",i,intersections[i] else: seg_intersections[intersections[i][0][SEG]].append(i) seg_intersections[intersections[i][A][SEG]].append(i) return [path, intersections, seg_intersections] def offset(x0,x1,x2,y0,y1,y2,r): # # calculate offset by r for vertex 1 # dx0 = x1 - x0 dx1 = x2 - x1 dy0 = y1 - y0 dy1 = y2 - y1 d0 = sqrt(dx0*dx0 + dy0*dy0) d1 = sqrt(dx1*dx1 + dy1*dy1) if ((d0 == 0) | (d1 == 0)): return [[],[]] dx0par = dx0 / d0 dy0par = dy0 / d0 dx0perp = dy0 / d0 dy0perp = -dx0 / d0 dx1perp = dy1 / d1 dy1perp = -dx1 / d1 #print "offset points:",x0,x1,x2,y0,y1,y2 #print "offset normals:",dx0perp,dx1perp,dy0perp,dy1perp if ((abs(dx0perp*dy1perp - dx1perp*dy0perp) < EPS) | \ (abs(dy0perp*dx1perp - dy1perp*dx0perp) < EPS)): dx = r * dx1perp dy = r * dy1perp #print " offset planar:",dx,dy elif ((abs(dx0perp+dx1perp) < EPS) & (abs(dy0perp+dy1perp) < EPS)): dx = r * dx1par dy = r * dy1par #print " offset hairpin:",dx,dy else: dx = r*(dy1perp - dy0perp) / \ (dx0perp*dy1perp - dx1perp*dy0perp) dy = r*(dx1perp - dx0perp) / \ (dy0perp*dx1perp - dy1perp*dx0perp) #print " offset OK:",dx,dy return [dx,dy] def displace(path): # # displace path inwards by tool radius # newpath = [] scale = float(sscale.get()) undercut = float(sundercut.get()) toolrad =(float(sdia.get())/2.0-undercut)/scale for seg in range(len(path)): newpath.append([]) if (len(path[seg]) > 2): for vert1 in range(len(path[seg])-1): if (vert1 == 0): vert0 = len(path[seg]) - 2 else: vert0 = vert1 - 1 vert2 = vert1 + 1 x0 = path[seg][vert0][X] x1 = path[seg][vert1][X] x2 = path[seg][vert2][X] y0 = path[seg][vert0][Y] y1 = path[seg][vert1][Y] y2 = path[seg][vert2][Y] [dx,dy] = offset(x0,x1,x2,y0,y1,y2,toolrad) if (dx != []): newpath[seg].append([(x1+dx),(y1+dy),[]]) x0 = newpath[seg][0][X] y0 = newpath[seg][0][Y] newpath[seg].append([x0,y0,[]]) elif (len(path[seg]) == 2): x0 = path[seg][0][X] y0 = path[seg][0][Y] x1 = path[seg][1][X] y1 = path[seg][1][Y] x2 = 2*x1 - x0 y2 = 2*y1 - y0 [dx,dy] = offset(x0,x1,x2,y0,y1,y2,toolrad) if (dx != []): newpath[seg].append([x0+dx,y0+dy,[]]) newpath[seg].append([x1+dx,y1+dy,[]]) else: newpath[seg].append([x0,y0,[]]) newpath[seg].append([x1,y1,[]]) else: print " displace: shouldn't happen" return newpath def prune(path,sign,event): # # prune path intersections # # first find the intersections # print " intersecting ..." #plot_path(event) #raw_input('before intersection') [path, intersections, seg_intersections] = add_intersections(path) #print 'path:',path #print 'intersections:',intersections #print 'seg_intersections:',seg_intersections #plot_boundary(event) #plot_path(event) #raw_input('after intersection') print "intersected" # # then copy non-intersecting segments to new path # newpath = [] for seg in range(len(seg_intersections)): if (seg_intersections[seg] == []): newpath.append(path[seg]) # # finally follow and remove the intersections # print " pruning ..." i = 0 newseg = 0 while (i < len(intersections)): if (intersections[i] == []): # # skip null intersections # i += 1 else: istart = i intersection = istart # # skip interior intersections # oldseg = -1 interior = TRUE while 1: #print 'testing intersection',intersection,':',intersections[intersection] if (intersections[intersection] == []): seg == oldseg else: [seg,vert] = union(intersection,path,intersections,sign) #print ' seg',seg,'vert',vert,'oldseg',oldseg if (seg == oldseg): #print " remove interior intersection",istart seg0 = intersections[istart][0][SEG] vert0 = intersections[istart][0][VERT] path[seg0][vert0][INTERSECT] = -1 seg1 = intersections[istart][1][SEG] vert1 = intersections[istart][1][VERT] path[seg1][vert1][INTERSECT] = -1 intersections[istart] = [] break elif (seg == []): seg = intersections[intersection][0][SEG] vert = intersections[intersection][0][SEG] oldseg = [] else: oldseg = seg intersection = [] while (intersection == []): if (vert < (len(path[seg])-1)): vert += 1 else: vert = 0 intersection = path[seg][vert][INTERSECT] if (intersection == -1): intersection = istart break elif (intersection == istart): #print ' back to',istart interior = FALSE intersection = istart break # # save path if valid boundary intersection # if (interior == FALSE): newseg = len(newpath) newpath.append([]) while 1: #print 'keeping intersection',intersection,':',intersections[intersection] [seg,vert] = union(intersection,path,intersections,sign) if (seg == []): seg = intersections[intersection][0][SEG] vert = intersections[intersection][0][VERT] #print ' seg',seg,'vert',vert intersections[intersection] = [] intersection = [] while (intersection == []): if (vert < (len(path[seg])-1)): x = path[seg][vert][X] y = path[seg][vert][Y] newpath[newseg].append([x,y,[]]) vert += 1 else: vert = 0 intersection = path[seg][vert][INTERSECT] if (intersection == istart): #print ' back to',istart x = path[seg][vert][X] y = path[seg][vert][Y] newpath[newseg].append([x,y,[]]) break i += 1 return newpath def union_boundary(event): global boundary, intersections # # union intersecting polygons on boundary # print "union boundary ..." sign = 1 boundary = prune(boundary,sign,event) print " done" plot(event) def contour_boundary(event): global boundary, toolpath # # contour boundary to find toolpath # print "contouring boundary ..." undercut = float(sundercut.get()) if (undercut != 0.0): print " undercutting contour by",undercut # # displace vertices inward by tool size # print " displacing ..." toolpath = displace(boundary) #plot_path(event) #raw_input('displaced') sign = -1 toolpath = prune(toolpath,sign,event) plot(event) print " done" def raster(event): global boundary, toolpath, ymin, ymax # # raster interior # print "rastering interior ..." scale = float(sscale.get()) tooldia = float(sdia.get())/scale overlap = float(soverlap.get()) if (toolpath == []): edgepath = boundary delta = tooldia/2.0 else: edgepath = toolpath delta = tooldia/4.0 # # find row-edge intersections # edges = [] dymin = ymin - 2*tooldia*overlap dymax = ymax + 2*tooldia*overlap row1 = int(floor((dymax-dymin)/(tooldia*overlap))) for row in range(row1+1): edges.append([]) for seg in range(len(edgepath)): for vertex in range(len(edgepath[seg])-1): x0 = edgepath[seg][vertex][X] y0 = edgepath[seg][vertex][Y] x1 = edgepath[seg][vertex+1][X] y1 = edgepath[seg][vertex+1][Y] if (y1 == y0): continue elif (y1 < y0): x0, x1 = x1, x0 y0, y1 = y1, y0 row0 = int(ceil((y0 - dymin)/(tooldia*overlap))) row1 = int(floor((y1 - dymin)/(tooldia*overlap))) for row in range(row0,(row1+1)): y = dymin + row*tooldia*overlap x = x0*(y1-y)/(y1-y0) + x1*(y-y0)/(y1-y0) edges[row].append(x) for row in range(len(edges)): edges[row].sort() y = dymin + row*tooldia*overlap edge = 0 while edge < len(edges[row]): x0 = edges[row][edge] + delta edge += 1 if (edge < len(edges[row])): x1 = edges[row][edge] - delta else: print "shouldn't happen: row",row,"length",len(edges[row]) break edge += 1 if (x0 < x1): toolpath.append([]) toolpath[-1].append([x0,y,[]]) toolpath[-1].append([x1,y,[]]) plot(event) print " done" def write_RML(path): # # RML (Modela-style HPGL) output # units = 1000 scale = float(sscale.get()) xoff = float(sxoff.get()) yoff = float(syoff.get()) text = outfile.get() izup = int(units*float(szup.get())) izdown = int(units*float(szdown.get())) file = open(text, 'w') file.write("PA;PA;!PZ"+str(izdown)+","+str(izup)+";") file.write("VS"+sxyvel.get()+";!VZ"+szvel.get()+";!MC1;") for segment in range(len(path)): vertex = 0 x = int(units*(path[segment][vertex][X]*scale + xoff)) y = int(units*(path[segment][vertex][Y]*scale + yoff)) file.write("PU"+str(x)+","+str(y)+";") for vertex in range(1,len(path[segment])): x = int(units*(path[segment][vertex][X]*scale + xoff)) y = int(units*(path[segment][vertex][Y]*scale + yoff)) file.write("PD"+str(x)+","+str(y)+";") #file.write("PU5000,5000;!MC0;") file.write("PU"+str(x)+","+str(y)+";!MC0;") file.close() print "wrote",len(path),"RML toolpath segments to",text def write_CAMM(path): # # CAMM (CAMM-style cutter HPGL) output # units = 1000 scale = float(sscale.get()) xoff = float(sxoff.get()) yoff = float(syoff.get()) text = outfile.get() izup = int(units*float(szup.get())) izdown = int(units*float(szdown.get())) file = open(text, 'w') file.write("PA;PA;!ST1;!FS"+sforce.get()+";VS"+svel.get()+";") for segment in range(len(path)): vertex = 0 x = int(units*(path[segment][vertex][X]*scale + xoff)) y = int(units*(path[segment][vertex][Y]*scale + yoff)) file.write("PU"+str(x)+","+str(y)+";") for vertex in range(1,len(path[segment])): x = int(units*(path[segment][vertex][X]*scale + xoff)) y = int(units*(path[segment][vertex][Y]*scale + yoff)) file.write("PD"+str(x)+","+str(y)+";") file.write("PU0,0;") file.close() print "wrote",len(path),"CAMM toolpath segments to",text def write_EPI(path): # # Epilog lasercutter output # units = 1000 scale = float(sscale.get()) xoff = float(sxoff.get()) yoff = float(syoff.get()) text = outfile.get() file = open(text, 'w') file.write("%-12345X@PJL JOB NAME=Graphic1\r\nE@PJL ENTER LANGUAGE=PCL\r\n&y1A&l0U&l0Z&u600D*p0X*p0Y*t600R*r0F&y50P&z50S*r6600T*r5100S*r1A*rC%1BIN;XR"+srate.get()+";YP"+spower.get()+";ZS"+sspeed.get()+";") for segment in range(len(path)): vertex = 0 x = int(units*(path[segment][vertex][X]*scale + xoff)) y = int(units*(path[segment][vertex][Y]*scale + yoff)) file.write("PU"+str(x)+","+str(y)+";") for vertex in range(1,len(path[segment])): x = int(units*(path[segment][vertex][X]*scale + xoff)) y = int(units*(path[segment][vertex][Y]*scale + yoff)) file.write("PD"+str(x)+","+str(y)+";") file.write("%0B%1BPUE%-12345X@PJL EOJ \r\n") file.close() print "wrote",len(path),"Epilog toolpath segments to",text def write_G(path): # # G code output # scale = float(sscale.get()) xoff = float(sxoff.get()) yoff = float(syoff.get()) text = outfile.get() file = open(text, 'w') file.write("G90\n") # absolute positioning file.write("F"+sfeed.get()+"\n") # feed rate file.write("S"+sspindle.get()+"\n") # spindle speed file.write("T"+stool.get()+"\n") # tool file.write("M08\n") # coolant on file.write("M03\n") # spindle on clockwise for segment in range(len(path)): vertex = 0 x = path[segment][vertex][X]*scale + xoff y = path[segment][vertex][Y]*scale + yoff file.write("G00X%0.4f"%x+"Y%0.4f"%y+"Z"+sztop.get()+"\n") # rapid motion file.write("G01Z"+szbottom.get()+"\n") # linear motion for vertex in range(1,len(path[segment])): x = path[segment][vertex][X]*scale + xoff y = path[segment][vertex][Y]*scale + yoff file.write("X%0.4f"%x+"Y%0.4f"%y+"\n") file.write("Z"+sztop.get()+"\n") file.write("M05\n") # spindle stop file.write("M09\n") # coolant off file.write("M30\n") # program end and reset file.close() print "wrote",len(path),"G code toolpath segments to",text def write_img(path): # # bitmap image output # scale = float(sscale.get()) size = float(ssize.get()) xoff = float(sxoff.get()) yoff = float(syoff.get()) text = outfile.get() ximg = int(sximg.get()) yimg = int(syimg.get()) image = Image.new("RGB",[ximg,yimg],(0,0,0)) draw = ImageDraw.Draw(image) for segment in range(len(path)): vertex = 0 x0 = int((path[segment][vertex][X]*scale + xoff)*ximg/size) y0 = yimg - int((path[segment][vertex][Y]*scale + yoff)*yimg/size) for vertex in range(1,len(path[segment])): x1 = int((path[segment][vertex][X]*scale + xoff)*ximg/size) y1 = yimg - int((path[segment][vertex][Y]*scale + yoff)*yimg/size) draw.line([(x0,y0),(x1,y1)],(255,255,255)) [x0,y0] = [x1,y1] image.save(text) print "wrote",len(path),"toolpath segments to image",text def write(event): global toolpath, boundary, xmin, xmax, ymin, ymax # # write toolpath # if (toolpath == []): toolpath = boundary text = outfile.get() if (find(text,".rml") != -1): write_RML(toolpath) elif (find(text,".camm") != -1): write_CAMM(toolpath) elif (find(text,".epi") != -1): write_EPI(toolpath) elif (find(text,".g") != -1): write_G(toolpath) elif ((find(text,".jpg") != -1) | (find(text,".bmp") != -1)): write_img(toolpath) else: print "unsupported output file format" return sxmin = scale+xmin + xoff sxmax = scale+xmax + xoff symin = scale+ymin + yoff symax = scale+ymax + yoff print " xmin: %0.3g "%sxmin,"xmax: %0.3g "%sxmax,"ymin: %0.3g "%symin,"ymax: %0.3g "%symax def delframes(): # # delete all CAM frames # cutframe.pack_forget() imgframe.pack_forget() toolframe.pack_forget() millframe.pack_forget() gframe.pack_forget() laserframe.pack_forget() def camselect(event): global size # # pack appropriate CAM GUI options based on output file # text = outfile.get() if (find(text,".rml") != -1): delframes() sdia.set("0.015") sundercut.set("0.00") soverlap.set("0.8") toolframe.pack() szup.set("0.04") szdown.set("-0.015") sxyvel.set("2") szvel.set("5") millframe.pack() elif (find(text,".camm") != -1): delframes() sforce.set("70") svel.set("2") cutframe.pack() elif (find(text,".epi") != -1): delframes() srate.set("2500") spower.set("50") sspeed.set("50") ssize.set("10") laserframe.pack() plot(event) elif (find(text,".g") != -1): delframes() sdia.set("0.015") sundercut.set("0.00") soverlap.set("0.8") toolframe.pack() sztop.set("1") szbottom.set("0") sfeed.set("5") sspindle.set("5000") stool.set("1") gframe.pack() elif ((find(text,".jpg") != -1) | (find(text,".bmp") != -1)): delframes() sdia.set("0.015") sundercut.set("0.00") soverlap.set("0.8") toolframe.pack() sximg.set("500") syimg.set("500") imgframe.pack() else: print "output file format not supported" return root = Tk() root.title('cam.py') root.bind('q','exit') infile = StringVar() outfile = StringVar() if (len(sys.argv) >= 2): infile.set(sys.argv[1]) else: infile.set('') if (len(sys.argv) >= 4): xoff = float(sys.argv[2]) yoff = float(sys.argv[3]) if (len(sys.argv) >= 5): size = float(sys.argv[4]) if (len(sys.argv) >= 6): outfile.set(sys.argv[5]) else: outfile.set('out.rml') if (len(sys.argv) >= 7): undercut = float(sys.argv[6]) inframe = Frame(root) Label(inframe, text="input file: ").pack(side="left") winfile = Entry(inframe, width=20, textvariable=infile) winfile.pack(side="left") winfile.bind('',read) ssize = StringVar() ssize.set(str(size)) Label(inframe, text=" ").pack(side="left") Label(inframe, text="display size:").pack(side="left") wsize = Entry(inframe, width=10, textvariable=ssize) wsize.pack(side="left") wsize.bind('',plot) Label(inframe, text=" ").pack(side="left") ivert = IntVar() wvert = Checkbutton(inframe, text="show vertices", variable=ivert) wvert.pack(side="left") wvert.bind('',plot) inframe.pack() coordframe = Frame(root) sxoff = StringVar() sxoff.set(str(xoff)) syoff = StringVar() syoff.set(str(yoff)) sscale = StringVar() sscale.set(str(scale)) Label(coordframe, text="x offset:").pack(side="left") wxoff = Entry(coordframe, width=10, textvariable=sxoff) wxoff.pack(side="left") wxoff.bind('',plot) Label(coordframe, text=" y offset:").pack(side="left") wyoff = Entry(coordframe, width=10, textvariable=syoff) wyoff.pack(side="left") wyoff.bind('',plot) Label(coordframe, text=" part scale factor:").pack(side="left") wscale = Entry(coordframe, width=10, textvariable=sscale) wscale.pack(side="left") wscale.bind('',plot_delete) coordframe.pack() c = Canvas(root, width=WINDOW, height=WINDOW, background='white') c.pack() outframe = Frame(root) Logo = Canvas(outframe, width=26, height=26, background="white") Logo.create_oval(2,2,8,8,fill="red",outline="") Logo.create_rectangle(11,2,17,8,fill="blue",outline="") Logo.create_rectangle(20,2,26,8,fill="blue",outline="") Logo.create_rectangle(2,11,8,17,fill="blue",outline="") Logo.create_oval(10,10,16,16,fill="red",outline="") Logo.create_rectangle(20,11,26,17,fill="blue",outline="") Logo.create_rectangle(2,20,8,26,fill="blue",outline="") Logo.create_rectangle(11,20,17,26,fill="blue",outline="") Logo.create_rectangle(20,20,26,26,fill="blue",outline="") Logo.pack(side="left") status = StringVar() namedate = " cam.py ("+DATE+") " status.set(namedate) Label(outframe, textvariable=status).pack(side="left") Label(outframe, text="output file: ").pack(side="left") woutfile = Entry(outframe, width=20, textvariable=outfile) woutfile.bind('',camselect) woutfile.pack(side="left") Label(outframe, text=" ").pack(side="left") Button(outframe, text="quit", command='exit').pack(side="left") Label(outframe, text=" ").pack(side="left") outframe.pack() camframe = Frame(root) unionbtn = Button(camframe, text="union polygons") unionbtn.bind('',union_boundary) unionbtn.pack(side="left") Label(camframe, text=" ").pack(side="left") contourbtn = Button(camframe, text="contour boundary") contourbtn.bind('',contour_boundary) contourbtn.pack(side="left") Label(camframe, text=" ").pack(side="left") rasterbtn = Button(camframe, text="raster interior") rasterbtn.bind('',raster) rasterbtn.pack(side="left") Label(camframe, text=" ").pack(side="left") writebtn = Button(camframe, text="write toolpath") writebtn.bind('',write) writebtn.pack(side="left") camframe.pack() toolframe = Frame(root) Label(toolframe, text="tool diameter: ").pack(side="left") sdia = StringVar() wtooldia = Entry(toolframe, width=10, textvariable=sdia) wtooldia.pack(side="left") wtooldia.bind('',plot_delete) Label(toolframe, text=" contour undercut: ").pack(side="left") sundercut = StringVar() wundercut = Entry(toolframe, width=10, textvariable=sundercut) wundercut.pack(side="left") wundercut.bind('',plot_delete) Label(toolframe, text=" raster overlap: ").pack(side="left") soverlap = StringVar() woverlap = Entry(toolframe, width=10, textvariable=soverlap) woverlap.pack(side="left") woverlap.bind('',plot_delete) millframe = Frame(root) Label(millframe, text="z up:").pack(side="left") szup = StringVar() Entry(millframe, width=10, textvariable=szup).pack(side="left") Label(millframe, text=" z down:").pack(side="left") szdown = StringVar() Entry(millframe, width=10, textvariable=szdown).pack(side="left") Label(millframe, text=" xy speed:").pack(side="left") sxyvel = StringVar() Entry(millframe, width=10, textvariable=sxyvel).pack(side="left") Label(millframe, text=" z speed:").pack(side="left") szvel = StringVar() Entry(millframe, width=10, textvariable=szvel).pack(side="left") gframe = Frame(root) Label(gframe, text="z top:").pack(side="left") sztop = StringVar() Entry(gframe, width=6, textvariable=sztop).pack(side="left") Label(gframe, text=" z bottom:").pack(side="left") szbottom = StringVar() Entry(gframe, width=6, textvariable=szbottom).pack(side="left") Label(gframe, text=" feed rate:").pack(side="left") sfeed = StringVar() Entry(gframe, width=6, textvariable=sfeed).pack(side="left") Label(gframe, text=" spindle speed:").pack(side="left") sspindle = StringVar() Entry(gframe, width=6, textvariable=sspindle).pack(side="left") Label(gframe, text=" tool:").pack(side="left") stool = StringVar() Entry(gframe, width=3, textvariable=stool).pack(side="left") cutframe = Frame(root) Label(cutframe, text="force: ").pack(side="left") sforce = StringVar() Entry(cutframe, width=10, textvariable=sforce).pack(side="left") Label(cutframe, text=" velocity:").pack(side="left") svel = StringVar() Entry(cutframe, width=10, textvariable=svel).pack(side="left") laserframe = Frame(root) Label(laserframe, text="rate: ").pack(side="left") srate = StringVar() Entry(laserframe, width=10, textvariable=srate).pack(side="left") Label(laserframe, text=" power:").pack(side="left") spower = StringVar() Entry(laserframe, width=10, textvariable=spower).pack(side="left") Label(laserframe, text=" speed:").pack(side="left") sspeed = StringVar() Entry(laserframe, width=10, textvariable=sspeed).pack(side="left") imgframe = Frame(root) Label(imgframe, text="x size (pixels): ").pack(side="left") sximg = StringVar() Entry(imgframe, width=10, textvariable=sximg).pack(side="left") Label(imgframe, text=" y size (pixels):").pack(side="left") syimg = StringVar() Entry(imgframe, width=10, textvariable=syimg).pack(side="left") camselect(0) if (len(infile.get()) != 0): read(0) root.mainloop()