### Python port of W5GFE's Necview Plot Perl/Tk Script
### Barry Newberger W5KH
### 23 Nov 03
### Version 1.0

### Python derivative work copyright Barry S. Newberger

# Original Perl/Tk script:
# copyright July 23, 1999, Bill Walker W5GFE, bw@cs.ecok.edu
# This software is available under the terms of the GNU Public License
#

from Tkinter import *
import math
import sys
import os
import re
import types

class Necview_plot:

    def __init__(self, master, pack_pos, pack_axis, origin_shift, color_list, graph_param) :
    
        master.title("Necview")
        master.maxsize(640,480)
        self.pos_X = pack_pos[0]
        self.pos_Y = pack_pos[1]
        self.Xx = pack_pos[0][0]
        self.Xy = pack_pos[0][1]
        self.Xz = pack_pos[0][2]
        self.Yx = pack_pos[1][0]
        self.Yy = pack_pos[1][1]
        self.Yz = pack_pos[1][2]
        
        self.AX0 = pack_axis[0]
        self.AX1 = pack_axis[1]
        self.AY0 = pack_axis[2]
        self.AY1 = pack_axis[3]
        self.AZ0 = pack_axis[4]
        self.AZ1 = pack_axis[5]
        
        self.Translate = origin_shift
        self.axislength = graph_param[0]
        self.maxaspect = graph_param[1]
        self.colors = color_list
        self.counter = 0      
        self.radfactor = 3.1415926/180.0
        self.theta = 0.
        self.phi = 0.
        self.vert_var =IntVar()
        self.horiz_var = IntVar()
        self.USECOLOR = []
        
################################# frames

        self.canvasframe = Frame(master, background = 'green')
        self.canvasframe.pack(side = 'top')
        
        self.listboxframe = Frame(self.canvasframe)
        self.listboxframe.pack(side = 'right')
    
        self.sliderframe = Frame(master, background = 'green')
        self.sliderframe.pack(side = 'top')
    
        self.bottomframe = Frame(master, background ='green')
        self.bottomframe.pack(side = 'top')
        
        
        self.c = Canvas(self.canvasframe, background = 'yellow',\
            width = 3*self.axislength, height = 3*self.axislength)               
        self.c_sb_horiz = Scrollbar(self.canvasframe, orient = "horizontal", command = self.c.xview)
        self.c_sb_horiz.pack(side = 'bottom', fill = 'x')
        self.c_sb_vert = Scrollbar(self.canvasframe, orient = "vertical", command = self.c.yview)  
        self.c_sb_vert.pack(side = 'right', fill = 'y')
        self.c.pack(side = 'left')
        self.c.configure(yscrollcommand = self.c_sb_vert.set)
        self.c.configure(xscrollcommand = self.c_sb_horiz.set)

        
        self.vertscale = Scale(self.canvasframe, from_ = -90, to = 0, orient = 'vertical',\
            tickinterval = 20, length = 2*self.axislength, variable = self.vert_var,
 	        command = self.scale_handler)
        self.vertscale.pack(side = 'left')
				    
        self.filebox = Listbox(self.listboxframe, width = '20',\
		    height = '10', setgrid = 1)
        self.filebox_sb = Scrollbar(self.listboxframe, orient = 'vertical',\
            command = self.filebox.yview)
        self.filebox_sb.pack(side = 'right', fill = 'y')
        self.filebox.pack(side = 'left')        
        self.filebox.configure(background = 'pink')
        self.filebox.configure(yscrollcommand = self.filebox_sb.set)

        self.horizscale = Scale(self.sliderframe, from_ = 0,  to =  360, orient = 'horizontal',\
            tickinterval = 40, length = 4*self.axislength, variable = self.horiz_var,\
            command = self.scale_handler)
        self.horizscale.pack( side = 'top')
        
################################################

# Create 'quit' button 
        self.quitter = Button(self.bottomframe, text = 'Quit',\
            command = master.destroy)
        self.quitter.pack(side ='left')

