#!/usr/bin/python

################################################################################
#                                                                              #
#  gcode generator for drilling holes read from the hole layer of a dxf file   #
#    Copyright (C) 2016 Phillip A Carter (phillcarter54_at_gmail_dot_com_)     #
#                                                                              #
#    This program is free software; you can redistribute it and/or modify      #
#    it under the terms of the GNU General Public License as published by      #
#    the Free Software Foundation; either version 2 of the License, or         #
#    (at your option) any later version.                                       #
#                                                                              #
#    This program is distributed in the hope that it will be useful,           #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of            #
#    MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              #
#    GNU General Public License for more details.                              #
#                                                                              #
#    You should have received a copy of the GNU General Public License         #
#    along with this program; if not, write to the Free Software Foundation,   #
#    51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             #
#                                                                              #
################################################################################

from Tkinter import *
from os import path, environ
from sys import platform
from math import sqrt, atan, sin
from time import strftime
from ConfigParser import SafeConfigParser, NoOptionError, Error
from tkFileDialog import asksaveasfilename, askopenfilename
import tkMessageBox

'''set up precision and tolerance'''
precisionMetric=3
precisionInch=4
pathToleranceMetric=0.025
pathToleranceInch=0.001

'''get environment parameters'''
iniFileName=path.dirname(sys.argv[0])+'/dxf2holes.ini'

'''check if we were called from Axis'''
if environ.has_key('AXIS_PROGRESS_BAR'):
    axisState='normal'
else:
    axisState='disabled'

'''set column widths'''
xBlank=5                            #width of blank coumns
xNarrow=68                          #width of columns 1 & 3
xWide=130                           #width of columns 2 & 4
xGcode=xBlank*3+xNarrow*2+xWide*2   #width of gcode output box

'''set column place positions'''
xCol1=xBlank+xNarrow                #right edge of column 1
xCol2=xCol1+xBlank                  #left edge of column 2
xCol3=xCol2+xWide+xBlank+xNarrow    #right edge of column 3
xCol4=xCol3+xBlank                  #left edge of column 4

'''set row heights'''
yBlank=5                            #height of blank rows
yNormal=20                          #height of normalrows
yGcode=320                          #height of gcode output box

'''set row offset'''
yRow=25                             #y offset for rows

'''set number of rows used'''
yUsed=8                             #used number of rows

'''set window dimensions'''
xWindow=xGcode+(xBlank*2)                  #width of window
yWindow=yBlank*2+(yRow*(yUsed+1))+yGcode   #height of window

'''set window position'''
xOffset=200                         #x window position from top left
yOffset=100                         #y window position from top left

class Application(Frame):
    def __init__(self,master=None):
        Frame.__init__(self,master)
        self.window=master
        self.window.title('dxf2holes.py gcode generator')
        self.window.geometry('%dx%d+%d+%d' %(xWindow,yWindow,xOffset,yOffset))
        self.pack(fill=BOTH, expand=1)
        self.dxfFile=''
        self.do_initialization()
        self.create_inputs()
        self.create_outputs()
        self.create_menu()
        if len(sys.argv)>1:
            self.dxfFilename=sys.argv[1]
            (self.openDirPath, self.dxfFile) = path.split(self.dxfFilename)
            self.generate_gcode()

### read the initialization values #############################################
    def do_initialization(self):
        '''set some defaults in case of missing or invalid ini file'''
        self.iniDirPath='linuxcnc/'
        self.iniSystemUnits='mm'
        self.iniSortOrder='x'
        self.iniDrillDepth=0
        self.iniDrillFeed=50
        self.iniSafeZ=5
        self.iniStartZ=0
        self.iniSpindleSpeed=0
        self.iniMistCool=0
        self.iniFloodCool=0
        self.iniPreamble='(preamble)'
        self.iniPostamble='(postamble)'
        '''read settings from ini file'''
        config=SafeConfigParser()
        try:
            config.read([iniFileName])
            self.iniDirPath = config.get('MAIN','FILEPATH')
            self.iniSystemUnits=config.get('MAIN','SYSTEMUNITS')
            self.iniSortOrder=config.get('MAIN','SORTORDER')
            self.iniDrillDepth=config.get('MAIN','DRILLDEPTH')
            self.iniDrillFeed=config.get('MAIN','DRILLFEED')
            self.iniSafeZ=config.get('MAIN','SAFEZ')
            self.iniStartZ=config.get('MAIN','STARTZ')
            self.iniSpindleSpeed=config.get('MAIN','SPINDLESPEED')
            self.iniMistCool=config.get('MAIN','MISTCOOL')
            self.iniFloodCool=config.get('MAIN','FLOODCOOL')
            self.iniPreamble=config.get('MAIN','PREAMBLE')
            self.iniPostamble=config.get('MAIN','POSTAMBLE')
        except:
            print 'ERROR in INI file, using defaults'

### setup the inputs ###########################################################
    def create_inputs(self):
        '''select the system units'''
        row=yBlank
        row=yRow
        self.systemUnits=StringVar()
        self.systemUnits.set(self.iniSystemUnits)
        Radiobutton(self,text='MM',variable=self.systemUnits,value='mm').place(x=xCol1,y=row,anchor=E)
        Radiobutton(self,text='Inch   \'Units\'',variable=self.systemUnits,value='inch').place(x=xCol2,y=row,anchor=W)
        '''select the sort order'''
        self.sortOrder=StringVar()
        self.sortOrder.set(self.iniSortOrder)
        Radiobutton(self,text='X',variable=self.sortOrder,value='x').place(x=xCol3,y=row,anchor=E)
        Radiobutton(self,text='Y   \'Sort Order\'',variable=self.sortOrder,value='y').place(x=xCol4,y=row,anchor=W)
        '''set the drill depth rate'''
        row=row+yRow
        self.drillDepth=DoubleVar()
        self.drillDepth.set(self.iniDrillDepth)
        self.drillDepth_entry=Entry(self,justify=RIGHT,textvariable=self.drillDepth)
        self.drillDepth_entry.place(anchor=E,x=xCol1,y=row,height=yNormal,width=xNarrow)
        self.drillDepth_label=Label(self,anchor=W,text='Drill Depth')
        self.drillDepth_label.place(anchor=W,x=xCol2,y=row,height=yNormal,width=xWide)
        '''set the drill feed rate'''
        self.drillFeed=IntVar()
        self.drillFeed.set(self.iniDrillFeed)
        self.drillFeed_entry=Entry(self,justify=RIGHT,textvariable=self.drillFeed)
        self.drillFeed_entry.place(anchor=E,x=xCol3,y=row,height=yNormal,width=xNarrow)
        self.drillFeed_label=Label(self,anchor=W,text='Drill Feed Rate')
        self.drillFeed_label.place(anchor=W,x=xCol4,y=row,height=yNormal,width=xWide)
        '''set the z start postion'''
        row=row+yRow
        self.startZ=DoubleVar()
        self.startZ.set(self.iniStartZ)
        self.startZ_entry=Entry(self,justify=RIGHT,textvariable=self.startZ)
        self.startZ_entry.place(anchor=E,x=xCol1,y=row,height=yNormal,width=xNarrow)
        self.startZ_label=Label(self,anchor=W,text='Z Start')
        self.startZ_label.place(anchor=W,x=xCol2,y=row,height=yNormal,width=xWide)
        '''set the z safe distance'''
        self.safeZ=DoubleVar()
        self.safeZ.set(self.iniSafeZ)
        self.safeZ_entry=Entry(self,justify=RIGHT,textvariable=self.safeZ)
        self.safeZ_entry.place(anchor=E,x=xCol3,y=row,height=yNormal,width=xNarrow)
        self.safeZ_label=Label(self,anchor=W,text='Z Safe')
        self.safeZ_label.place(anchor=W,x=xCol4,y=row,height=yNormal,width=xWide)
        '''set the spindle speed'''
        row=row+yRow
        self.spindleSpeed=IntVar()
        self.spindleSpeed.set(self.iniSpindleSpeed)
        self.spindleSpeed_entry=Entry(self,justify=RIGHT,textvariable=self.spindleSpeed)
        self.spindleSpeed_entry.place(anchor=E,x=xCol3,y=row,height=yNormal,width=xNarrow)
        self.spindleSpeed_entry.focus_set()
        self.spindleSpeed_entry.icursor(END)
        self.spindleSpeed_entry.select_range(0,END)
        self.spindleSpeed_label=Label(self,anchor=W,text='Spindle Speed')
        self.spindleSpeed_label.place(anchor=W,x=xCol4,y=row,height=yNormal,width=xWide)
        '''is mist coolant required'''
        row=row+yRow
        self.mistCool=IntVar()
        self.mistCool.set(self.iniMistCool)
        self.mistCool_entry=Checkbutton(self,variable=self.mistCool)
        self.mistCool_entry.place(anchor=E,x=xCol1,y=row,height=yNormal)
        self.mistCool_label=Label(self,anchor=W,text='Mist coolant')
        self.mistCool_label.place(anchor=W,x=xCol2,y=row,height=yNormal,width=xWide)
        '''is flood coolant required'''
        self.floodCool=IntVar()
        self.floodCool.set(self.iniFloodCool)
        self.floodCool_entry=Checkbutton(self,variable=self.floodCool)
        self.floodCool_entry.place(anchor=E,x=xCol3,y=row,height=yNormal)
        self.floodCool_label=Label(self,anchor=W,text='Flood coolant')
        self.floodCool_label.place(anchor=W,x=xCol4,y=row,height=yNormal,width=xWide)
        '''select the preamble'''
        row=row+yRow
        self.preamble=StringVar()
        self.preamble.set(self.iniPreamble)
        self.preamble_label=Label(self,anchor=W,text='Preamble')
        self.preamble_label.place(anchor=E,x=xCol1,y=row,height=yNormal,width=xNarrow)
        self.preamble_entry=Entry(self,justify=LEFT,textvariable=self.preamble)
        self.preamble_entry.place(anchor=W,x=xCol2,y=row,height=yNormal,width=xBlank*2+xNarrow+xWide*2)
        '''select the postamble'''
        row=row+yRow
        self.postamble=StringVar()
        self.postamble.set(self.iniPostamble)
        self.postamble_label=Label(self,anchor=W,text='Postamble')
        self.postamble_label.place(anchor=E,x=xCol1,y=row,height=yNormal,width=xNarrow)
        self.postamble_entry=Entry(self,justify=LEFT,textvariable=self.postamble)
        self.postamble_entry.place(anchor=W,x=xCol2,y=row,height=yNormal,width=xBlank*2+xNarrow+xWide*2)
        '''set the default path'''
        row=row+yRow
        self.dirPath=StringVar()
        self.dirPath.set(self.iniDirPath)
        self.dirPath_label=Label(self,anchor=W,text='File Path')
        self.dirPath_label.place(anchor=E,x=xCol1,y=row,height=yNormal,width=xNarrow)
        self.dirPath_entry=Entry(self,justify=LEFT,textvariable=self.dirPath)
        self.dirPath_entry.place(anchor=W,x=xCol2,y=row,height=yNormal,width=xBlank*2+xNarrow+xWide*2)

