cyclone-pcb-factory/Software/GcodeGenerators/cam.py

1493 lines
46 KiB
Python
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

#
# 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()