#################### a button

        self.filebox.bind ('<Double-1>', self.button_handler);
        
    def loadantenna (self, in_file) :
        
        self.X0 = []
        self.X1 = []
        
        self.Y0 = []
        self.Y1 = []
        
        self.Z0 = []
        self.Z1 = []
        
        RADIUS = []
        
        done = 0
        self.counter = 0
        
        patt1obj = re.compile("^GW")
        patt2obj = re.compile(",")
        patt3obj = re.compile("^[ \t][ \t]*")
        patt4obj = re.compile("[ \t][ \t]*")
        patt5obj = re.compile("^GR")
        
        input = open(in_file)
        while(not done):
            line = input.readline()    
            line.rstrip()
            m = patt1obj.search(line)
            if m != None :
 # next two for somnec input            
                line = patt1obj.sub("GW ", line)
                line = patt2obj.sub(" ",line)
                line = patt3obj.sub('', line)
                line = patt4obj.sub('|',line)
                    
                self.X0.insert(self.counter, 0.)
                self.X1.insert(self.counter, 0.)
        
                self.Y0.insert(self.counter, 0.)
                self.Y1.insert(self.counter, 0.)
        
                self.Z0.insert(self.counter, 0.)
                self.Z1.insert(self.counter, 0.)
                
                RADIUS.insert(self.counter, 0.)
                    
                (GW,tag,segments,self.X0[self.counter], self.Y0[self.counter], self.Z0[self.counter],\
                    self.X1[self.counter], self.Y1[self.counter], self.Z1[self.counter],\
                    RADIUS[self.counter]) = re.split('\|',line)
                self.USECOLOR.append(self.colors[self.counter % len(self.colors)])
                self.counter = self.counter + 1
                
        
            m = patt5obj.search(line)
            if m != None :
   # next two for somnec input
                line = patt5obj.sub("GR ", line)
                line = patt2obj.sub(" " ,line)
                line = patt3obj.sub('', line)
                line = patt4obj.sub('|', line)
                    
                self.X0.insert(self.counter, 0.)
                self.X1.insert(self.counter, 0.)
        
                self.Y0.insert(self.counter, 0.)
                self.Y1.insert(self.counter, 0.)
        
                self.Z0.insert(self.counter, 0.)
                self.Z1.insert(self.counter, 0.)
                
                RADIUS.insert(self.counter, 0.)
             
                (GR, tag, Repeats) = re.split('\|', line)
                Repeats = int(Repeats)
                  
      # the Z axis stuff remains the same, but the X and Y coordinates must change
      # there should be a total of Repeats copies of the structure,
      # not Repeats more copies
      
      # use these angle increments
                increment = 360.0 / Repeats * 3.1415926/180.0
      
      # the structure is in X0,Y0,Z0,Z1,Y1,Z1  [0 .. $counter - 1],
      # Z0 and Z1  and USECOLOR  and RADIUS stay the same
      # step through each of the old points

                specialcounter = self.counter
                newcounter = 0
                    
                while( newcounter < self.counter) :
                    self.X0[newcounter] = float(self.X0[newcounter])
                    self.X1[newcounter] = float(self.X1[newcounter])
                    self.Y0[newcounter] = float(self.Y0[newcounter])
                    self.Y1[newcounter] = float(self.Y1[newcounter])
                    self.Z0[newcounter] = float(self.Z0[newcounter])
                    self.Z1[newcounter] = float(self.Z1[newcounter])
                    
    #repeat entire structure at each angle
                    repeatcounter = 0
    # here are the initial angles for each of the  endpoints of a segment
    
                    theta = 0.
                    theta1 = 0.
                        
                    if (self.X0[newcounter] != 0.0 ) :
                        theta = math.atan2(self.X0[newcounter],self.Y0[newcounter])
                    if (self.X1[newcounter] != 0.0) :
                        theta1 = math.atan2(self.X1[newcounter],self.Y1[newcounter])
                            
                    theta = theta + increment
                    theta1 = theta1 + increment
                        
                    while (repeatcounter < Repeats - 1) :
                        dist0 = math.sqrt(self.X0[newcounter] * self.X0[newcounter]\
                            + self.Y0[newcounter] * self.Y0[newcounter])
                             
                        self.X0.append(dist0 * math.cos(theta))
                        self.Y0.append(dist0 * math.sin(theta))
                        self.Z0.append(self.Z0[newcounter])
                            
                        dist1 = math.sqrt(self.X1[newcounter] * self.X1[newcounter]\
                            + self.Y1[newcounter] * self.Y1[newcounter])
                            
                        self.X1.append(dist1 * math.cos(theta1))
                        self.Y1.append(dist1 * math.sin(theta1))
                        self.Z1.append(self.Z1[newcounter]) 
                            
                        RADIUS.append(RADIUS[newcounter])
                        self.USECOLOR.append(self.USECOLOR[newcounter])
                        specialcounter = specialcounter + 1
                        repeatcounter = repeatcounter + 1
                        theta = theta + increment
                        theta1 = theta1 + increment
                    newcounter = newcounter + 1     
      # fix the counter
      # there should be counter + (Repeats -1) * counter cells now
                    
      # end loop over newcounter
                self.counter = specialcounter                           
                                               
            if (line == ''):
                done = 1
                
    # End of loop over antenna file
    	               
        input.close()
        
        (MAXSCALE, MINSCALE) = maxmin (flatten_tuple((maxmin(self.X0), maxmin(self.Y0), maxmin(self.Z0),\
            maxmin(self.X1), maxmin(self.Y1), maxmin(self.Z1))))
        
        MAXSCALE = float(MAXSCALE)
        MINSCALE = float(MINSCALE)
        
        if (MAXSCALE == MINSCALE) :
            SCALE = self.maxaspect
        else :
            SCALE = self.maxaspect/(MAXSCALE - MINSCALE)

#scale it
        i = 0
        while (i < self.counter) :
#scale it and put it on the graphs

            self.X0[i] =  SCALE * float(self.X0[i])
            self.X1[i] =  SCALE * float(self.X1[i])
            self.Y0[i] =  SCALE * float(self.Y0[i])
            self.Y1[i] =  SCALE * float(self.Y1[i])
            self.Z0[i] =  SCALE * float(self.Z0[i])
            self.Z1[i] =  SCALE * float(self.Z1[i])
            i += 1
        
# -- end of subroutine loadantenna -- #                                           
                                    
    def drawaxes (self) :
    
        self.c.delete('axes')
        
        AX00_v = [[self.AX0[0], self.AY0[0], self.AZ0[0]], self.pos_X, self.pos_Y]
        AX10_v = [[self.AX1[0], self.AY1[0], self.AZ1[0]], self.pos_X, self.pos_Y]
        AX12_v = [[self.AX1[2], self.AY1[2], self.AZ1[2]], self.pos_X, self.pos_Y]
        AX11_v = [[self.AX1[1], self.AY1[1], self.AZ1[1]], self.pos_X, self.pos_Y]
        AX13_v = [[self.AX1[3], self.AY1[3], self.AZ1[3]], self.pos_X, self.pos_Y]
        
        self.c.create_line(proj(AX00_v, self.Translate),\
            proj(AX10_v, self.Translate), tags = 'axes', arrow = 'last', width = 3)
        self.c.create_line(proj(AX00_v, self.Translate),\
            proj(AX12_v, self.Translate),tags = 'axes')
        self.c.create_line(proj(AX00_v, self.Translate),\
            proj(AX11_v,self.Translate ), tags = 'axes', arrow = 'last', width = 3)
        self.c.create_line(proj(AX00_v, self.Translate),\
            proj(AX13_v, self.Translate), tags = 'axes')

# -- end of subroutine drawaxes -- #

    def drawantenna (self) :
        self.c.delete('lines')
        i = 0
        while (i < self.counter):
            v0 = [[self.X0[i], self.Y0[i], self.Z0[i]], self.pos_X,\
                self.pos_Y]
            v1 = [[self.X1[i], self.Y1[i], self.Z1[i]], self.pos_X,\
                self.pos_Y] 
            self.c.create_line(proj(v0, self.Translate),\
            proj(v1, self.Translate),\
		    tags = 'lines', fill = self.USECOLOR[i], width = 2)
            i += 1
                
        self.c.configure(scrollregion = self.c.bbox('all'))

# -- end of subroutine drawantenna -- #

    def button_handler (self, event):
		self.loadantenna(self.filebox.get('active'))
		self.drawaxes()
		self.drawantenna()

# -- end of subroutine button_handler -- #

    def scale_handler (self, value) :        
        self.phi = self.horiz_var.get()
        self.theta = self.vert_var.get()
        (self.Xz, self.Yz, self.Xx, self.Xy, self.Yx, self.Yy) \
		    = makematrix(self.theta, self.phi, self.radfactor)
        self.pos_X = [self.Xx, self.Xy, self.Xz]
        self.pos_Y = [self.Yx, self.Yy, self.Yz]
        self.drawaxes()
        self.drawantenna()
		
# -- end of subroutine scale_handler -- #

# -- end of class Necview_plot -- #
  

################################### global subroutines

#       subroutine proj takes an input vector in the form:
#       in_Vect[[x, y, z], [Xx, Xy, Xz], [Yx, Yy, Yz]]      
   
def proj(in_Vect, p_Translate) :
    newx = 0.
    newy = 0.
    for i in (0, 1, 2) :       
        newx += float(in_Vect[1][i])*float(in_Vect[0][i])
        newy += float(in_Vect[2][i])*float(in_Vect[0][i])
  # use this to flip the thing
    return ((newx + p_Translate[0]) , -(newy - p_Translate[1]))    

# -- end of subroutine proj -- #      

def makematrix(theta, phi, lcl_radfactor) :
    lcl_Xz = 0.
    lcl_Yz = -math.sin(theta * lcl_radfactor)
    lcl_Xx = -math.sin(phi * lcl_radfactor)
    lcl_Xy = math.cos(phi * lcl_radfactor);
    lcl_Yx = math.cos(theta * lcl_radfactor) * math.cos(phi * lcl_radfactor)
    lcl_Yy = math.sin(phi * lcl_radfactor) * math.cos(theta * lcl_radfactor)
    return lcl_Xz, lcl_Yz, lcl_Xx, lcl_Xy, lcl_Yx, lcl_Yy
        
# -- end of subroutine makematrix -- #

def maxmin (vect) :
  # returns (maximum, minimum) of a vector
    max = vect[0]
    min = vect[0]
    i = 0
    while (i < len(vect) ) :
        if max < vect[i] :
            max = vect[i]
        if min > vect[i] :
            min = vect[i]
        i = i + 1
    return (max, min)
  
# -- end of subroutine maxmin -- #

def isTuple(x): return type(x) == types.TupleType

# -- end of subroutine isTuple -- #
 
# -- subroutine flatten_tuple due to David Yoo(dyoo@hkn.eecs.berkeley.edu) -- #

def flatten_tuple(T):
	if not isTuple(T) : return (T,)
	elif len(T) == 0: return ()
	else: return flatten_tuple(T[0]) + flatten_tuple(T[1:])

# -- end of subroutine flatten_tuple -- #

  
def main() :
    colors = ( 'green',  'cyan', 'PeachPuff3', \
        'RosyBrown1', 'IndianRed1', 'PaleVioletRed1', \
        'MediumOrchid1','pink','blue')
    colorindex = 0
    axislength = 100
    maxaspect = axislength
    graph_param = (axislength, maxaspect)

    Xx = 0.
    Xy = 0.
    Xz = 0.

    Yx = 0.
    Yy = 0.
    Yz = 0.
    
# pack position vectors

    pv = [[Xx, Xy, Xz], [Yx, Yy, Yz]]
    
###############################

# initialize the axis arrays

    AX0 = [0.,0.,0.,0.]
    AX1 = [axislength,0.,-axislength,0.]
    AY0 = [0,0,0,0]
    AY1 = [0.,axislength,0.,-axislength]
    AZ0 = [0.,0.,0.,0.]
    AZ1 = [0.,0.,0.,0.]
    
# pack axis arrays

    pa = [AX0, AX1, AY0, AY1, AZ0, AZ1]
    
################################   
    Translate = (150.,150.)

    root = Tk()
    views = Necview_plot(root, pv, pa, Translate, colors, graph_param)
    
################################

    views.c.configure(scrollregion = views.c.bbox('all'))
# populate the file box

    for file in os.listdir(os.getcwd()) :
        views.filebox.insert('end', file)

#   Interact...

    root.mainloop()

# -- end of main -- #

if __name__ == '__main__': 
    main()


