Skip to content

Instantly share code, notes, and snippets.

@shohei
Created April 25, 2013 06:32
Show Gist options
  • Save shohei/5457915 to your computer and use it in GitHub Desktop.
Save shohei/5457915 to your computer and use it in GitHub Desktop.
from MIT
#
# 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('<Return>',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('<Return>',plot)
Label(inframe, text=" ").pack(side="left")
ivert = IntVar()
wvert = Checkbutton(inframe, text="show vertices", variable=ivert)
wvert.pack(side="left")
wvert.bind('<ButtonRelease-1>',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('<Return>',plot)
Label(coordframe, text=" y offset:").pack(side="left")
wyoff = Entry(coordframe, width=10, textvariable=syoff)
wyoff.pack(side="left")
wyoff.bind('<Return>',plot)
Label(coordframe, text=" part scale factor:").pack(side="left")
wscale = Entry(coordframe, width=10, textvariable=sscale)
wscale.pack(side="left")
wscale.bind('<Return>',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('<Return>',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('<Button-1>',union_boundary)
unionbtn.pack(side="left")
Label(camframe, text=" ").pack(side="left")
contourbtn = Button(camframe, text="contour boundary")
contourbtn.bind('<Button-1>',contour_boundary)
contourbtn.pack(side="left")
Label(camframe, text=" ").pack(side="left")
rasterbtn = Button(camframe, text="raster interior")
rasterbtn.bind('<Button-1>',raster)
rasterbtn.pack(side="left")
Label(camframe, text=" ").pack(side="left")
writebtn = Button(camframe, text="write toolpath")
writebtn.bind('<Button-1>',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('<Return>',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('<Return>',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('<Return>',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()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment