#!/usr/bin/env python
#
# cad.py
#
# Neil Gershenfeld
#
# (c) Massachusetts Institute of Technology 2006
# Permission granted for experimental and personal use;
# license for commercial sale available from MIT.
#
# todo:
#
# rule table errors, handling
# test/debug vectorization steps, straight lines, N/S direction
# switch to grid layout manager
# fix rot view refresh
# eps output
# check vector stroke before/after error threshold
# bitmap laser output
# image drawing tools
# compare 4x CW<->CCW, inner<->outer orders
# xz, yz finish cut contours
# test camm, epi, ord to absolute units otuput
# sort toolpath starts for proximity
# add autoscale output button
# include polygons
# overload operators
# preserve z aspect ratio
# clean up nesting of parens
# send-to direct output
# 3D text primitives
# bit depth for color
# parse .cad file units
#
DATE = "11/22/06a"

from numarray import *
from numarray.convolve import *
from string import *
from Tkinter import *
from tkFileDialog import *
import Image, ImageTk, ImageDraw, ImageFont, ImageOps

class point:
   #
   # an xyz point
   #
   def __init__(self,x,y,z=0):
      self.x = x
      self.y = y
      self.z = z

class cad_variables:
   #
   # cad variables
   #
   def __init__(self):
      self.xmin = 0 # minimum x value to render
      self.xmax = 0 # maximum x value to render
      self.ymin = 0 # minimum y value to render
      self.ymax = 0 # maximum y value to render
      self.zmin = 0 # minimum z value to render
      self.zmax = 0 # maximum z value to render
      self.zlist = [] # z values to render
      self.nx = 0 # number of x points to render
      self.ny = 0 # number of y points to render
      self.nz = 1 # number of z points to render
      self.rz = 0 # perspective view z rotation (degrees)
      self.rx = 0 # perspective view x rotation (degrees)
      self.units = 'in' # file units
      self.depth = 1 # bit depth
      self.function = '0' # cad function
      self.paths = [] # toolpaths
      self.labels = [] # display labels
      self.image = array(0) # image array
      self.image_min = 0 # image min value
      self.image_max = 0 # image max value
      self.stop = 0 # stop rendering
      self.nplot = 400 # plot window size
   def nxplot(self):
      xwidth = self.xmax - self.xmin
      ywidth = self.ymax - self.ymin
      if (xwidth >= ywidth):
         n = self.nplot
      else:
         n = int(self.nplot*xwidth/float(ywidth))
      return n
   def nyplot(self):
      xwidth = self.xmax - self.xmin
      ywidth = self.ymax - self.ymin
      if (xwidth < ywidth):
         n = self.nplot
      else:
         n = int(self.nplot*ywidth/float(xwidth))
      return n
   def nzplot(self):
      xwidth = self.xmax - self.xmin
      zwidth = self.zmax - self.zmin
      n = int(self.nxplot()*zwidth/float(xwidth))
      return n

cad = cad_variables()

class cad_text:
   def __init__(self,x,y,z,text,size=10):
      self.x = x
      self.y = y
      self.z = z
      self.text = text
      self.size = size

class images_class:
   def __init__(self):
      self.xy = 0
      self.xz = 0
      self.yz = 0
      self.rot = 0

images = images_class()

class CA_states:
   #
   # CA state definition class
   #
   def __init__(self):
      self.empty = 0
      self.interior = 1
      self.edge = (1 << 1)
      self.north = (1 << 2)
      self.west = (2 << 2)
      self.east = (3 << 2)
      self.south = (4 << 2)
      self.stop = (5 << 2)

class rule_table:
   #
   # CA rule table class
   #
   def __init__(self):
      self.table = zeros(2**(9*2))
      self.s = CA_states()
      #
      # 1 0:
      #
      # 011
      # 111
      # 111
      self.add_rule(0,1,1,1,1,1,1,1,1,self.s.north)
      # 101
      # 111
      # 111
      self.add_rule(1,0,1,1,1,1,1,1,1,self.s.east)
      #
      # 2 0's:
      #
      # 001
      # 111
      # 111
      self.add_rule(0,0,1,1,1,1,1,1,1,self.s.east)
      # 100
      # 111
      # 111
      self.add_rule(1,0,0,1,1,1,1,1,1,self.s.east)
      # 010
      # 111
      # 111
      self.add_rule(0,1,0,1,1,1,1,1,1,self.s.east)
      # 011
      # 110
      # 111
      self.add_rule(0,1,1,1,1,0,1,1,1,self.s.south)
      # 110
      # 011
      # 111
      self.add_rule(1,1,0,0,1,1,1,1,1,self.s.east)
      # 011
      # 111
      # 110
      self.add_rule(0,1,1,1,1,1,1,1,0,self.s.north)
      # 011
      # 111
      # 101
      self.add_rule(0,1,1,1,1,1,1,0,1,self.s.north)
      # 110
      # 111
      # 101
      self.add_rule(1,1,0,1,1,1,1,0,1,self.s.west)
      # 111
      # 010
      # 111
      self.add_rule(1,1,1,0,1,0,1,1,1,self.s.stop)
      #
      # 3 0's:
      #
      # 001
      # 011
      # 111
      self.add_rule(0,0,1,0,1,1,1,1,1,self.s.east)
      # 010
      # 011
      # 111
      self.add_rule(0,1,0,0,1,1,1,1,1,self.s.east)
      # 010
      # 110
      # 111
      self.add_rule(0,1,0,1,1,0,1,1,1,self.s.south)
      # 001
      # 111
      # 011
      self.add_rule(0,0,1,1,1,1,0,1,1,self.s.east)
      # 100
      # 111
      # 110
      self.add_rule(1,0,0,1,1,1,1,1,0,self.s.south)
      # 011
      # 110
      # 011
      self.add_rule(0,1,1,1,1,0,0,1,1,self.s.south)
      # 011
      # 011
      # 011
      self.add_rule(0,1,1,0,1,1,0,1,1,self.s.north)
      # 010
      # 111
      # 011
      self.add_rule(0,1,0,1,1,1,0,1,1,self.s.east)
      # 010
      # 111
      # 110
      self.add_rule(0,1,0,1,1,1,1,1,0,self.s.south)
      # 011
      # 010
      # 111
      self.add_rule(0,1,1,0,1,0,1,1,1,self.s.stop)
      # 101
      # 010
      # 111
      self.add_rule(1,0,1,0,1,0,1,1,1,self.s.stop)
      # 110
      # 010
      # 111
      self.add_rule(1,1,0,0,1,0,1,1,1,self.s.stop)
      #
      # 4 0's:
      #
      # 001
      # 011
      # 011
      self.add_rule(0,0,1,0,1,1,0,1,1,self.s.east)
      # 100
      # 110
      # 110
      self.add_rule(1,0,0,1,1,0,1,1,0,self.s.south)
      # 010
      # 011
      # 011
      self.add_rule(0,1,0,1,1,0,1,1,0,self.s.east)
      # 010
      # 110
      # 110
      self.add_rule(0,1,0,1,1,0,1,1,0,self.s.south)
      # 001
      # 110
      # 110
      self.add_rule(0,0,1,1,1,0,1,1,0,self.s.south)
      # 100
      # 011
      # 011
      self.add_rule(1,0,0,0,1,1,0,1,1,self.s.east)
      # 001
      # 111
      # 001
      self.add_rule(0,0,1,1,1,1,0,0,1,self.s.stop)
      # 100
      # 111
      # 100
      self.add_rule(1,0,0,1,1,1,1,0,0,self.s.stop)
      # 011
      # 010
      # 110
      self.add_rule(0,1,1,0,1,0,1,1,0,self.s.stop)
      # 110
      # 010
      # 011
      self.add_rule(1,1,0,0,1,0,0,1,1,self.s.stop)
      # 110
      # 010
      # 110
      self.add_rule(1,1,1,0,1,0,1,1,0,self.s.stop)
      # 011
      # 010
      # 011
      self.add_rule(0,1,1,0,1,0,0,1,1,self.s.stop)
      #
      # 5 0's:
      #
      # 000 
      # 011
      # 011
      self.add_rule(0,0,0,0,1,1,0,1,1,self.s.east)
      # 001
      # 110
      # 001
      self.add_rule(0,0,1,1,1,0,0,0,1,self.s.stop)
      # 100
      # 110
      # 100
      self.add_rule(1,0,0,1,1,0,1,0,0,self.s.stop)
      # 001
      # 011
      # 001
      self.add_rule(0,0,1,0,1,1,0,0,1,self.s.stop)
      # 001
      # 111
      # 000
      self.add_rule(0,0,1,1,1,1,0,0,0,self.s.stop)
      # 100
      # 111
      # 000
      self.add_rule(1,0,0,1,1,1,0,0,0,self.s.stop)
      # 010
      # 010
      # 011
      self.add_rule(0,1,0,0,1,0,0,1,1,self.s.stop)
      # 010
      # 010
      # 110
      self.add_rule(0,1,0,0,1,0,1,1,0,self.s.stop)
      #
      # 6 0's:
      #
      # 010
      # 010
      # 010
      self.add_rule(0,1,0,0,1,0,0,1,0,self.s.stop)
      # 100
      # 010
      # 010
      self.add_rule(1,0,0,0,1,0,0,1,0,self.s.stop)
      # 001
      # 010
      # 010
      self.add_rule(0,0,1,0,1,0,0,1,0,self.s.stop)
      # 100
      # 010
      # 100
      self.add_rule(1,0,0,0,1,0,1,0,0,self.s.stop)
      # 100
      # 010
      # 001
      self.add_rule(1,0,0,0,1,0,0,0,1,self.s.stop)
      #
      # 7 0's:
      #
      # 100
      # 010
      # 000
      self.add_rule(1,0,0,0,1,0,0,0,0,self.s.stop)
      # 010
      # 010
      # 000
      self.add_rule(0,1,0,0,1,0,0,0,0,self.s.stop)
      #
      # 8 0's:
      #
      # 000
      # 010
      # 000
      self.add_rule(0,0,0,0,1,0,0,0,0,self.s.stop)
      #
      # edge states
      #
      # 200
      # 211
      # 211
      self.add_rule(2,0,0,2,1,1,2,1,1,self.s.east)
      # 201
      # 211
      # 211
      self.add_rule(2,0,1,2,1,1,2,1,1,self.s.east)
      # 201
      # 211
      # 210
      self.add_rule(2,0,1,2,1,1,2,1,0,self.s.east)
      # 002
      # 112
      # 112
      self.add_rule(0,0,2,1,1,2,1,1,2,self.s.stop)
      # 102
      # 112
      # 112
      self.add_rule(1,0,2,1,1,2,1,1,2,self.s.stop)
      # 002
      # 112
      # 102
      self.add_rule(0,0,2,1,1,2,1,0,2,self.s.stop)
      # 012
      # 112
      # 112
      self.add_rule(0,1,2,1,1,2,1,1,2,self.s.stop)
      # 012
      # 112
      # 102
      self.add_rule(0,1,2,1,1,2,1,0,2,self.s.stop)

   def add_rule(self,nw,nn,ne,ww,cc,ee,sw,ss,se,rule):
      #
      # add a CA rule, with rotations
      #
      s = CA_states()
      #
      # add the rule
      #
      state = \
         (nw <<  0) + (nn <<  2) + (ne <<  4) + \
         (ww <<  6) + (cc <<  8) + (ee << 10) + \
         (sw << 12) + (ss << 14) + (se << 16)
      self.table[state] = rule
      #
      # rotate 90 degrees
      # 
      state = \
         (sw <<  0) + (ww <<  2) + (nw <<  4) + \
         (ss <<  6) + (cc <<  8) + (nn << 10) + \
         (se << 12) + (ee << 14) + (ne << 16)
      if (rule == s.east):
         self.table[state] = s.south
      elif (rule == s.south):
         self.table[state] = s.west
      elif (rule == s.west):
         self.table[state] = s.north
      elif (rule == s.north):
         self.table[state] = s.east
      elif (rule == s.stop):
         self.table[state] = s.stop
      #
      # rotate 180 degrees
      # 
      state = \
         (se <<  0) + (ss <<  2) + (sw <<  4) + \
         (ee <<  6) + (cc <<  8) + (ww << 10) + \
         (ne << 12) + (nn << 14) + (nw << 16)
      if (rule == s.east):
         self.table[state] = s.west
      elif (rule == s.south):
         self.table[state] = s.north
      elif (rule == s.west):
         self.table[state] = s.east
      elif (rule == s.north):
         self.table[state] = s.south
      elif (rule == s.stop):
         self.table[state] = s.stop
      #
      # rotate 270 degrees
      # 
      state = \
         (ne <<  0) + (ee <<  2) + (se <<  4) + \
         (nn <<  6) + (cc <<  8) + (ss << 10) + \
         (nw << 12) + (ww << 14) + (sw << 16)
      if (rule == s.east):
         self.table[state] = s.north
      elif (rule == s.south):
         self.table[state] = s.east
      elif (rule == s.west):
         self.table[state] = s.south
      elif (rule == s.north):
         self.table[state] = s.west
      elif (rule == s.stop):
         self.table[state] = s.stop

def evaluate_state(array):
   #
   # assemble the state bit strings
   #
   (ny, nx) = shape(array)
   s = CA_states()
   nn = concatenate(([s.edge+zeros(nx)],array[:(ny-1)]))
   ss = concatenate((array[1:],[s.edge+zeros(nx)]))
   ww = concatenate((reshape(s.edge+zeros(ny),(ny,1)),array[:,:(nx-1)]),1)
   ee = concatenate((array[:,1:],reshape(s.edge+zeros(ny),(ny,1))),1)
   cc = array
   nw = concatenate(([s.edge+zeros(nx)],ww[:(ny-1)]))
   ne = concatenate(([s.edge+zeros(nx)],ee[:(ny-1)]))
   sw = concatenate((ww[1:],[s.edge+zeros(nx)]))
   se = concatenate((ee[1:],[s.edge+zeros(nx)]))
   state = (nw <<  0) + (nn <<  2) + (ne <<  4) + \
            (ww <<  6) + (cc <<  8) + (ee << 10) + \
            (sw << 12) + (ss << 14) + (se << 16)
   return state

def vectorize_toolpaths(array):
   #
   # convert lattice toolpath directions to vectors
   #
   s = CA_states()
   paths = []
   nvectors = 0
   max_dist = cad.nx*float(string_vector_error.get())/(cad.xmax-cad.xmin)
   sites = (array == s.north) | (array == s.south) | (array == s.east) | (array == s.west)
   remaining_sites = sum(sum(1.0*sites))
   while (remaining_sites != 0):
      #
      # loop over starting points
      #
      if (argmax(array[0,:] == s.south) != 0):
         start_row = 0
         start_col = argmax(sites[0,:])
      elif (argmax(array[:,-1] == s.west) != 0):
         start_row = argmax(sites[:,-1])
         start_col = cad.nx-1
      elif (argmax(array[-1,:] == s.north) != 0):
         start_row = cad.ny-1
         start_col = argmax(sites[-1,:])
      elif (argmax(array[:,0] == s.east) != 0):
         start_row = argmax(sites[:,0])
         start_col = 0
      else:
         maxcols = argmax(sites)
         start_row = argmax(argmax(sites))
         start_col = maxcols[start_row]
      initial_row = start_row
      initial_col = start_col
      old_row = initial_row
      old_col = initial_col
      current_state = array[initial_row][initial_col]
      array[initial_row][initial_col] = s.interior
      paths.append([point(initial_col,initial_row)])
      sum_row = initial_row
      sum_col = initial_col
      sum_row_2 = initial_row*initial_row
      sum_col_2 = initial_col*initial_col
      sum_row_col = initial_row*initial_col
      N = 1
      while 1:
         #
	 # follow segment
	 #
         if (current_state == s.north):
	    new_row = old_row - 1
	    new_col = old_col
         elif (current_state == s.south):
            new_row = old_row + 1
	    new_col = old_col
         elif (current_state == s.east):
            new_col = old_col + 1
	    new_row = old_row
         elif (current_state == s.west):
            new_col = old_col - 1
	    new_row = old_row
	 else:
	    #
	    # terminate segment on last good point if not a valid direction
	    #
            print("rule table error: path didn't close")
            paths[-1].append(point(old_col,old_row))
	    nvectors += 1
	    break
	 #
	 # check for termination conditions at new point
	 #
         if ((new_row == start_row) & (new_col == start_col)):
	    #
	    # end segment if starting point reached
	    #
            paths[-1].append(point(new_col,new_row))
	    nvectors += 1
	    break
         elif (array[new_row][new_col] == s.stop):
	    #
	    # end segment if stopping point reached
	    #
            paths[-1].append(point(new_col,new_row))
            array[new_row][new_col] = s.interior
	    nvectors += 1
	    break
         #
	 # update fit sums
	 #
	 sum_row += new_row
	 sum_row_2 += new_row*new_row
	 sum_2_row = sum_row*sum_row
	 sum_col += new_col
	 sum_col_2 += new_col*new_col
	 sum_2_col = sum_col*sum_col
	 sum_row_col += new_row*new_col
	 N += 1
	 #
	 # find perdendicular distance to fit
	 #
 	 if ((N*sum_col_2) != sum_2_col):
	    slope = (N*sum_row_col - sum_col*sum_row) / \
	       float(N*sum_col_2 - sum_2_col)
	    norm_row = slope/sqrt(1+slope**2)
	    norm_col = 1/sqrt(1+slope**2)
	    trans_row = norm_col
	    trans_col = -norm_row
	    dist = abs((new_row-initial_row)*trans_row + (new_col-initial_col)*trans_col)
	 else:
	    slope = (N*sum_row_col - sum_col*sum_row) / \
	       float(N*sum_row_2 - sum_2_row)
	    norm_col = slope/sqrt(1+slope**2)
	    norm_row = 1/sqrt(1+slope**2)
	    trans_col = norm_row
	    trans_row = -norm_col
	    dist = abs((new_row-initial_row)*trans_row + (new_col-initial_col)*trans_col)
	 #
         # start new vector if distance greater than max and vector has at least one point
         #
	 if ((dist >= max_dist) & ~((old_row == initial_row) & (old_col == initial_col))):
	    paths[-1].append(point(old_col,old_row))
	    nvectors += 1
	    initial_row = old_row
	    initial_col = old_col
            sum_row = initial_row
            sum_col = initial_col
            sum_row_2 = initial_row*initial_row
            sum_col_2 = initial_col*initial_col
            sum_row_col = initial_row*initial_col
            N = 1
	 #
         # otherwise accept point
         #
	 else:
	    current_state = array[new_row][new_col]
            array[new_row][new_col] = s.interior
	    old_row = new_row
	    old_col = new_col
      sites = (array == s.north) | (array == s.south) | (array == s.east) | (array == s.west)
      remaining_sites = sum(sum(1.0*sites))
   #
   # reverse segment order, to start from inside to out
   #
   newpaths = []
   for segment in range(len(paths)):
      newpaths.append(paths[-1-segment])
   root.update()
   return newpaths

def render(event):
   render_stop_flag = 0
   #
   # switch render button to stop rendering
   #
   widget_stop.pack()
   cad.stop = 0
   #
   # delete windows
   #
   delete_windows()
   #
   # initialize variables
   #
   if (cad.image.size() == 1):
      #
      # .cad
      #
      cad_text_string = widget_cad_text.get("1.0",END)
      exec cad_text_string in globals()
      widget_function_text.config(state=NORMAL)
      widget_function_text.delete("1.0",END)
      widget_function_text.insert("1.0",cad.function)
      widget_function_text.config(state=DISABLED)
   else:
      #
      # image 
      #
      cad.xmin = float(string_image_xmin.get())
      xwidth = float(string_image_xwidth.get())
      cad.xmax = cad.xmin + xwidth
      cad.ymin = float(string_image_ymin.get())
      yheight = float(string_image_yheight.get())
      cad.ymax = cad.ymin + yheight
      cad.zmin = float(string_image_zmin.get())
      cad.zmax = float(string_image_zmax.get())
      cad.image_min = float(string_image_min.get())
      cad.image_max = float(string_image_max.get())
      cad.nz = int(string_image_nz.get())
   cad.paths = []
   cad.zlist = []
   rx = pi*cad.rx/180.
   rz = pi*cad.rz/180.
   r = rule_table()
   s = CA_states()
   #
   # evaluate coordinate arrays
   #
   Xarray = outerproduct(ones((cad.ny,1)),cad.xmin+(cad.xmax-cad.xmin)*arange(cad.nx)/(cad.nx-1.0))
   Yarray = outerproduct(cad.ymin+(cad.ymax-cad.ymin)*arange(cad.ny-1,-1,-1)/(cad.ny-1.0),ones((1,cad.nx)))
   if (cad.zlist == []):
      if ((cad.nz == 1) & (cad.image.size() != 1)):
         cad.zlist = [(cad.zmax+cad.zmin)/2.0]
#      elif ((cad.nz == 1) | (find(cad.function,'Z') == -1)):
      elif (cad.nz == 1):
         cad.zlist = [cad.zmin]
      else:
         cad.zlist = cad.zmin + (cad.zmax-cad.zmin)*arange(cad.nz)/(cad.nz-1.0)
   else:
      cad.nz = len(cad.zlist)
   #
   # draw orthogonal views
   #
   X = Xarray
   Y = Yarray
   accum = zeros((cad.ny,cad.nx))
   intensity_yz = zeros((cad.ny,cad.nz))
   intensity_xz = zeros((cad.nz,cad.nx))
   for layer in range(cad.nz):
      #
      # check render stop button
      #
      if (cad.stop == 1):
         break
      #
      # evaluate z layer
      #
      Z = cad.zlist[layer]
      string_msg.set("render z = %.3f"%Z)
      root.update()
      if (cad.image.size() == 1):
         array = eval(cad.function)
      else:
         array = (cad.image > (cad.image_min + (cad.image_max-cad.image_min)*(Z-cad.zmin)/float(cad.zmax-cad.zmin)))
      #
      # xy view
      #
      if ((cad.zmax == cad.zmin) | (cad.nz == 1)):
         zi = 255
      else:
         zi = 55 + int(200.0*layer/(cad.nz-1.0))
      accum = where(((zi*array) > accum),(zi*array),accum)
      if (cad.image.size() == 1):
         intensity = ((1 << 16) + (1 << 8) + (1 << 0)) * accum
      else:
         intensity = ((1 << 16) + (1 << 8) + (1 << 0)) * (255-accum)
      im_xy = Image.frombuffer("RGBX",(cad.nx,cad.ny),intensity)
      im_xy = im_xy.transpose(Image.FLIP_TOP_BOTTOM)
      im_xy_draw = ImageDraw.Draw(im_xy)
      #im_xy = im_xy.resize((cad.nplot,cad.nplot),Image.ANTIALIAS)
      im_xy = im_xy.resize((cad.nxplot(),cad.nyplot()))
      images.xy = ImageTk.PhotoImage(im_xy)
      canvas_xy.create_image(cad.nplot/2,cad.nplot/2,image=images.xy)
      root.update()
      #
      # find toolpaths if needed
      #
      ncontours = int(string_num_contours.get())
      if (ncontours == -1):
         ncontours = 2**20 # a big number
      cad.paths.append([])
      for contour in range(ncontours):
         #
         # check render stop button
         #
         if (cad.stop == 1):
            break
         #
	 # convolve tool for contour
	 #
         string_msg.set(" convolve tool ... ")
	 root.update()
	 tool_rad = float(string_tool_dia.get())/2.0
	 tool_dia = float(string_tool_dia.get())
	 tool_overlap = float(string_tool_overlap.get())
	 kernel_rad = tool_rad + contour*tool_overlap*tool_dia
	 ikernel_rad = 1 + int(cad.nx*kernel_rad/(cad.xmax-cad.xmin))
	 k = ones((2*ikernel_rad,2*ikernel_rad)).astype(Bool)
	 kx = 1+outerproduct(ones((2*ikernel_rad,1)),arange(2*ikernel_rad))
	 ky = 1+outerproduct(arange(2*ikernel_rad),ones((1,2*ikernel_rad)))
	 k = ((kx-ikernel_rad)**2 + (ky-ikernel_rad)**2) < ikernel_rad**2
	 interior = (array == s.interior)
	 conv = convolve2d(interior,k,fft=1)
	 conv = (.5+conv).astype(UInt32)
	 conv = s.interior * conv.astype(Bool)
	 conv_array = conv + (conv != s.interior)*array
         #
	 # use CA rule table to find edge directions
	 #
         string_msg.set("  follow edges ... ")
	 root.update()
         state = evaluate_state(conv_array)
	 toolpath = r.table[state]
	 tool_array = toolpath + (toolpath == s.empty)*conv_array
         tool_intensity = \
            ((200 << 16) + (200 << 8) + (200 << 0))*(tool_array == s.empty) +\
            ((255 << 16) + (255 << 8) + (255 << 0))*(tool_array == s.interior) +\
            ((  0 << 16) + (  0 << 8) + (255 << 0))*(tool_array == s.north) +\
            ((  0 << 16) + (255 << 8) + (  0 << 0))*(tool_array == s.south) +\
            ((255 << 16) + (  0 << 8) + (  0 << 0))*(tool_array == s.east) +\
            ((  0 << 16) + (255 << 8) + (255 << 0))*(tool_array == s.west) +\
            ((128 << 16) + (  0 << 8) + (128 << 0))*(tool_array == s.stop)
         #im_xy = Image.frombuffer("RGBX",(cad.nx,cad.ny),tool_intensity)
         #im_xy = im_xy.transpose(Image.FLIP_TOP_BOTTOM)
         #im_xy = im_xy.resize((cad.nplot,cad.nplot),Image.ANTIALIAS)
         #im_xy = im_xy.resize((cad.nplot,cad.nplot))
	 #root.update()
	 #raw_input('edges')
	 #
	 # vectorize contour
	 #
         string_msg.set("    vectorize ...    ")
	 root.update()
         new_paths = vectorize_toolpaths(tool_array)
	 if (len(new_paths) == 0):
	    break
	 cad.paths[layer].extend(new_paths)
         #
	 # draw toolpath
	 #
         im_xy_draw = ImageDraw.Draw(im_xy)
         for segment in range(len(cad.paths[layer])):
            x = cad.nxplot()*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)
            y = cad.nyplot()*(cad.paths[layer][segment][0].y+0.5)/float(cad.ny)
            for vertex in range(1,len(cad.paths[layer][segment])):
               xnew = cad.nxplot()*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)
               ynew = cad.nyplot()*(cad.paths[layer][segment][vertex].y+0.5)/float(cad.ny)
               im_xy_draw.line([x,y,xnew,ynew],fill="#ffa0a0",width=1)
               x = xnew
               y = ynew
         #
         # show xy toolpath view
         #
         images.xy = ImageTk.PhotoImage(im_xy)
         canvas_xy.create_image(cad.nplot/2,cad.nplot/2,image=images.xy)
	 root.update()
      #
      # draw labels
      #
      for label in range(len(cad.labels)):
	 x = cad.nplot*(cad.labels[label].x-cad.xmin)/(cad.xmax-cad.xmin)
	 y = cad.nplot - cad.nplot*(cad.labels[label].y-cad.ymin)/(cad.ymax-cad.ymin)
	 string = cad.labels[label].text
	 size = cad.labels[label].size
         canvas_xy.create_text(x,y,text=string,font=('arial',size),fill='#ff0000',anchor=CENTER,justify=CENTER)
      #
      # draw origin
      #
      x0 = cad.nplot/2. + cad.nxplot()*(0-(cad.xmax+cad.xmin)/2.)/(cad.xmax-cad.xmin)
      y0 = cad.nplot/2. - cad.nyplot()*(0-(cad.ymax+cad.ymin)/2.)/(cad.ymax-cad.ymin)
      dxy = .025*cad.nplot
      canvas_xy.create_line([x0-dxy,y0,x0+dxy,y0],fill="green")
      canvas_xy.create_line([x0,y0-dxy,x0,y0+dxy],fill="green")
      #
      # yz view
      #
      if ((cad.nz != 1) & ((find(cad.function,'Z') != -1) | (cad.image.size() != 1))):
         accum_yz = zeros(cad.ny)
         for vertex in range(cad.nx):
            xi = 55 + int(200.0*vertex/(cad.nx-1.0))
            slice = array[:,vertex]
            accum_yz = where(((xi*slice) > accum_yz),(xi*slice),accum_yz)
         intensity_yz[:,layer] = ((1 << 16) + (1 << 8) + (1 << 0)) * accum_yz
         im_yz = Image.frombuffer("RGBX",(cad.nz,cad.ny),intensity_yz)
         im_yz = im_yz.transpose(Image.FLIP_TOP_BOTTOM)
         im_yz = im_yz.transpose(Image.FLIP_LEFT_RIGHT)
         im_yz = im_yz.resize((cad.nzplot(),cad.nyplot()))
         images.yz = ImageTk.PhotoImage(im_yz)
         canvas_yz.create_image(cad.nplot/2,cad.nplot/2,image=images.yz)
         #
         # draw origin
         #
         z0 = cad.nplot/2. - cad.nzplot()*(0-(cad.zmax+cad.zmin)/2.)/(cad.zmax-cad.zmin)
         y0 = cad.nplot/2. - cad.nyplot()*(0-(cad.ymax+cad.ymin)/2.)/(cad.ymax-cad.ymin)
         canvas_yz.create_line([z0-dxy,y0,z0+dxy,y0],fill="green")
         canvas_yz.create_line([z0,y0-dxy,z0,y0+dxy],fill="green")
      #
      # xz view
      #
      if ((cad.nz != 1) & ((find(cad.function,'Z') != -1) | (cad.image.size() != 1))):
         accum_xz = zeros(cad.nx)
         for vertex in range(cad.ny):
            yi = 55 + int(200.0*vertex/(cad.ny-1.0))
	    slice = array[vertex,:]
            accum_xz = where(((yi*slice) > accum_xz),(yi*slice),accum_xz)
         intensity_xz[(cad.nz-1-layer),:] = ((1 << 16) + (1 << 8) + (1 << 0)) * accum_xz
         im_xz = Image.frombuffer("RGBX",(cad.nx,cad.nz),intensity_xz)
         im_xz = im_xz.transpose(Image.FLIP_TOP_BOTTOM)
         im_xz = im_xz.resize((cad.nxplot(),cad.nzplot()))
         images.xz = ImageTk.PhotoImage(im_xz)
         canvas_xz.create_image(cad.nplot/2,cad.nplot/2,image=images.xz)
         #n
         # draw origin
         #
         x0 = cad.nplot/2. + cad.nxplot()*(0-(cad.xmax+cad.xmin)/2.)/(cad.xmax-cad.xmin)
         z0 = cad.nplot/2. - cad.nzplot()*(0-(cad.zmax+cad.zmin)/2.)/(cad.zmax-cad.zmin)
         canvas_xz.create_line([x0-dxy,z0,x0+dxy,z0],fill="green")
         canvas_xz.create_line([x0,z0-dxy,x0,z0+dxy],fill="green")
      #
      # draw it
      #
      root.update()
   #
   # rotated view
   #
   if ((cad.nz != 1) & (find(cad.function,'Z') != -1) & (ncontours == 0) & (cad.image.size() == 1)):
      accum = zeros((cad.ny,cad.nx))
      for Z in cad.zlist:
         #
         # check render stop button
         #
         if (cad.stop == 1):
            break
         string_msg.set("render z = %.3f"%Z)
         dY = cos(rx)*(Yarray-(cad.ymax+cad.ymin)/2.0) - sin(rx)*(Z-(cad.zmax+cad.zmin)/2.0)
         Z = (cad.zmax+cad.zmin)/2.0 + sin(rx)*(Yarray-(cad.ymax+cad.ymin)/2.0) + cos(rx)*(Z-(cad.zmax+cad.zmin)/2.0)
         X = (cad.xmax+cad.xmin)/2.0 + cos(rz)*(Xarray-(cad.xmax+cad.xmin)/2.0) - sin(rz)*dY
         Y = (cad.ymax+cad.ymin)/2.0 + sin(rz)*(Xarray-(cad.xmax+cad.xmin)/2.0) + cos(rz)*dY
         array = eval(cad.function)
         if (cad.zmax == cad.zmin):
            zi = 255
         else:
            zi = 55 + 245.0*(Z-cad.zmin)/(cad.zmax-cad.zmin)
            zi = zi.astype(UInt8)
         accum = where(((zi*array) > accum),(zi*array),accum)
         intensity = ((1 << 16) + (1 << 8) + (1 << 0)) * accum
         im_rot = Image.frombuffer("RGBX",(cad.nx,cad.ny),intensity)
         im_rot = im_rot.transpose(Image.FLIP_TOP_BOTTOM)
         #im_rot = im_rot.resize((cad.nplot,cad.nplot),Image.ANTIALIAS)
         im_rot = im_rot.resize((cad.nxplot(),cad.nyplot()))
         images.rot = ImageTk.PhotoImage(im_rot)
         canvas_rot.create_image(cad.nplot/2,cad.nplot/2,image=images.rot)
         root.update()
   #
   # return
   #
   widget_stop.pack_forget()
   string_msg.set("done")
   root.update()
   return

def draw_toolpath():
   im_xy = Image.new("RGBX",(cad.nxplot(),cad.nyplot()),'white')
   im_xy_draw = ImageDraw.Draw(im_xy)
   for layer in range(len(cad.paths)):
      for segment in range(len(cad.paths[layer])):
         x = cad.nxplot()*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)
         y = cad.nyplot()*(cad.paths[layer][segment][0].y+0.5)/float(cad.ny)
         for vertex in range(1,len(cad.paths[layer][segment])):
            xnew = cad.nxplot()*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)
            ynew = cad.nyplot()*(cad.paths[layer][segment][vertex].y+0.5)/float(cad.ny)
            im_xy_draw.line([x,y,xnew,ynew],fill="black")
            x = xnew
            y = ynew
   images.xy = ImageTk.PhotoImage(im_xy)
   canvas_xy.create_image(cad.nplot/2,cad.nplot/2,image=images.xy)

def delete_windows():
   im_xy = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
   images.xy = ImageTk.PhotoImage(im_xy)
   canvas_xy.create_image(cad.nplot/2,cad.nplot/2,image=images.xy)
   im_yz = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
   images.yz = ImageTk.PhotoImage(im_yz)
   canvas_yz.create_image(cad.nplot/2,cad.nplot/2,image=images.yz)
   im_xz = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
   images.xz = ImageTk.PhotoImage(im_xz)
   canvas_xz.create_image(cad.nplot/2,cad.nplot/2,image=images.xz)
   im_rot = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
   images.rot = ImageTk.PhotoImage(im_rot)
   canvas_rot.create_image(cad.nplot/2,cad.nplot/2,image=images.rot)
   root.update()

def select_cad():
   image_space_frame.pack_forget()
   image_x_frame.pack_forget()
   image_y_frame.pack_forget()
   image_z_frame.pack_forget()
   image_intensity_frame.pack_forget()
   widget_cad_text.delete("1.0",END)
   widget_cad_text.insert("1.0",cad_template)
   cad_text_frame.pack()
   cad.image = array(0)
   cad.paths = []
   string_num_contours.set('0')
   delete_windows()

def select_image():
   cad_text_frame.pack_forget()
   image_space_frame.pack()
   image_x_frame.pack()
   image_y_frame.pack()
   image_z_frame.pack()
   image_intensity_frame.pack()
   cad.paths = []
   string_num_contours.set('0')
   delete_windows()

def input_open():
   filename = askopenfilename()
   filename = os.path.basename(filename)
   string_input_file.set(filename)
   if (find(filename,'.cad') != -1):
      cad_load(0)
   elif ((find(filename,'.jpg') != -1) | (find(filename,'.JPG') != -1) |
      (find(filename,'.png') != -1) | (find(filename,'.PNG') != -1) |
      (find(filename,'.gif') != -1) | (find(filename,'.GIF') != -1) |
      (find(filename,'.tif') != -1) | (find(filename,'.TIF') != -1) |
      (find(filename,'.tiff') != -1) | (find(filename,'.TIFF') != -1) |
      (find(filename,'.bmp') != -1) | (find(filename,'.BMP') != -1)):
      image_load(0)
   else:
      string_msg.set("unsupported input file format")
      root.update()
      
def cad_load(event):
   cam_pack_forget()
   select_cad()
   function_string_label_frame.pack()
   function_string_frame.pack()
   input_file_name = string_input_file.get()
   input_file = open(input_file_name,'rb')
   cad_text_string = input_file.read()
   widget_cad_text.delete("1.0",END)
   widget_cad_text.insert("1.0",cad_text_string)
   input_file.close()
   cad.paths = []
   cad.image = array(0)
   cad.nz = 1
   string_num_contours.set('0')
   render(0)

def image_load(event):
   cam_pack_forget()
   select_image()
   function_string_label_frame.pack_forget()
   function_string_frame.pack_forget()
   input_file_name = string_input_file.get()
   input_file = open(input_file_name,'rb')
   input_file.close()
   cad.paths = []
   string_num_contours.set('0')
   image = Image.open(input_file_name)
   image = ImageOps.grayscale(image)
   (cad.nx,cad.ny) = image.size
   info = image.info
   if ('dpi' in info):
      (xdpi,ydpi) = info['dpi']
   else:
      xdpi = cad.nx
      ydpi = xdpi
   string_image_nx.set(" nx = "+str(cad.nx))
   string_image_ny.set(" ny = "+str(cad.ny))
   cad.nz = 10
   string_image_nz.set(str(cad.nz))
   cad.xmin = 0
   string_image_xmin.set('0')
   cad.xmax = cad.nx/float(xdpi)
   string_image_xwidth.set(str(cad.xmax-cad.xmin))
   cad.ymin = 0
   string_image_ymin.set('0')
   cad.ymax = cad.ny/float(ydpi)
   string_image_yheight.set(str(cad.ymax-cad.ymin))
   cad.zmin = -1
   string_image_zmin.set('-1')
   cad.zmax = 0
   string_image_zmax.set('0')
   cad.image = array(list(image.getdata()),shape=(cad.ny,cad.nx))
   cad.image = max(ravel(cad.image)) - cad.image
   cad.image_min = min(ravel(cad.image)) + 10 # offset to clip background noise
   string_image_min.set(str(cad.image_min))
   cad.image_max = max(ravel(cad.image))
   string_image_max.set(str(cad.image_max))
   render(0)

def cad_save(event):
   input_file_name = string_input_file.get()
   input_file = open(input_file_name,'wb')
   cad_text_string = widget_cad_text.get("1.0",END)
   input_file.write(cad_text_string)
   input_file.close()
   string_msg.set(input_file_name+" saved")
   root.update()

def render_button(event):
   cam_pack_forget()
   if (cad.image.size() == 1):
      function_string_label_frame.pack()
      function_string_frame.pack()
   cad.paths = []
   string_num_contours.set('0')
   render(0)

def render_stop(event):
   cad.stop = 1
   widget_stop.pack_forget()
      
def cam(event):
   function_string_label_frame.pack_forget()
   function_string_frame.pack_forget()
   cam_file_frame.pack()
   string_num_contours.set('1')
   root.update()

def contour(event):
   render('contour')

def select_epi():
   input_file_name = string_input_file.get()
   string_cam_file.set(input_file_name[0:-4]+'.epi')
   cam_pack_forget()
   cam_file_frame.pack()
   cam_vector_frame.pack()
   cam_dia_frame.pack()
   cam_contour_frame.pack()
   laser_frame1.pack()
   laser_frame2.pack()
   string_laser_rate.set("2500")
   string_laser_power.set("50")
   string_laser_speed.set("50")
   string_tool_dia.set("0.01")
   root.update()

def select_camm():
   input_file_name = string_input_file.get()
   string_cam_file.set(input_file_name[0:-4]+'.camm')
   cam_pack_forget()
   cam_file_frame.pack()
   cam_vector_frame.pack()
   cam_dia_frame.pack()
   cam_contour_frame.pack()
   cut_frame.pack()
   string_cut_force.set("45")
   string_cut_velocity.set("2")
   string_tool_dia.set("0.01")
   root.update()

def select_ps():
   input_file_name = string_input_file.get()
   string_cam_file.set(input_file_name[0:-4]+'.ps')
   cam_pack_forget()
   cam_file_frame.pack()
   cam_vector_frame.pack()
   cam_dia_frame.pack()
   cam_contour_frame.pack()
   string_tool_dia.set("0.0")
   root.update()

def select_ord():
   input_file_name = string_input_file.get()
   string_cam_file.set(input_file_name[0:-4]+'.ord')
   cam_pack_forget()
   cam_file_frame.pack()
   cam_vector_frame.pack()
   cam_dia_frame.pack()
   cam_contour_frame.pack()
   string_tool_dia.set("0.01")
   waterjet_frame.pack()
   string_lead_in.set("0.05")
   string_quality.set("-3")
   root.update()

def select_rml():
   input_file_name = string_input_file.get()
   string_cam_file.set(input_file_name[0:-4]+'.rml')
   cam_pack_forget()
   cam_file_frame.pack()
   cam_vector_frame.pack()
   cam_dia_frame.pack()
   cam_contour_frame.pack()
   speed_frame.pack()
   string_tool_dia.set("0.0156")
   string_xy_speed.set("4")
   string_z_speed.set("4")
   root.update()

def cam_pack_forget():
   cam_file_frame.pack_forget()
   cam_vector_frame.pack_forget()
   cam_dia_frame.pack_forget()
   cam_contour_frame.pack_forget()
   laser_frame1.pack_forget()
   laser_frame2.pack_forget()
   cut_frame.pack_forget()
   speed_frame.pack_forget()
   waterjet_frame.pack_forget()

def save_cam(event):
   #
   # write toolpath
   #
   text = string_cam_file.get()
   if (find(text,".epi") != -1):
      write_epi()
   elif (find(text,".camm") != -1):
      write_camm()
   elif (find(text,".ps") != -1):
      write_ps()
   elif (find(text,".ord") != -1):
      write_ord()
   elif (find(text,".rml") != -1):
      write_rml()
   else:
      string_msg.set("unsupported output file format")
      root.update()

def write_epi():
   #
   # Epilog lasercutter output
   # todo: try 1200 DPI
   #
   units = 600
   filename = string_cam_file.get()
   file = open(filename, 'wb')
   if (integer_laser_autofocus.get() == 0):
      #
      # init with autofocus off
      #
      file.write("%-12345X@PJL JOB NAME="+string_cam_file.get()+"\r\nE@PJL ENTER LANGUAGE=PCL\r\n&y0A&l0U&l0Z&u600D*p0X*p0Y*t600R*r0F&y50P&z50S*r6600T*r5100S*r1A*rC%1BIN;XR"+string_laser_rate.get()+";YP"+string_laser_power.get()+";ZS"+string_laser_speed.get()+";")
   else:
      #
      # init with autofocus on
      #
      file.write("%-12345X@PJL JOB NAME="+string_cam_file.get()+"\r\nE@PJL ENTER LANGUAGE=PCL\r\n&y1A&l0U&l0Z&u600D*p0X*p0Y*t600R*r0F&y50P&z50S*r6600T*r5100S*r1A*rC%1BIN;XR"+string_laser_rate.get()+";YP"+string_laser_power.get()+";ZS"+string_laser_speed.get()+";")
   for layer in range(len(cad.paths)):
      for segment in range(len(cad.paths[layer])):
         x = int(units*(cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)))
         y = int(units*(-cad.ymin - ((cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][0].y)+0.5)/float(cad.ny))))
         file.write("PU"+str(x)+","+str(y)+";")
         for vertex in range(1,len(cad.paths[layer][segment])):
            x = int(units*(cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)))
            y = int(units*(-cad.ymin - ((cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][vertex].y)+0.5)/float(cad.ny))))
            file.write("PD"+str(x)+","+str(y)+";")
      file.write("%0B%1BPUE%-12345X@PJL EOJ \r\n")
   file.close()
   draw_toolpath()
   string_msg.set("wrote %s"%filename)
   root.update()

def write_camm():
   filename = string_cam_file.get()
   file = open(filename, 'wb')
   units = 1000
   file.write("PA;PA;!ST1;!FS"+string_cut_force.get()+";VS"+string_cut_velocity.get()+";")
   for layer in range(len(cad.paths)):
      for segment in range(len(cad.paths[layer])):
         x = int(units*(cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)))
         y = int(units*(cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][0].y)+0.5)/float(cad.ny)))
         file.write("PU"+str(x)+","+str(y)+";")
         for vertex in range(1,len(cad.paths[layer][segment])):
            x = int(units*(cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)))
            y = int(units*(cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][vertex].y)+0.5)/float(cad.ny)))
            file.write("PD"+str(x)+","+str(y)+";")
   file.write("PU0,0;")
   file.close()
   draw_toolpath()
   string_msg.set("wrote %s"%filename)
   root.update()

def write_ps():
   #
   # Postscript output
   #
   filename = string_cam_file.get()
   file = open(filename, 'wb')
   file.write("%! cad.py output\n")
   file.write("%%%%BoundingBox: 0 0 %.3f %.3f\n"%
      (72.0*(cad.xmax-cad.xmin),72.0*(cad.ymax-cad.ymin)))
   file.write("/m {moveto} def\n")
   file.write("/l {lineto} def\n")
   file.write("72 72 scale\n")
   file.write(".005 setlinewidth\n")
   file.write("%f %f translate\n"%(0.5,0.5))
   for layer in range(len(cad.paths)):
      for segment in range(len(cad.paths[layer])):
         x = cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)
         y = cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][0].y)+0.5)/float(cad.ny)
         file.write("%f %f m\n"%(x,y))
         for vertex in range(1,len(cad.paths[layer][segment])):
            x = cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)
            y = cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][vertex].y)+0.5)/float(cad.ny)
            file.write("%f %f l\n"%(x,y))
         file.write("stroke\n")
   file.write("showpage\n")
   file.close()
   draw_toolpath()
   string_msg.set("wrote %s"%filename)
   root.update()

def write_ord():
   #
   # OMAX waterjet output
   #
   lead_in = float(string_lead_in.get())
   quality = int(string_quality.get())
   filename = string_cam_file.get()
   file = open(filename, 'wb')
   xlead = []
   ylead = []
   for layer in range(len(cad.paths)):
      for segment in range(len(cad.paths[layer])):
         #
         # calculate and write lead-in
         #
         x0 = cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)
         y0 = cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][0].y)+0.5)/float(cad.ny)
         x1 = cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][1].x+0.5)/float(cad.nx)
         y1 = cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][1].y)+0.5)/float(cad.ny)
         dx = x1 - x0
         dy = y1 - y0
         norm_x = -dy
         norm_y = dx
         norm = sqrt(norm_x**2 + norm_y**2)
         norm_x = norm_x/norm
         norm_y = norm_y/norm
         xlead.append(x0 + norm_x*lead_in)
         ylead.append(y0 + norm_y*lead_in)
         file.write("%f, %f, 0, %d\n"%(xlead[segment],ylead[segment],quality))
         #
         # loop over segment
         #
         for vertex in range(len(cad.paths[layer][segment])):
            x = cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)
            y = cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][vertex].y)+0.5)/float(cad.ny)
            file.write("%f, %f, 0, %d\n"%(x,y,quality))
         #
         # write lead-out
         #
         file.write("%f, %f, 0, 0\n"%(x0,y0))
         file.write("%f, %f, 0, 0\n"%(xlead[segment],ylead[segment]))
   file.close()
   #
   # draw toolpath with lead-in/out
   #
   im_xy = Image.new("RGBX",(cad.nxplot(),cad.nyplot()),'white')
   im_xy_draw = ImageDraw.Draw(im_xy)
   for layer in range(len(cad.paths)):
      for segment in range(len(cad.paths[layer])):
         x = cad.nxplot()*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)
         y = cad.nyplot()*(cad.paths[layer][segment][0].y+0.5)/float(cad.ny)
         xl = cad.nxplot()*(xlead[segment]-cad.xmin)/(cad.xmax-cad.xmin)
         yl = cad.nyplot()-cad.nyplot()*(ylead[segment]-cad.ymin)/(cad.ymax-cad.ymin)
         im_xy_draw.line([xl,yl,x,y],fill="black")
         for vertex in range(1,len(cad.paths[layer][segment])):
            xnew = cad.nxplot()*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)
            ynew = cad.nyplot()*(cad.paths[layer][segment][vertex].y+0.5)/float(cad.ny)
            im_xy_draw.line([x,y,xnew,ynew],fill="black")
            x = xnew
            y = ynew
   images.xy = ImageTk.PhotoImage(im_xy)
   canvas_xy.create_image(cad.nplot/2,cad.nplot/2,image=images.xy)
   string_msg.set("wrote %s"%filename)
   root.update()

def write_rml():
   #
   # Roland Modela output
   #
   filename = string_cam_file.get()
   file = open(filename, 'wb')
   file.write("PA;PA;VS"+string_xy_speed.get()+";!VZ"+string_z_speed.get()+";!MC1;")
   #file.write("PA;PA;VS"+string_xy_speed.get()+";!VZ"+string_z_speed.get()+";!MC0;")
   units = 1000
   zup = cad.zmax
   izup = int(units*zup)
   for layer in range(len(cad.zlist)-1,-1,-1):
      zdown = cad.zlist[layer]
      izdown = int(units*zdown)
      file.write("!PZ"+str(izdown)+","+str(izup)+";")
      #
      # follow toolpaths CCW, for CW tool motion
      #
      for segment in range(len(cad.paths[layer])):      
         x = int(units*(cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][0].x+0.5)/float(cad.nx)))
         y = int(units*(cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][0].y)+0.5)/float(cad.ny)))
         file.write("PU"+str(x)+","+str(y)+";")
         for vertex in range(1,len(cad.paths[layer][segment])):
            x = int(units*(cad.xmin + (cad.xmax-cad.xmin)*(cad.paths[layer][segment][vertex].x+0.5)/float(cad.nx)))
            y = int(units*(cad.ymin + (cad.ymax-cad.ymin)*((cad.ny-cad.paths[layer][segment][vertex].y)+0.5)/float(cad.ny)))
            file.write("PD"+str(x)+","+str(y)+";")
   file.write("PU"+str(x)+","+str(y)+";!MC0;")
   #
   # file padding hack for end-of-file buffering problems
   #
   for i in range(750):
      file.write("!MC0;")
   file.close()
   draw_toolpath()
   string_msg.set("wrote %s"%filename)
   root.update()

def msg_xy(event):
   x = (cad.xmin+cad.xmax)/2. + (cad.xmax-cad.xmin)*(1+event.x-cad.nplot/2.)/float(cad.nxplot())
   y = (cad.ymin+cad.ymax)/2. + (cad.ymin-cad.ymax)*(1+event.y-cad.nplot/2.)/float(cad.nyplot())
   string_msg.set("x = %.2f  y = %.2f"%(x,y))

def msg_yz(event):
   if (cad.nz > 1):
      y = (cad.ymin+cad.ymax)/2. + (cad.ymin-cad.ymax)*(1+event.y-cad.nplot/2.)/float(cad.nyplot())
      z = (cad.zmin+cad.zmax)/2. + (cad.zmin-cad.zmax)*(1+event.x-cad.nplot/2.)/float(cad.nzplot())
      string_msg.set("y = %.2f  z = %.2f"%(y,z))
   else:
      string_msg.set("")

def msg_xz(event):
   if (cad.nz > 1):
      x = (cad.xmin+cad.xmax)/2. + (cad.xmax-cad.xmin)*(1+event.x-cad.nplot/2.)/float(cad.nxplot())
      z = (cad.zmin+cad.zmax)/2. + (cad.zmin-cad.zmax)*(1+event.y-cad.nplot/2.)/float(cad.nzplot())
      string_msg.set("x = %.2f  z = %.2f"%(x,z))
   else:
      string_msg.set("")

def msg_nomsg(event):
   string_msg.set("")

def image_min_x(event):
   cad.xmin = float(string_image_xmin.get())
   xwidth = float(string_image_xwidth.get())
   cad.xmax = cad.xmin + xwidth
   root.update()

def image_min_y(event):
   cad.ymin = float(string_image_ymin.get())
   yheight = float(string_image_yheight.get())
   cad.ymax = cad.ymin + yheight
   root.update()

def image_scale_x(event):
   yheight = float(string_image_yheight.get())
   xwidth = yheight*cad.nx/float(cad.ny)
   cad.xmax = cad.xmin + xwidth
   string_image_xwidth.set(str(xwidth))
   root.update()

def image_scale_y(event):
   xwidth = float(string_image_xwidth.get())
   yheight = xwidth*cad.ny/float(cad.nx)
   cad.ymax = cad.ymin + yheight
   string_image_yheight.set(str(yheight))
   root.update()

#
# set up GUI
#
root = Tk()
root.title('cad.py')
#
msg_frame = Frame(root)
string_msg = StringVar()
widget_msg = Label(msg_frame, textvariable = string_msg)
widget_msg.pack(side='left')
Label(msg_frame, text=" ").pack(side='left')
widget_stop = Button(msg_frame, text='stop')
widget_stop.bind('<Button-1>',render_stop)
msg_frame.pack()
#
# top, side, input frame
#
top_side_image_frame = Frame(root)
canvas_xy = Canvas(top_side_image_frame, width=cad.nplot, height=cad.nplot)
imxy = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
image_xy = ImageTk.PhotoImage(imxy)
canvas_xy.create_image(cad.nplot/2,cad.nplot/2,image=image_xy)
canvas_xy.bind('<Motion>',msg_xy)
canvas_xy.pack(side='left')
canvas_yz = Canvas(top_side_image_frame, width=cad.nplot, height=cad.nplot)
imyz = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
image_yz = ImageTk.PhotoImage(imyz)
canvas_yz.create_image(cad.nplot/2,cad.nplot/2,image=image_yz)
canvas_yz.bind('<Motion>',msg_yz)
canvas_yz.pack(side='left')
#
cad_text_frame = Frame(top_side_image_frame)
widget_text_yscrollbar = Scrollbar(cad_text_frame)
widget_cad_text = Text(cad_text_frame, bg='white', bd=5, width=45, height=30, yscrollcommand=widget_text_yscrollbar.set)
widget_cad_text.pack(side='left', fill=Y)
widget_text_yscrollbar.pack(side='left', fill=Y)
widget_text_yscrollbar.config(command=widget_cad_text.yview)
widget_cad_text.bind('<Motion>',msg_nomsg)
cad_text_frame.pack(side='left')
#
image_space_frame = Frame(top_side_image_frame)
Label(image_space_frame, text="                                                                                      ").pack(side='left')
image_space_frame.pack()
#
image_x_frame = Frame(top_side_image_frame)
Label(image_x_frame, text="x min: ").pack(side='left')
string_image_xmin = StringVar()
widget_image_xmin = Entry(image_x_frame, width=6, bg='white', textvariable=string_image_xmin)
widget_image_xmin.bind('<Return>',image_min_x)
widget_image_xmin.pack(side='left')
Label(image_x_frame, text="   x width: ").pack(side='left')
string_image_xwidth = StringVar()
widget_image_xwidth = Entry(image_x_frame, width=6, bg='white', textvariable=string_image_xwidth)
widget_image_xwidth.bind('<Return>',image_scale_y)
widget_image_xwidth.pack(side='left')
string_image_nx = StringVar()
Label(image_x_frame, textvariable = string_image_nx).pack(side='left')
#
image_y_frame = Frame(top_side_image_frame)
Label(image_y_frame, text="y min: ").pack(side='left')
string_image_ymin = StringVar()
widget_image_ymin = Entry(image_y_frame, width=6, bg='white', textvariable=string_image_ymin)
widget_image_ymin.bind('<Return>',image_min_y)
widget_image_ymin.pack(side='left')
Label(image_y_frame, text="  y height: ").pack(side='left')
string_image_yheight = StringVar()
widget_image_yheight = Entry(image_y_frame, width=6, bg='white', textvariable=string_image_yheight)
widget_image_yheight.bind('<Return>',image_scale_x)
widget_image_yheight.pack(side='left')
string_image_ny = StringVar()
Label(image_y_frame, textvariable = string_image_ny).pack(side='left')
#
image_z_frame = Frame(top_side_image_frame)
Label(image_z_frame, text="zmin: ").pack(side='left')
string_image_zmin = StringVar()
widget_image_zmin = Entry(image_z_frame, width=6, bg='white', textvariable=string_image_zmin)
widget_image_zmin.pack(side='left')
Label(image_z_frame, text="   zmax: ").pack(side='left')
string_image_zmax = StringVar()
widget_image_zmax = Entry(image_z_frame, width=6, bg='white', textvariable=string_image_zmax)
widget_image_zmax.pack(side='left')
Label(image_z_frame, text="   nz: ").pack(side='left')
string_image_nz = StringVar()
widget_image_nz = Entry(image_z_frame, width=6, bg='white', textvariable=string_image_nz)
widget_image_nz.pack(side='left')
#
image_intensity_frame = Frame(top_side_image_frame)
Label(image_intensity_frame, text="intensity min: ").pack(side='left')
string_image_min = StringVar()
widget_image_min = Entry(image_intensity_frame, width=6, bg='white', textvariable=string_image_min)
widget_image_min.pack(side='left')
Label(image_intensity_frame, text="   intensity max: ").pack(side='left')
string_image_max = StringVar()
widget_image_max = Entry(image_intensity_frame, width=6, bg='white', textvariable=string_image_max)
widget_image_max.pack(side='left')
#
top_side_image_frame.pack()
#
# front, perspective, UI frame
#
front_persp_UI_frame = Frame(root)
canvas_xz = Canvas(front_persp_UI_frame, width=cad.nplot, height=cad.nplot)
imxz = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
image_xz = ImageTk.PhotoImage(imxz)
canvas_xz.create_image(cad.nplot/2,cad.nplot/2,image=image_xz)
canvas_xz.bind('<Motion>',msg_xz)
canvas_xz.pack(side='left')
canvas_rot = Canvas(front_persp_UI_frame, width=cad.nplot, height=cad.nplot)
imrot = Image.new("RGBX",(cad.nplot,cad.nplot),'#dcd9cb')
image_rot = ImageTk.PhotoImage(imrot)
canvas_rot.create_image(cad.nplot/2,cad.nplot/2,image=image_rot)
canvas_rot.bind('<Motion>',msg_nomsg)
canvas_rot.pack(side='left')
#
# UI
#
cad_frame = Frame(front_persp_UI_frame)
cad_frame.bind('<Motion>',msg_nomsg)
widget_input_format_button = Menubutton(cad_frame,text="format", relief=RAISED)
widget_input_format_button.pack(side='left')
widget_input_format_menu = Menu(widget_input_format_button)
widget_input_format_menu.add_command(label='.cad',command=select_cad)
widget_input_format_menu.add_command(label='image',command=select_image)
widget_input_format_button['menu'] = widget_input_format_menu
Label(cad_frame, text=" ").pack(side='left')
widget_input_file = Button(cad_frame, text="file:",command=input_open)
widget_input_file.pack(side='left')
string_input_file = StringVar()
string_input_file.set('out.cad')
Label(cad_frame, text=" ").pack(side='left')
widget_cad = Entry(cad_frame, width=12, bg='white', textvariable=string_input_file)
widget_cad.pack(side='left')
Label(cad_frame, text=" ").pack(side='left')
widget_cad_load = Button(cad_frame, text="load")
widget_cad_load.bind('<Button-1>',cad_load)
widget_cad_load.pack(side='left')
Label(cad_frame, text=" ").pack(side='left')
widget_cad_save = Button(cad_frame, text="save")
widget_cad_save.bind('<Button-1>',cad_save)
widget_cad_save.pack(side='left')
cad_frame.pack()
#
control_frame = Frame(front_persp_UI_frame)
widget_render = Button(control_frame, text="render")
widget_render.bind('<Button-1>',render_button)
widget_render.pack(side='left')
Label(control_frame, text=" ").pack(side='left')
canvas_logo = Canvas(control_frame, width=26, height=26, background="white")
canvas_logo.create_oval(2,2,8,8,fill="red",outline="")
canvas_logo.create_rectangle(11,2,17,8,fill="blue",outline="")
canvas_logo.create_rectangle(20,2,26,8,fill="blue",outline="")
canvas_logo.create_rectangle(2,11,8,17,fill="blue",outline="")
canvas_logo.create_oval(10,10,16,16,fill="red",outline="")
canvas_logo.create_rectangle(20,11,26,17,fill="blue",outline="")
canvas_logo.create_rectangle(2,20,8,26,fill="blue",outline="")
canvas_logo.create_rectangle(11,20,17,26,fill="blue",outline="")
canvas_logo.create_rectangle(20,20,26,26,fill="blue",outline="")
canvas_logo.pack(side="left")
control_text = " cad.py (%s) "%DATE
Label(control_frame, text=control_text).pack(side='left')
widget_cam = Button(control_frame, text="cam")
widget_cam.bind('<Button-1>',cam)
widget_cam.pack(side='left')
Label(control_frame, text=" ").pack(side='left')
widget_quit = Button(control_frame, text="quit", command='exit')
widget_quit.pack(side='left')
control_frame.pack()
#
function_string_label_frame = Frame(front_persp_UI_frame)
Label(function_string_label_frame, text="function:").pack(side='left')
#function_string_label_frame.pack()
#
function_string_frame = Frame(front_persp_UI_frame)
widget_function_yscrollbar = Scrollbar(function_string_frame)
widget_function_text = Text(function_string_frame, bg='white', bd=5, width=45, yscrollcommand=widget_function_yscrollbar.set, state=DISABLED)
widget_function_text.pack(side='left', fill=Y)
widget_function_yscrollbar.pack(side='left', fill=Y)
widget_function_yscrollbar.config(command=widget_function_text.yview)
#
front_persp_UI_frame.pack(side='left')
#
cam_file_frame = Frame(front_persp_UI_frame)
widget_cam_menu_button = Menubutton(cam_file_frame,text="output format", relief=RAISED)
widget_cam_menu_button.pack(side='left')
widget_cam_menu = Menu(widget_cam_menu_button)
widget_cam_menu.add_command(label='.epi (Epilog)',command=select_epi)
widget_cam_menu.add_command(label='.camm (CAMM)',command=select_camm)
widget_cam_menu.add_command(label='.rml (Modela)',command=select_rml)
widget_cam_menu.add_command(label='.ps (Postscript)',command=select_ps)
widget_cam_menu.add_command(label='.ord (OMAX)',command=select_ord)
widget_cam_menu.add_command(label='.oms (Resonetics)',state=DISABLED)
widget_cam_menu.add_command(label='.stl (STL)',state=DISABLED)
widget_cam_menu.add_command(label='.uni (Universal)',state=DISABLED)
widget_cam_menu.add_command(label='.g (G codes)',state=DISABLED)
widget_cam_menu.add_command(label='.jpg (JPG)',state=DISABLED)
widget_cam_menu_button['menu'] = widget_cam_menu
Label(cam_file_frame, text=" output file: ").pack(side='left')
string_cam_file = StringVar()
widget_cam_file = Entry(cam_file_frame, width=12, bg='white', textvariable=string_cam_file)
widget_cam_file.pack(side='left')
Label(cam_file_frame, text=" ").pack(side='left')
widget_cam_save = Button(cam_file_frame, text="save")
widget_cam_save.bind('<Button-1>',save_cam)
widget_cam_save.pack()
#
cam_vector_frame = Frame(front_persp_UI_frame)
Label(cam_vector_frame, text="maximum vector fit error: ").pack(side='left')
string_vector_error = StringVar()
string_vector_error.set('0.0005')
widget_vector_error = Entry(cam_vector_frame, width=6, bg='white', textvariable=string_vector_error)
widget_vector_error.pack(side='left')
#
cam_dia_frame = Frame(front_persp_UI_frame)
Label(cam_dia_frame, text="tool diameter: ").pack(side='left')
string_tool_dia = StringVar()
string_tool_dia.set('0')
widget_tool_dia = Entry(cam_dia_frame, width=6, bg='white', textvariable=string_tool_dia)
widget_tool_dia.pack(side='left')
Label(cam_dia_frame, text=" tool overlap: ").pack(side='left')
string_tool_overlap = StringVar()
string_tool_overlap.set('0.5')
widget_tool_overlap = Entry(cam_dia_frame, width=6, bg='white', textvariable=string_tool_overlap)
widget_tool_overlap.pack(side='left')
#
cam_contour_frame = Frame(front_persp_UI_frame)
Label(cam_contour_frame, text=" # contours (-1 for max): ").pack(side='left')
string_num_contours = StringVar()
string_num_contours.set('0')
widget_num_contours = Entry(cam_contour_frame, width=6, bg='white', textvariable=string_num_contours)
widget_num_contours.pack(side='left')
Label(cam_contour_frame, text=" ").pack(side='left')
widget_cam_contour = Button(cam_contour_frame, text="contour")
widget_cam_contour.pack(side='left')
widget_cam_contour.bind('<Button-1>',contour)
#
laser_frame1 = Frame(front_persp_UI_frame)
Label(laser_frame1, text=" power:").pack(side="left")
string_laser_power = StringVar()
Entry(laser_frame1, width=6, bg='white', textvariable=string_laser_power).pack(side="left")
Label(laser_frame1, text=" speed:").pack(side="left")
string_laser_speed = StringVar()
Entry(laser_frame1, width=6, bg='white', textvariable=string_laser_speed).pack(side="left")
Label(laser_frame1, text=" rate: ").pack(side="left")
string_laser_rate = StringVar()
Entry(laser_frame1, width=6, bg='white', textvariable=string_laser_rate).pack(side="left")
#
laser_frame2 = Frame(front_persp_UI_frame)
integer_laser_autofocus = IntVar()
widget_autofocus = Checkbutton(laser_frame2, text="Auto Focus", variable=integer_laser_autofocus).pack()
#
cut_frame = Frame(front_persp_UI_frame)
Label(cut_frame, text="force: ").pack(side="left")
string_cut_force = StringVar()
Entry(cut_frame, width=6, bg='white', textvariable=string_cut_force).pack(side="left")
Label(cut_frame, text=" velocity:").pack(side="left")
string_cut_velocity = StringVar()
Entry(cut_frame, width=6, bg='white', textvariable=string_cut_velocity).pack(side="left")
#
speed_frame = Frame(front_persp_UI_frame)
Label(speed_frame, text="xy speed:").pack(side="left")
string_xy_speed = StringVar()
Entry(speed_frame, width=6, bg='white', textvariable=string_xy_speed).pack(side="left")
Label(speed_frame, text=" z speed:").pack(side="left")
string_z_speed = StringVar()
Entry(speed_frame, width=6, bg='white', textvariable=string_z_speed).pack(side="left")
#
waterjet_frame = Frame(front_persp_UI_frame)
Label(waterjet_frame,text="lead-in/out: ").pack(side="left")
string_lead_in = StringVar()
widget_lead_in = Entry(waterjet_frame, width=4, bg='white', textvariable=string_lead_in)
widget_lead_in.pack(side="left")
Label(waterjet_frame,text="quality: ").pack(side="left")
string_quality = StringVar()
widget_quality = Entry(waterjet_frame, width=4, bg='white', textvariable=string_quality)
widget_quality.pack(side="left")

#
# define .cad template
#
cad_template = """#
# .cad template
# must define cad.function
#   (at end of file)
#

#
# define shapes and transformation
#
# circle(x0, y0, r)
# cylinder(x0, y0, z0, z1, r)
# sphere(x0, y0, z0, r)
# torus(x0, y0, z0, r0, r1)
# rectangle(x0, x1, y0, y1)
# cube(x0, x1, y0, y1, z0, z1)
# function(Z_of_XY)
# functions(upper_Z_of_XY,lower_Z_of_XY)
# add(part1, part2)
# subtract(part1, part2)
# intersect(part1, part2)
# move(part,dx,dy)
# translate(part,dx,dy,dz)
# rotate(part, angle)
# rotate_x(part, angle)
# rotate_y(part, angle)
# rotate_z(part, angle)
# rotate_z_90(part)
# rotate_z_180(part)
# rotate_z_270(part)
# reflect_x(part)
# reflect_y(part)
# reflect_z(part)
# reflect_xy(part)
# reflect_xz(part)
# reflect_yz(part)
# scale_x(part, x0, sx)
# scale_y(part, y0, sy)
# scale_z(part, z0, sz)
# scale_xy(part, x0, y0, sxy)
# scale_xyz(part, x0, y0, z0, sxyz)
# coscale_x_y(part, x0, y0, y1, angle0, angle1, amplitude, offset)
# coscale_x_z(part, x0, z0, z1, angle0, angle1, amplitude, offset)
# coscale_xy_z(part, x0, y0, z0, z1, angle0, angle1, amplitude, offset)
# taper_x_y(part, x0, y0, y1, s0, s1)
# taper_x_z(part, x0, z0, z1, s0, s1)
# taper_xy_z(part, x0, y0, z0, z1, s0, s1)
# shear_x_y(part, y0, y1, dx0, dx1)
# shear_x_z(part, z0, z1, dx0, dx1)
# (more to come)

# coshear

def circle(x0, y0, r):
   part = "(((X-x0)**2 + (Y-y0)**2) <= r**2)"
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'r',str(r))
   return part

def cylinder(x0, y0, z0, z1, r):
   part = "(((X-x0)**2 + (Y-y0)**2 <= r**2) & (Z >= z0) & (Z <= z1))"
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'r',str(r))
   return part

def sphere(x0, y0, z0, r):
   part = "(((X-x0)**2 + (Y-y0)**2 + (Z-z0)**2) <= r**2)"
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'r',str(r))
   return part

def torus(x0, y0, z0, r0, r1):
   part = "(((r0 - sqrt((X-x0)**2 + (Y-y0)**2))**2 + (Z-z0)**2) <= r1**2)"
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'r0',str(r0))
   part = replace(part,'r1',str(r1))
   return part

def rectangle(x0, x1, y0, y1):
   part = "((X >= x0) & (X <= x1) & (Y >= y0) & (Y <= y1))"
   part = replace(part,'x0',str(x0))
   part = replace(part,'x1',str(x1))
   part = replace(part,'y0',str(y0))
   part = replace(part,'y1',str(y1))
   return part

def cube(x0, x1, y0, y1, z0, z1):
   part = "((X >= x0) & (X <= x1) & (Y >= y0) & (Y <= y1) & (Z >= z0) & (Z <= z1))"
   part = replace(part,'x0',str(x0))
   part = replace(part,'x1',str(x1))
   part = replace(part,'y0',str(y0))
   part = replace(part,'y1',str(y1))
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   return part

def function(Z_of_XY):
   part = '(Z <= '+Z_of_XY+')'
   return part

def functions(upper_Z_of_XY,lower_Z_of_XY):
   part = '(Z <= '+upper_Z_of_XY+') & (Z >= '+lower_Z_of_XY+')'
   return part

def add(part1, part2):
   part = "(part1) | (part2)"
   part = replace(part,'part1',part1)
   part = replace(part,'part2',part2)
   return part

def subtract(part1, part2):
   part = "(part1) & ~(part2)"
   part = replace(part,'part1',part1)
   part = replace(part,'part2',part2)
   return part

def intersect(part1, part2):
   part = "(part1) & (part2)"
   part = replace(part,'part1',part1)
   part = replace(part,'part2',part2)
   return part

def move(part,dx,dy):
   part = replace(part,'X','(X-'+str(dx)+')')
   part = replace(part,'Y','(Y-'+str(dy)+')')
   return part   

def translate(part,dx,dy,dz):
   part = replace(part,'X','(X-'+str(dx)+')')
   part = replace(part,'Y','(Y-'+str(dy)+')')
   part = replace(part,'Z','(Z-'+str(dz)+')')
   return part   

def rotate(part, angle):
   angle = angle*pi/180
   part = replace(part,'X','(cos(angle)*X+sin(angle)*y)')
   part = replace(part,'Y','(-sin(angle)*X+cos(angle)*y)')
   part = replace(part,'y','Y')
   part = replace(part,'angle',str(angle))
   return part

def rotate_x(part, angle):
   angle = angle*pi/180
   part = replace(part,'Y','(cos(angle)*Y+sin(angle)*z)')
   part = replace(part,'Z','(-sin(angle)*Y+cos(angle)*z)')
   part = replace(part,'z','Z')
   part = replace(part,'angle',str(angle))
   return part

def rotate_y(part, angle):
   angle = angle*pi/180
   part = replace(part,'X','(cos(angle)*X+sin(angle)*z)')
   part = replace(part,'Z','(-sin(angle)*X+cos(angle)*z)')
   part = replace(part,'z','Z')
   part = replace(part,'angle',str(angle))
   return part

def rotate_z(part, angle):
   angle = angle*pi/180
   part = replace(part,'X','(cos(angle)*X+sin(angle)*y)')
   part = replace(part,'Y','(-sin(angle)*X+cos(angle)*y)')
   part = replace(part,'y','Y')
   part = replace(part,'angle',str(angle))
   return part

def rotate_z_90(part):
   part = reflect_xy(part)
   part = reflect_y(part)
   return part

def rotate_z_180(part):
   part = reflect_xy(part)
   part = reflect_y(part)
   part = reflect_xy(part)
   part = reflect_y(part)
   return part

def rotate_z_270(part):
   part = reflect_xy(part)
   part = reflect_y(part)
   part = reflect_xy(part)
   part = reflect_y(part)
   part = reflect_xy(part)
   part = reflect_y(part)
   return part

def reflect_x(part):
   part = replace(part,'X','-X')
   return part

def reflect_y(part):
   part = replace(part,'Y','-Y')
   return part

def reflect_z(part):
   part = replace(part,'Z','-Z')
   return part

def reflect_xy(part):
   part = replace(part,'X','temp')
   part = replace(part,'Y','X')
   part = replace(part,'temp','Y')
   return part

def reflect_xz(part):
   part = replace(part,'X','temp')
   part = replace(part,'Z','X')
   part = replace(part,'temp','Z')
   return part

def reflect_yz(part):
   part = replace(part,'Y','temp')
   part = replace(part,'Z','Y')
   part = replace(part,'temp','Z')
   return part

def scale_x(part, x0, sx):
   part = replace(part,'X','(x0 + (X-x0)/sx)')
   part = replace(part,'x0',str(x0))
   part = replace(part,'sx',str(sx))
   return part

def scale_y(part, y0, sy):
   part = replace(part,'Y','(y0 + (Y-y0)/sy)')
   part = replace(part,'y0',str(y0))
   part = replace(part,'sy',str(sy))
   return part

def scale_z(part, z0, sz):
   part = replace(part,'Z','(z0 + (Z-z0)/sz)')
   part = replace(part,'z0',str(z0))
   part = replace(part,'sz',str(sz))
   return part

def scale_xy(part, x0, y0, sxy):
   part = replace(part,'X','(x0 + (X-x0)/sx)')
   part = replace(part,'Y','(y0 + (Y-y0)/sy)')
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'sxy',str(sxy))
   return part

def scale_xyz(part, x0, y0, z0, sxy):
   part = replace(part,'X','(x0 + (X-x0)/sx)')
   part = replace(part,'Y','(y0 + (Y-y0)/sy)')
   part = replace(part,'Z','(z0 + (Z-z0)/sz)')
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'sxyz',str(sxyz))
   return part

def coscale_x_y(part, x0, y0, y1, angle0, angle1, amplitude, offset):
   phase0 = pi*angle0/180.
   phase1 = pi*angle1/180.
   part = replace(part,'X','(x0 + (X-x0)/(offset + amplitude*cos(phase0 + (phase1-phase0)*(Y-y0)/(y1-y0))))')
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'y1',str(y1))
   part = replace(part,'phase0',str(phase0))
   part = replace(part,'phase1',str(phase1))
   part = replace(part,'amplitude',str(amplitude))
   part = replace(part,'offset',str(offset))
   return part

def coscale_x_z(part, x0, z0, z1, angle0, angle1, amplitude, offset):
   phase0 = pi*angle0/180.
   phase1 = pi*angle1/180.
   part = replace(part,'X','(x0 + (X-x0)/(offset + amplitude*cos(phase0 + (phase1-phase0)*(Z-z0)/(z1-z0))))')
   part = replace(part,'x0',str(x0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'phase0',str(phase0))
   part = replace(part,'phase1',str(phase1))
   part = replace(part,'amplitude',str(amplitude))
   part = replace(part,'offset',str(offset))
   return part

def coscale_xy_z(part, x0, y0, z0, z1, angle0, angle1, amplitude, offset):
   phase0 = pi*angle0/180.
   phase1 = pi*angle1/180.
   part = replace(part,'X','(x0 + (X-x0)/(offset + amplitude*cos(phase0 + (phase1-phase0)*(Z-z0)/(z1-z0))))')
   part = replace(part,'Y','(y0 + (Y-y0)/(offset + amplitude*cos(phase0 + (phase1-phase0)*(Z-z0)/(z1-z0))))')
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'phase0',str(phase0))
   part = replace(part,'phase1',str(phase1))
   part = replace(part,'amplitude',str(amplitude))
   part = replace(part,'offset',str(offset))
   return part

def taper_x_y(part, x0, y0, y1, s0, s1):
   part = replace(part,'X','(x0 + (X-x0)*(y1-y0)/(s1*(Y-y0) + s0*(y1-Y)))')
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'y1',str(y1))
   part = replace(part,'s0',str(s0))
   part = replace(part,'s1',str(s1))
   return part

def taper_x_z(part, x0, z0, z1, s0, s1):
   part = replace(part,'X','(x0 + (X-x0)*(z1-z0)/(s1*(Z-z0) + s0*(z1-Z)))')
   part = replace(part,'x0',str(x0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'s0',str(s0))
   part = replace(part,'s1',str(s1))
   return part

def taper_xy_z(part, x0, y0, z0, z1, s0, s1):
   part = replace(part,'X','(x0 + (X-x0)*(z1-z0)/(s1*(Z-z0) + s0*(z1-Z)))')
   part = replace(part,'Y','(y0 + (Y-y0)*(z1-z0)/(s1*(Z-z0) + s0*(z1-Z)))')
   part = replace(part,'x0',str(x0))
   part = replace(part,'y0',str(y0))
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'s0',str(s0))
   part = replace(part,'s1',str(s1))
   return part

def shear_x_y(part, y0, y1, dx0, dx1):
   part = replace(part,'X','(X - dx0 - (dx1-dx0)*(Y-y0)/(y1-y0))')
   part = replace(part,'y0',str(y0))
   part = replace(part,'y1',str(y1))
   part = replace(part,'dx0',str(dx0))
   part = replace(part,'dx1',str(dx1))
   return part

def shear_x_z(part, z0, z1, dx0, dx1):
   part = replace(part,'X','(X - dx0 - (dx1-dx0)*(Z-z0)/(z1-z0))')
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'dx0',str(dx0))
   part = replace(part,'dx1',str(dx1))
   return part

def coshear_x_z(part, z0, z1, angle0, angle1, amplitude, offset):
   phase0 = pi*angle0/180.
   phase1 = pi*angle1/180.
   part = replace(part,'X','(X - offset - amplitude*cos(phase0 + (phase1-phase0)*(Z-z0)/(z1-z0)))')
   part = replace(part,'z0',str(z0))
   part = replace(part,'z1',str(z1))
   part = replace(part,'phase0',str(phase0))
   part = replace(part,'phase1',str(phase1))
   part = replace(part,'amplitude',str(amplitude))
   part = replace(part,'offset',str(offset))
   return part

#
# define part
#

d = .5
teapot = cylinder(0,0,-d,d,d)
teapot = coscale_xy_z(teapot,0,0,-d,d,-90,90,.5,.75)

handle = torus(0,0,0,3.5*d/5.,d/10.)
handle = reflect_xz(handle)
handle = reflect_xy(handle)
handle = scale_x(handle,0,.75)
handle = scale_y(handle,0,3)
handle = translate(handle,-6*d/5.,0,0)
teapot = add(teapot,handle)

spout = torus(2.1*d,-.2*d,0,1.1*d,.2*d)
spout = reflect_yz(spout)
spout = intersect(spout,cube(-3*d,1.8*d,-3*d,3*d,0,3*d))
teapot = add(teapot,spout)

interior = cylinder(0,0,.1-d,.1+d,d-.1)
interior = coscale_xy_z(interior,0,0,-d,d,-90,90,.5,.75)
teapot = subtract(teapot,interior)

spout_interior = torus(2.1*d,-.2*d,0,1.1*d,.15*d)
spout_interior = reflect_yz(spout_interior)
spout_interior = intersect(spout_interior,cube(-3*d,1.8*d,-3*d,3*d,0,3*d))
teapot = subtract(teapot,spout_interior)

teapot = subtract(teapot,cube(0,3*d,-3*d,0,-3*d,3*d))

#
# define limits and parameters
#

width = 2.5
x0 = 0
y0 = 0
z0 = 0
cad.xmin = x0-width/2. # min x to render
cad.xmax = x0+width/2. # max x to render
cad.ymin = y0-width/2. # min y to render
cad.ymax = y0+width/2. # max y to render
cad.zmin = z0-width/2. # min z to render
cad.zmax = z0+width/2. # max x to render
cad.rx = 30 # x view rotation (degrees)
cad.rz = 20 # z view rotation (degrees)
nxy = 200
cad.nx = nxy # x points to render
cad.ny = nxy # y points to render
cad.nz = 200 # z points to render

#
# assign part to cad.function
#

cad.function = teapot

"""


#
# read input file if on command line, otherwise use template
#
if len(sys.argv) == 2:
   filename = sys.argv[1]
   string_input_file.set(filename)
   if (find(filename,'.cad') != -1):
      cad_load(0)
   elif ((find(filename,'.jpg') != -1) | (find(filename,'.JPG') != -1) |
      (find(filename,'.png') != -1) | (find(filename,'.PNG') != -1) |
      (find(filename,'.gif') != -1) | (find(filename,'.GIF') != -1) |
      (find(filename,'.tif') != -1) | (find(filename,'.TIF') != -1) |
      (find(filename,'.tiff') != -1) | (find(filename,'.TIFF') != -1) |
      (find(filename,'.bmp') != -1) | (find(filename,'.BMP') != -1)):
      image_load(0)
   else:
      string_msg.set("unsupported input file format")
      root.update()
else:
   widget_cad_text.insert("1.0",cad_template)

#
# start GUI
#

root.mainloop()