### setup the outputs ##########################################################
    def create_outputs(self):
        '''sets up the gcode output screen'''
        self.gcodeBox=Text(self,bg='lightgrey')
        self.gcodeBox.place(x=xBlank,y=yRow*(yUsed+1),height=yGcode,width=xGcode)

### setup the menuBar ###########################################################
    def create_menu(self):
        '''sets up the menu bar'''
        top = self.winfo_toplevel()
        self.menuBar=Menu(top)
        top['menu']=self.menuBar
        self.fileMenu=Menu(self.menuBar,tearoff=0)
        self.fileMenu.add_command(label='Open DXF',command=self.get_dxf_filename)
        self.fileMenu.add_command(label='Save NGC',command=self.save_gcode)
        self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Write to AXIS',command=self.write_to_axis,state=axisState)
        self.fileMenu.add_separator()
        self.fileMenu.add_command(label='Exit',command=self.good_bye)
        self.menuBar.add_cascade(label='File',menu=self.fileMenu)
        self.editMenu=Menu(self.menuBar, tearoff=0)
        self.editMenu.add_command(label='Regenerate G-Code',command=self.generate_gcode)
        self.editMenu.add_command(label='Copy to Clipboard',command=self.copy_gcode)
        self.menuBar.add_cascade(label='Edit',menu=self.editMenu)
        self.settingsMenu=Menu(self.menuBar, tearoff=0)
        self.settingsMenu.add_command(label='Save',command=self.write_ini_file)
        self.menuBar.add_cascade(label='Settings',menu=self.settingsMenu)
        self.infoMenu=Menu(self.menuBar, tearoff=0)
        self.infoMenu.add_command(label='About',command=lambda:self.show_message('about'))
        self.infoMenu.add_command(label='Info',command=lambda:self.show_message('info'))
        self.menuBar.add_cascade(label='Info',menu=self.infoMenu)

### write g-code to axis and exit ##############################################
    def write_to_axis(self):
        if self.dxfFile:
            sys.stdout.write(self.gcodeBox.get(0.0,END))
            self.quit()

### save g-code ################################################################
    def save_gcode(self):
        if self.dxfFile:
            
            self.ngcFileName=asksaveasfilename(initialdir=self.openDirPath,\
                             initialfile=[(path.basename(self.dxfFile).\
                             replace('dxf','ngc')).replace(' ','_')],\
                             filetypes=[('ngc files','.ngc')])
            if self.ngcFileName:
                (self.openDirPath, self.ngcFile) = path.split(self.ngcFileName)
                ngcFile=open(self.ngcFileName,'w')
                ngcFile.write(self.gcodeBox.get(0.0,END))
                ngcFile.close()

### copy g-code to clipboard ###################################################
    def copy_gcode(self):
        self.gcodeBox.clipboard_clear()
        self.gcodeBox.clipboard_append(self.gcodeBox.get(0.0,END))

### exit #######################################################################
    def good_bye(self):
        self.gcodeBox.delete(1.0,END)
        sys.stdout.write('M2')
        self.quit()

### get the filename to open ################################################
    def get_dxf_filename(self):
        self.dxfFilename=askopenfilename(title='DXF File to convert',\
                         initialdir=self.dirPath.get(),\
                         filetypes=[('dxf files','.dxf')])
        if self.dxfFilename:
            (self.openDirPath, self.dxfFile) = path.split(self.dxfFilename)
            self.gcodeBox.insert(END,'(Drawing file = %s)\n'%(self.dxfFilename))
            self.generate_gcode()

### write the ini file #########################################################
    def write_ini_file(self):
        self.init=SafeConfigParser()
        self.init.add_section('MAIN')
        self.init.set('MAIN','FILEPATH',str(self.dirPath.get()))
        self.iniDirPath=str(self.dirPath.get())
        self.init.set('MAIN','SYSTEMUNITS',str(self.systemUnits.get()))
        self.init.set('MAIN','SORTORDER',str(self.sortOrder.get()))
        self.init.set('MAIN','DRILLDEPTH',str(self.drillDepth.get()))
        self.init.set('MAIN','DRILLFEED',str(self.drillFeed.get()))
        self.init.set('MAIN','SAFEZ',str(self.safeZ.get()))
        self.init.set('MAIN','STARTZ',str(self.startZ.get()))
        self.init.set('MAIN','SPINDLESPEED',str(self.spindleSpeed.get()))
        self.init.set('MAIN','MISTCOOL',str(self.mistCool.get()))
        self.init.set('MAIN','FLOODCOOL',str(self.floodCool.get()))
        self.init.set('MAIN','PREAMBLE',str(self.preamble.get()))
        self.init.set('MAIN','POSTAMBLE',str(self.postamble.get()))
        iniFile=open(iniFileName,'wb')
        self.init.write(iniFile)
        iniFile.close()

### generate g-code ############################################################
    def generate_gcode(self):
        if self.systemUnits.get()=='mm':
            self.gcodeUnits='G21'
            self.precision=precisionMetric
            pathTolerance=pathToleranceMetric
        else:
            self.gcodeUnits='G20'
            self.precision=precisionInch
            pathTolerance=pathToleranceInch
        self.find_extremities()
        self.write_info()
        if len(self.drillSizes)!=0:
            self.write_preamble()
            self.write_drilling_code()
            self.write_postamble()

### find x,y extremities ###################################################
    def find_extremities(self):
        self.xMinimum=99999
        self.xMaximum=-99999
        self.yMinimum=99999
        self.yMaximum=-99999
        self.drillSizes=[]
        dxfFile=open(self.dxfFilename)
        dxfEntities=dxfFile.next()
        while dxfEntities.strip()!='ENTITIES':
            dxfEntities=dxfFile.next()
        inLine=False
        for readLine in dxfFile:
            readLine=readLine.strip()
            if readLine=='ENDSEC':
                break
            elif inLine==True:
                inData=dict.fromkeys(['8','10','20','40'],0.0) # 8=layer, 10=X, 20=Y, 40=radius
                while readLine!='0':
                    if readLine in inData:
                        inData[readLine]=dxfFile.next().strip()
                    readLine=dxfFile.next().strip()
                if inData['8'].upper()=='HOLES':
                    x=round(float(inData['20']),self.precision)
                    y=round(float(inData['10']),self.precision)
                    holeSize=round((float(inData['40'])*2),self.precision)
                    if holeSize not in self.drillSizes:
                        name='holeSize%s'%(str(holeSize).replace('.',''))
                        setattr(self,name,[])
                        self.drillSizes.append(holeSize)
                    if x<self.xMinimum:
                        self.xMinimum=x
                    if x>self.xMaximum:
                        self.xMaximum=x
                    if y<self.yMinimum:
                        self.yMinimum=y
                    if y>self.yMaximum:
                        self.yMaximum=y
                inLine=False
            else:
                if readLine=='CIRCLE':
                    inLine=True
        dxfFile.close()
        self.drillSizes.sort()

### g-code info ################################################################
    def write_info(self):
        if self.dxfFile:
            self.gcodeBox.delete(1.0,END)
            self.gcodeBox.insert(END,'(generated by dxf2holes.py gcode generator)\n')
            self.gcodeBox.insert(END,'(at %s:%s:%s on %s)\n'%(strftime('%H'),strftime('%M'),\
                                                              strftime('%S'),strftime('%d %b %Y')))
            self.gcodeBox.insert(END,'(from %s)\n'%(self.dxfFilename))
            if len(self.drillSizes)!=0:
                self.gcodeBox.insert(END,'\n(drill sizes required:)\n')
                for drillSize in self.drillSizes:
                    self.gcodeBox.insert(END,'(%s %s)\n'%(drillSize,self.systemUnits.get()))
            else:
                self.gcodeBox.insert(END,'\n(#########################)\n')
                self.gcodeBox.insert(END,'(# No holes found:       #)\n')
                self.gcodeBox.insert(END,'(# Holes must be CIRCLES #)\n')
                self.gcodeBox.insert(END,'(# & on the HOLES layer  #)\n')
                self.gcodeBox.insert(END,'(#########################)\n\n')
                self.gcodeBox.insert(END,'M2\n')
                return
            self.gcodeBox.insert(END,'\n(x minimum = %s)\n'%(self.xMinimum))
            self.gcodeBox.insert(END,'(x maximum = %s)\n'%(self.xMaximum))
            self.gcodeBox.insert(END,'(y minimum = %s)\n'%(self.yMinimum))
            self.gcodeBox.insert(END,'(y maximum = %s)\n'%(self.yMaximum))

### g-code preamble ############################################################
    def write_preamble(self):
        if self.dxfFile:
            self.gcodeBox.insert(END,'\n(*** PREAMBLE ***)\n')
            self.gcodeBox.insert(END,'%s\n'%(self.gcodeUnits))
            for code in self.preamble.get().split():
                self.gcodeBox.insert(END,'%s\n'%(code))
            if self.spindleSpeed.get()!=0:
                self.gcodeBox.insert(END,'M3 S%s\n'%(self.spindleSpeed.get()))
            if self.mistCool.get()==1:
                self.gcodeBox.insert(END,'M7\n')
            if self.floodCool.get()==1:
                self.gcodeBox.insert(END,'M8\n')

### drilling ###################################################################
    def write_drilling_code(self):
        dxfFile=open(self.dxfFilename)
        dxfEntities=dxfFile.next()
        while dxfEntities.strip()!='ENTITIES':
            dxfEntities=dxfFile.next()
        inLine=False
        for readLine in dxfFile:
            readLine=readLine.strip()
            if readLine=='ENDSEC':
                break
            elif inLine==True:
                inData=dict.fromkeys(['8','10','20','40'],0.0)
                while readLine!='0':
                    if readLine in inData:
                        inData[readLine]=dxfFile.next().strip()
                    readLine=dxfFile.next().strip()
                if inData['8'].upper()=='HOLES':
                    x=(round(float(inData['10']),self.precision))
                    y=(round(float(inData['20']),self.precision))
                    holeSize=round((float(inData['40'])*2),self.precision)
                    name='holeSize%s'%(str(holeSize).replace('.',''))
                    getattr(self,name).append([x,y])
                inLine=False
            else:
                if readLine=='CIRCLE':
                    inLine=True
        dxfFile.close()
        for drillSize in self.drillSizes:
            name='holeSize%s'%(str(drillSize).replace('.',''))
            if self.sortOrder.get()=='y':
                getattr(self,name).sort(key=lambda k:(k[0],k[1]))
                sortType=0
            else:
                getattr(self,name).sort(key=lambda k:(k[1],k[0]))
                sortType=1
            holeCentre=[[getattr(self,name)[0][0],getattr(self,name)[0][1]]]
            oldPosition=holeCentre[0][sortType]
            flowDirection=True
            insertionPoint=1
            for hole in range(1,len(getattr(self,name))):
                if getattr(self,name)[hole][sortType]!=oldPosition:
                    flowDirection = not flowDirection
                    oldPosition = getattr(self,name)[hole][sortType]
                if flowDirection:
                    holeCentre.append([getattr(self,name)[hole][0],getattr(self,name)[hole][1]])
                    insertionPoint = hole + 1
                else:
                    holeCentre.insert(insertionPoint,[getattr(self,name)[hole][0],getattr(self,name)[hole][1]])
            holeNumber=1
            self.gcodeBox.insert(END,'\n(*** %.*f %s DRILL ***)\n'%(self.precision,drillSize,self.systemUnits.get()))
            self.gcodeBox.insert(END,'G0 Z%.*f\n'%(self.precision,self.safeZ.get()))
            for hole in range(len(holeCentre)):
                xpos=holeCentre[hole][0]
                ypos=holeCentre[hole][1]
                self.gcodeBox.insert(END,'(Hole # %s)\n'%(holeNumber))
                self.gcodeBox.insert(END,'G0 X%.*f Y%.*f\n'%(self.precision,xpos,self.precision,ypos))
                self.gcodeBox.insert(END,'G0 Z%.*f\n'%(self.precision,self.startZ.get()))
                self.gcodeBox.insert(END,'G1 Z%.*f F%d\n'%(self.precision,\
                                                           self.startZ.get()-self.drillDepth.get(),\
                                                           self.drillFeed.get()))
                self.gcodeBox.insert(END,'G0 Z%.*f\n'%(self.precision,self.safeZ.get()))
                holeNumber+=1

### postamble ##################################################################
    def write_postamble(self):
        self.gcodeBox.insert(END,'\n(*** POSTAMBLE ***)\n')
        if self.mistCool.get()==1 or self.floodCool.get()==1:
            self.gcodeBox.insert(END,'(M9\n')
        if self.spindleSpeed.get():
            self.gcodeBox.insert(END,'M5\n')
        for code in self.postamble.get().split():
            self.gcodeBox.insert(END,'%s\n'%(code))
        self.gcodeBox.insert(END,'M2\n')

### show help ##################################################################
    def show_message(self,message):
        infoMsg='''
 dxf2holes can be called with an optionally named dxf file to open 
 (e.g.,   dxf2holes linuxcnc/projects/samples/sample_holes.dxf) 

 If called directly from Axis it can write gcode directly to Axis 

 Sort Order determines how the holes are drilled, 
 'X' means that a row of holes along x with the same y value are drilled 
 then the next row in increasing y value and so on 
 'Y' means that a column of holes along y with the same x value are 
 drilled then the next column in increasing x value and so on 

 G20/G21 are not required in the preamble as the correct code is 
 generated by the Units setting 

 If any parameters are changed after a dxf file is loaded then 
 'Edit' - 'Regenerate' will need to be selected to update the code 

 'Settings' - 'Save' will save all currently displayed parameters to the 
 ini file (dxf2holes.ini) 

 Code can be edited directly in the code text box 

 If you do not have spindle control then leave 'Spindle Speed' at 0 and 
 no spindle code will be generated 
'''
        aboutMsg='''
 DXF to Gcode Generator for hole patterns \n
 Copyright (C) 2016 Phillip A Carter \n
'''
        aboutGPL='''
 This program is free software; you can redistribute it and/or modify \n
 it under the terms of the GNU General Public License as published by \n
 the Free Software Foundation; either version 2 of the License, or \n
 (at your option) any later version. \n
 This program is distributed in the hope that it will be useful, \n
 but WITHOUT ANY WARRANTY; without even the implied warranty of \n
 MERCHANTIBILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \n
 GNU General Public License for more details. \n
'''
        msg=Toplevel()
        msg.resizable(width=FALSE,height=FALSE)
        msg.geometry('+%d+%d' %(xOffset,yOffset))
        if message=='about':
            msg.title('about dxf2holes.py')
            about=Label(msg,text=aboutMsg,justify=CENTER)
            about.pack()
            gpl=Label(msg,text=aboutGPL,justify=LEFT)
            gpl.pack()
        else:
            msg.title('info for dxf2holes.py')
            info=Label(msg,text=infoMsg,justify=LEFT)
            info.pack()
        button=Button(msg,text='OK',command=msg.destroy)
        button.pack()

### main loop ##################################################################
def main():
    root=Tk()
    app=Application(root)
    root.resizable(width=FALSE, height=FALSE)
    root.mainloop()

if __name__=='__main__':
    main()
