#!/usr/bin/python # Before you go reading this code, I think I should warn you that: # a) This is my first python program # b) This is my first "real" object oriented program # c) This is the first code I've ever written that dealt with graphics # and # d) I know nothing about optics # oh, and # e) I hope you're not working in an 80x25 terminal if you plan on # reading this. There are a few very long lines. # # There are tons of "magic numbers". I hate them as much as you do but # they seem to be a fact of life when doing GUI code. # # I intend to port this to wxWindows, so I don't want to take advantage of # too many python features (only makes the port harder) # # Having said that, I now welcome you to read and modify this code as you # see fit, but PLEASE send all changes and improvements to # rdr@ralcon.com (with the subject line "Tir2 code") so they can be # reviewed for possible inclusion. # # Please read the __about__ string for licensing information. from math import * from string import atof, splitfields from wxPython.wx import * __version__ = "Version 0.7.1 (6-13-1999)" __about__ = """Tir2 """ + __version__ + """ The original version of of tir2 was written for DOS in C by Steve Bialkowski as part of the Ralcon holotools. This version was written by Ray Rallison in wxPython. It will run under any system on which wxPython can run, but was originally written and tested under Debian GNU/Linux 2.1. A standalone version using wxWindows is in the works. It should be available for both Linux and MS-Windows 95/98/NT Real Soon Now(tm) Tir2 calculates Ar ion laser write beam geometry for total internal reflection. The program compensates for changes in index and for changes in thickness. The program finds record beam angles which will produce TIR of the playback beam for both the intact recording medium and one which expands upon processing. The K vector is used to determine the write beam angles. All angles are relative to the hologram input surface normal. For more information on tir2 and Holotools, see the documentation or visit the Ralcon web page at http://www.xmission.com/~ralcon/ Please send bug reports, patches, improvements, or documentation to rdr@ralcon.com and he will forward them to Ray. Please only send ASCII messages, as some of us loath getting MS-Word docs via email. """ # META: I need to add some online help at some point __help__ = __about__ defaults = { "r_lambda" : "488", # Playback beam wavelength (in nm) "rn" : "1.540", # Playback Refractive index "w_lambda" : "488", # Record beam wavelength (in nm) "wn" : "1.540", # Record Refractive index "r1_phi" : "", # External input playback beam angle (in degrees) "r2" : "", # Internal output playback beam angle (in degrees) "expn" : "1", # Media expansion factor "pr_n1" : "1.517", # 1st Prism RI "pr_n2" : "1.517" # 2nd Prism RI } # Utility functions, used all through the code # ********************************************************************* # asinsin does an arcsin(sin(x)), but in a way that gets around the # "limitations" of arcsin def asinsin(angle, scale): if angle == None: return None # META is this the right thing to do? while angle > pi: angle = angle - pi while angle < -pi: angle = angle + pi compare = sin(angle) * scale if (fabs(compare) <= 1.0 and fabs(angle) <= pi/2): angle = asin(compare) else: scale = scale*sin(pi-fabs(angle)) if scale <= 1.0: angle = angle/fabs(angle) * (pi - asin(scale)) else: angle = None return angle # Conversion routines. def to_deg(rad): return 180.0*rad/pi def to_rad(deg): return (pi*deg)/180.0 # For some reason, int(-40.0) == -39. This function is a workaround. def trunc(num): if num < 0: num = num - 0.000001 return int(num) def print_ang(angle): dec = to_deg(angle) deg = trunc(dec) min = trunc(60*dec - 60*deg) sec = trunc(3600*dec - 60*min - 3600*deg) return "%g (%d %d' %d\")" % (dec, deg, min, sec) # ********************************************************************* # I just did this class to as a convenience. All it does is add # NumValue to the TextCtrl class. # # NumValue is used for getting the floating point value that is # represented by the string returned by GetValue() # # NumValue checks to make sure a value has been given. If not, it tries # to return a previously defined default value. If that fails, it raises # and exception to tell the calling function that it shouldn't bother # continuing. class NumCtrl(wxTextCtrl): def __init__(self, parent, id, value, pos, size, default = None): wxTextCtrl.__init__(self, parent, id, value, pos, size) self.default = default zero = "Field empty or zero" def NumValue(self): text = self.GetValue() if (text): return atof(text) if self.default != "": self.SetValue(self.default) return atof(self.default) raise self.zero return None # ********************************************************************* # I appologize to anyone who has to read (and modify!) this code. # This is the first code I have ever written that deals with graphics. # # There are far too many special cases for this code to be elegant, # but if you would like to take a shot at cleaning it up, be my guest # (but do me a favour and send me the new version *grin*) # # As of this point, it is painfully non-general. class Drawing(wxWindow): def __init__(self, parent, id, dim, font_size): wxWindow.__init__(self, parent, id, wxDefaultPosition, wxDefaultSize, wxRAISED_BORDER) self.parent = parent self._dim = dim self.SetDimensions(dim[0], dim[1], dim[2], dim[3]) self.font = wxFont(font_size, wxMODERN, wxNORMAL, wxNORMAL) self.Reset() # Resets everything to known values. def Reset(self): self.valid = 0 self.keys = ( 'playback', 'record', 'in_int', 'in_ext', 'out_int', 'out_ext', 'r_in_int', 'r_in_ext', 'r_out_int', 'r_out_ext', 'sp_freq', 'kappa', 'expansion', 'prism0', 'prism1' ) self.messages = {} for key in self.keys: self.messages[key] = ""; # Playback angles self._pb_angs = [ None, # in_int None, # in_ext None, # out_int None, # out_ext None, # None: used just to keep this structure None # the same size as that in re_angs. ] # Record angles self._re_angs = [ None, # r_in_int None, # r_in_ext None, # r_out_int None, # r_out_ext None, # pr_in - input prism angle None # pr_out - output prism angle ] self._prism_ang = [ None, None ] self._cent_prism = [ None, None ] # Set Playback Input Angles def SetInAng(self, in_ext, in_int): self._pb_angs[0] = in_int self._pb_angs[1] = in_ext if in_int != None: self.messages['in_int'] = "Internal input angle: %8.3fø" % to_deg(in_int) if in_ext != None: self.messages['in_ext'] = "External input angle: %8.3fø" % to_deg(in_ext) # Set Playback Output Angles def SetOutAng(self, out_ext, out_int): self._pb_angs[2] = out_int self._pb_angs[3] = out_ext if out_int != None: self.messages['out_int'] = "Internal output ang: %8.3fø" % to_deg(out_int) if out_ext != None: self.messages['out_ext'] = "External output ang: %8.3fø" % to_deg(out_ext) # Set Record Internal Angles def SetRIntAng(self, r_in_int, r_out_int): self._re_angs[0] = r_in_int self._re_angs[2] = r_out_int if r_in_int != None: self.messages['r_in_int'] = "Internal input angle: %8.3fø" % to_deg(r_in_int) if r_out_int != None: self.messages['r_out_int'] = "Internal output angle: %8.3fø" % to_deg(r_out_int) # Set Record External Angles def SetRExtAng(self, r_in_ext, pr_in, r_out_ext, pr_out): self._re_angs[1] = r_in_ext self._re_angs[3] = r_out_ext if pr_in: self._re_angs[4] = 0 if pr_out: if pr_in: # Check for same prism if (pr_in == pr_out): self._re_angs[5] = 0 else: # Two prisms self._re_angs[5] = 1 else: self._re_angs[5] = 0 if r_in_ext != None: self.messages['r_in_ext'] = "External input angle: %s" % print_ang(r_in_ext) if r_out_ext != None: self.messages['r_out_ext'] = "External output angle: %s" % print_ang(r_out_ext) def SetPrismAng(self, ideal, prism_ang, which = 0): self._prism_ang[which] = prism_ang if which == 0: self.messages['prism0'] = ( "Prism angle:\nIdeal: %4.2fø\nUsing: %4.2fø" % (to_deg(ideal), to_deg(prism_ang))) else: self._prism_ang2 = prism_ang self.messages['prism1'] = ( "Output Prism angle:\nIdeal: %4.2fø\nUsing: %4.2fø" % (to_deg(ideal), to_deg(prism_ang))) def OnPaint(self, event): dc = wxPaintDC(self) self.do_drawing(dc) def _DrawSubstrate(self, dc, cen_x, cen_y, size, angs, check_prism = 0): dc.SetPen(wxPen(wxNamedColour('BLACK'))) dc.DrawRectangle(cen_x - size, cen_y - size/2, size*2, size) dc.DrawLine(cen_x, cen_y - size, cen_x, cen_y + size) dc.DrawLine(cen_x - size*1.2, cen_y, cen_x + size*1.2, cen_y) if (check_prism): self._draw_prism(dc, size, cen_x, cen_y, 0, 0.5) self._draw_prism(dc, size, cen_x, cen_y, 1, 0.5) dc.SetPen(wxPen(wxNamedColour('GREEN'))) if (angs[0] != None): self._draw_ang(dc, cen_x - (size/2), cen_y, size/2, angs[0], angs[1], angs[4]) dc.SetPen(wxPen(wxNamedColour('RED'))) if (angs[2] != None): self._draw_ang(dc, cen_x + (size/2), cen_y, size/2, angs[2], angs[3], angs[5]) dc.SetPen(wxPen(wxNamedColour('BLACK'))) def do_drawing(self, dc = None): if not dc: dc = wxClientDC(self) size = 70 pcen_x = 350 pcen_y = 120 rcen_x = pcen_x rcen_y = 320 #dc.BeginDrawing() dc.Clear() dc.SetFont(self.font) # Draw border dc.SetPen(wxPen(wxNamedColour('BLACK'))) dc.DrawRectangle(0, 0, self._dim[2], self._dim[3]) if not self.valid: return # Playback self._DrawSubstrate(dc, pcen_x, pcen_y, size, self._pb_angs) # Record self._DrawSubstrate(dc, rcen_x, rcen_y, size, self._re_angs, 1) self._report(dc, pcen_x, pcen_y, rcen_x, rcen_y, size) #dc.EndDrawing() def _draw_ang(self, dc, x, y, size, int_ang, ext_ang, with_prism = None): dc.DrawLine(x, y, x+size*sin(int_ang), y-size*cos(int_ang)) if (ext_ang != None): if (fabs(ext_ang) > pi/2): y = y + size else: y = y - size if (with_prism != None): x = self._cent_prism[with_prism][0] y = self._cent_prism[with_prism][1] ext_ang = ext_ang + self._prism_ang[with_prism] dc.DrawLine(x, y, x+size*sin(ext_ang), y-size*cos(ext_ang)) def _draw_prism(self, dc, size, rcen_x, rcen_y, which, scale = 1): if self._prism_ang[which] == None: return store = dc.GetPen() dc.SetPen(wxPen(wxNamedColour('BLUE'))) ang = self._prism_ang[which] if (ang < pi/2 and ang >= 0): # First quadrant start_y = rcen_y - size/2 start_x = rcen_x + size/2 elif (ang < pi and ang >= pi/2): # Forth quadrant start_y = rcen_y + size/2 start_x = rcen_x + size/2 elif (ang < 0 and ang >= -pi/2): # Second quadrant start_y = rcen_y - size/2 start_x = rcen_x - size/2 else: # (ang < -pi/2 and ang >= -pi): # Third quadrant start_y = rcen_y + size/2 start_x = rcen_x - size/2 scale = size/2 end_y = start_y - (scale)*cos(ang) end_x = start_x - (scale)*sin(ang) text_start = (30, rcen_y - size/2 + which * 50) dc.DrawLine(start_x, start_y, end_x, end_y) # Find the midpoint of the prism line. It is used as # the starting point of lines drawn from the prism. mid_x = (start_x + end_x)/2 mid_y = (start_y + end_y)/2 self._cent_prism[which] = ( mid_x, mid_y ) self._print_msg(dc, self.messages['prism' + "%i" % which], text_start[0], text_start[1]) dc.SetPen(store) # Returns the "size" (in pixels) of a string, taking into account # multiple lines and different font sizes. def _msg_size(self, dc, message): list = splitfields(message, '\n') w, h = 0, 0 for line in list: te = dc.GetTextExtent(line) if te[0] > w: w = te[0] h = h + te[1] spacing = te[1] return w, h, spacing # Prints a message, returning to the caller the next # available verticle location for printing a message. def _print_msg(self, dc, message, x, y, spacing = 12): if message == "": return y list = splitfields(message, '\n') for line in list: dc.DrawText(line, x, y) y = y + spacing return y # Draws all the pretty text. I really wanted to label the angles, # but after the 6,023,452th if-then-else statement, I decided # to give it up. If you get it working, please send me your changes # and you will have my eternal gratitude. def _report(self, dc, pcen_x, pcen_y, rcen_x, rcen_y, size): (w,h) = dc.GetTextExtent('X') spacing = h + 5 dc.DrawText(self.messages['playback'], 10, pcen_y - size - 40) next = self._print_msg(dc, self.messages['in_ext'], spacing, pcen_y - size/2) next = self._print_msg(dc, self.messages['in_int'], spacing, next) next = next + spacing next = self._print_msg(dc, self.messages['out_ext'], spacing, next) self._print_msg(dc, self.messages['out_int'], spacing, next) start_y = rcen_y - size - 40 dc.DrawLine(10, start_y - 5, self._dim[2] - 10, start_y - 5) dc.DrawText(self.messages['record'], 10, start_y) next = self._print_msg(dc, self.messages['r_in_ext'], spacing, rcen_y + size + 20) next = self._print_msg(dc, self.messages['r_in_int'], spacing, next) next = next + spacing next = self._print_msg(dc, self.messages['r_out_ext'], spacing, next) next = self._print_msg(dc, self.messages['r_out_int'], spacing, next) dc.DrawLine(10, next + 5, self._dim[2] - 10, next + 5) next = next + spacing for key in ('sp_freq', 'kappa', 'expansion'): next = self._print_msg(dc, self.messages[key], spacing, next) + 5 # ********************************************************************* # Pretty easy to figure out if you know what wxStatusBar is all about. # Creates a two field status bar and supplies methods for clearing # and writing to it. class StatusBar(wxStatusBar): def __init__(self, parent): wxStatusBar.__init__(self,parent, -1) self.SetFieldsCount(2) self.SetStatusWidths([100, -1]) def message(self, level, text): self.SetStatusText(level, 0) if level == "ERROR": self.SetStatusText(text + " (Do Not Trust The Results!)", 1) else: self.SetStatusText(text, 1) def clear(self): self.SetStatusText("", 0) self.SetStatusText("", 1) # ********************************************************************* # The ugly GUI code. This sets up the text entry boxes, the menus, # the static messages, and your kitchen sink. # I just went ahead and added S.B.'s code to this class. class TirFrame(wxFrame): _win_width = 870 _win_height = 640 # Note: This is only the panel size vert = 4 # Current vertical position indent = 4 # First column indent2 = 260 # Second column panel_center = 380 def __init__(self, parent, id, title): wxFrame.__init__(self, parent, id, title, wxDefaultPosition, wxSize(self._win_width, self._win_height + 60)) if wxPlatform == '__WXGTK__': font_size = 12 else: font_size = 10 self.font = wxFont(font_size, wxMODERN, wxNORMAL, wxNORMAL) dc = wxClientDC(self) dc.SetFont(self.GetFont()) (self.w,self.h) = dc.GetTextExtent('X') self.vert_space = 2.5*self.h # Ammount of vertical space to leave self.mainmenu = wxMenuBar() file_menu = wxMenu() #ID = NewId() #file_menu.Append(ID, '&Page setup', 'Page setup') #EVT_MENU(self, ID, self.OnPageSetup) ID = NewId() file_menu.Append(ID, '&Print', 'Print') EVT_MENU(self, ID, self.OnPrint) file_menu.AppendSeparator() ID = NewId() file_menu.Append(ID, 'E&xit', 'Quit tir2') EVT_MENU(self, ID, self.OnExit) self.mainmenu.Append(file_menu, '&File') help_menu = wxMenu() ID = NewId() help_menu.Append(ID, '&Help', 'Help') EVT_MENU(self, ID, self.OnHelp) ID = NewId() help_menu.Append(ID, '&About', 'About tir2') EVT_MENU(self, ID, self.OnAbout) self.mainmenu.Append(help_menu, '&Help') self.SetMenuBar(self.mainmenu) self.sb = StatusBar(self) self.SetStatusBar(self.sb) self.sb.message("", "Welcome to TIR2 " + __version__) self.panel = wxWindow(self, -1, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL) self.panel.SetDimensions(self.indent, 10, self.panel_center, self._win_height) self.panel.SetFont(self.font) self.draw_panel = Drawing(self, -1, (self.panel_center, 10, (self._win_width - self.panel_center)-10, self._win_height), font_size) self.r_lambda = self.EntryBox("Playback beam wavelength", defaults["r_lambda"], "nm") self.rn = self.EntryBox("Refractive index", defaults["rn"]) self.w_lambda = self.EntryBox("Record beam wavelength", defaults["w_lambda"], "nm") self.wn = self.EntryBox("Refractive index", defaults["wn"]) self.r1_phi = self.EntryBox("Ext input playback beam angle", defaults["r1_phi"], "deg") wxStaticText(self.panel, -1, "Int angles in which Playback TIR occurs:", wxPoint(self.indent,self.vert+10), wxSize(self.panel_center - self.indent, self.h)) self.hint = wxStaticText(self.panel, -1, "Not yet calculted", wxPoint(self.indent,self.vert+10+self.h), wxSize(self.panel_center - self.indent, self.h)) self.vert = self.vert + self.vert_space*2 self.int_ext, self.r2 = self.EntryBox( ["Int output playback beam angle", "Ext output playback beam angle"], defaults["r2"], "deg", ) self.expansion = self.EntryBox("Media expansion factor", defaults["expn"]) self.alpha1 = self.EntryBox("1st Prism face normal angle", "", "deg") self.pr_n1 = self.EntryBox("Prism refractive index", defaults["pr_n1"]) self.alpha2 = self.EntryBox("2nd Prism face normal angle", "", "deg") self.pr_n2 = self.EntryBox("Prism refractive index", defaults["pr_n2"]) ID = NewId() button = wxButton(self.panel, ID, "Calculate", wxPoint(self.indent, self.vert)) EVT_BUTTON(self, ID, self.button_calculate) def EntryBox(self, label, default_val, units = None): numbox_size = self.w * 8 indent3 = self.indent2 + numbox_size + 10 with_choice = 0 lab_size = wxSize(self.indent2 - 10, self.h*2) if type(label) == type([]): lab_point = wxPoint(self.indent, self.vert - self.h/2) which = wxChoice(self.panel, -1, lab_point, lab_size, label) with_choice = 1 else: lab_point = wxPoint(self.indent, self.vert) wxStaticText(self.panel, -1, label, lab_point, lab_size) data = NumCtrl(self.panel, -1, default_val, wxPoint(self.indent2,self.vert - 4), wxSize(numbox_size, self.h*2), default_val) if (units): wxStaticText(self.panel, -1, units, wxPoint(indent3,self.vert), wxDefaultSize) self.vert = self.vert + self.vert_space if with_choice: return which, data return data def OnCloseWindow(self, event): self.Destroy() def OnExit(self, event): self.Close() def OnPrint(self, event): # psdc = wxPostScriptDC("out.ps", FALSE, self) # self.draw_panel.Reset() # self.draw_panel.do_drawing(psdc) dlg = wxMessageDialog(self, """Ooops As of this writing, printing support in wxPython is non-functional. It is expected to work in the next version, but the time frame is "when it's ready". It should work in the wxWindows version, if/when that gets completed. In the mean time, you will have to use a screen capture app or some other kluge" Sorry. """, "Sorry", wxOK) dlg.ShowModal() #def OnPageSetup(self, event): # data = wxPageSetupData() # data.SetMarginTopLeft(wxPoint(50,50)) # data.SetMarginBottomRight(wxPoint(50,50)) # dlg = wxPageSetupDialog(self, data) # if dlg.ShowModal() == wxID_OK: # data = dlg.GetPageSetupData() # dlg.Destroy() def OnAbout(self, event): about = wx.wxMessageDialog(self, __about__, "About...", wxOK) about.ShowModal() def OnHelp(self, event): help = wx.wxMessageDialog(self, __help__, "Help...", wxOK) help.ShowModal() # One of the side effects of not knowing anything about optics is # that I rarely pick good function names. This is not an exception # to the rule. # This method simply displays the range of angles in which TIR occurs. def fix_playback_tir(self): try: ang = asin(1/self.rn.NumValue()) self.hint.SetLabel("%8.3fø to %8.3fø " % (to_deg(ang), to_deg(pi-ang))) except self.rn.zero: self.hint.SetLabel("Can not be calculated") self.sb.message("ERROR", "Playback refractive index not set") def button_calculate(self, event): self.sb.clear() self.fix_playback_tir() try: self.draw_panel.Reset() r2 = (self.int_ext.GetSelection(), to_rad(self.r2.NumValue())) self.calc_and_report(self.expansion.NumValue(), to_rad(self.r1_phi.NumValue()), r2, self.r_lambda.NumValue(), self.rn.NumValue(), self.w_lambda.NumValue(), self.wn.NumValue(), self.pr_n1.NumValue(), self.pr_n2.NumValue()) self.draw_panel.valid = 1 except self.expansion.zero: self.sb.message("ERROR", self.expansion.zero) self.draw_panel.valid = 0 self.draw_panel.do_drawing() # ********************************************************************* # Steve B. gets most of the credit for the math and logic from # here down. I did a major rewrite on the structure of the code # and added a few features, but, especially on the functions # marked, he did most of the work when he wrote the original # version. def calc_and_report(self, expansion, r1_phi, r2, r_lambda, rn, w_lambda, wn, pr_n1, pr_n2): r_lambda = r_lambda * 1.0e-9 w_lambda = w_lambda * 1.0e-9 if r2[0] == 1: r2_phi = r2[1] r2_phi_int = None else: r2_phi = None r2_phi_int = r2[1] # calculate and report playback angles r1_phi_int = asinsin(r1_phi, 1/rn) if r2_phi_int == None: r2_phi_int = asinsin(r2_phi, 1/rn) else: r2_phi = asinsin(r2_phi_int, rn) self.draw_panel.messages['playback'] = "Playback at %6.2f nm with RI %5.3f" % (1.E9*r_lambda, rn) self.draw_panel.messages['record'] = "Record at %6.2f nm with RI %5.3f" % (1.E9*w_lambda, wn) self.draw_panel.SetInAng(r1_phi, r1_phi_int) self.draw_panel.SetOutAng(r2_phi, r2_phi_int) d, kappa = self.calc_kappa(r1_phi_int, r2_phi_int, expansion, r_lambda, rn) # check record beam angle if (w_lambda/(2.0*wn*d) > 1.0): self.sb.message("ERROR", "Grating spacing too small") return w1_phi_int, w2_phi_int = self.calc_int_angles(w_lambda, wn, d, kappa) self.calc_ext_angles(wn, w1_phi_int, w2_phi_int, pr_n1, pr_n2) # ********************************************************************* def test_ext(self, phi, alpha, wn, n): if alpha: phi = asinsin(phi, wn/n) if phi == None: self.sb.message("WARNING", "Invalid prism RI") phi = None else: phi = asinsin(phi - alpha, wn) return phi # ********************************************************************* # Calculates the input and output external angles. More # importantly, it tries to find a situation in which it is # possible to even have external angles: # No prism # One prism # Two prisms # def calc_ext_angles(self, wn, w1_phi_int, w2_phi_int, pr_n1, pr_n2): if (fabs(wn*sin(w1_phi_int)) <= 1.0) and (fabs(wn*sin(w2_phi_int)) <= 1.0): # No prisms needed w1_phi = asinsin(w1_phi_int, wn) alpha1 = 0 w2_phi = asinsin(w2_phi_int, wn) alpha2 = 0 else: # Try with just one prism if (fabs(w2_phi_int) > pi/2): # Input in front, output out back alpha1 = 0 alpha2 = self.calc_prism(w2_phi_int) else: # Both to front (same prism) alpha1 = self.calc_prism((w1_phi_int+w2_phi_int)/2.0) alpha2 = alpha1 w1_phi = self.test_ext(w1_phi_int, alpha1, wn, pr_n1) w2_phi = self.test_ext(w2_phi_int, alpha2, wn, pr_n1) if (w1_phi == None) or (w2_phi == None): # Nope, that didn't work. Try two prisms alpha1 = self.calc_prism(w1_phi_int, 0) alpha2 = self.calc_prism(w2_phi_int, 1) w1_phi = self.test_ext(w1_phi_int, alpha1, wn, pr_n1) w2_phi = self.test_ext(w2_phi_int, alpha2, wn, pr_n2) if (w1_phi == None): self.sb.message("ERROR", "Input angle exceeds critical") return if (w2_phi == None): self.sb.message("ERROR", "Output angle exceeds critical") return self.draw_panel.SetRExtAng(w1_phi, alpha1, w2_phi, alpha2) # ********************************************************************* # S.B. gets full credit for this function. I don't even understand what # it's doing. def calc_kappa(self, r1_phi_int, r2_phi_int, expansion, r_lambda, rn): kappa = (r1_phi_int+r2_phi_int)/2.0 self.draw_panel.messages['kappa'] = "Internal grating vector angle: %8.3fø" % to_deg(kappa) # calculate grating spacing d = fabs(r_lambda/(2.0*rn*sin(pi/2.0-(kappa-r1_phi_int)))) self.draw_panel.messages['sp_freq'] = ("Internal spatial freq: %ld l/mm (%ld l/mm at surface)" % (long((1.0e-3)/d), long((1.0e-3)*sin(kappa)/d))) kappa = kappa - (pi/2) if (expansion != 1.0): old_kappa = kappa kappa = atan2(sin(kappa), cos(kappa)/expansion) # corrected grating spacing if (kappa <= 0.1*pi/2): d = d * fabs(cos(kappa)/cos(old_kappa)) else: d = d * fabs(sin(kappa)/(expansion*sin(old_kappa))) self.draw_panel.messages['expansion'] = ( "Parameters for x%g film expansion:\n" "Int grating vector ang prior to expansion: %8.3fø\n" "Grating freq prior to expansion: %ld lines/mm" % (expansion, to_deg(kappa+pi/2), (1.0e-3)/d)) return d, kappa # ********************************************************************* # S.B. gets 99% credit for this function. def calc_int_angles(self, w_lambda, wn, d, kappa): del_kappa = fabs(asin(w_lambda/(2.0 * wn * d))) # kappa correction w1_phi_int = kappa+del_kappa # internal angles w2_phi_int = kappa-del_kappa if (fabs(w1_phi_int) > fabs(w2_phi_int)): temp = w1_phi_int w1_phi_int = w2_phi_int w2_phi_int = temp # Force input angle to front side w1_phi_int = asin(sin(w1_phi_int)) self.draw_panel.SetRIntAng(w1_phi_int, w2_phi_int) return w1_phi_int, w2_phi_int # ********************************************************************* # Calculates the ideal prism and then finds the closest "common" # prism angle. # # If the user has specified a prism angle, that angle will be # returned instead. # # S.B. gets 99% credit for this function. def calc_prism(self, ideal, which = 0): choices = ( -90.0, -60.0, -45.0, -30.0, 30.0, 45.0, 60.0, 90.0 ) if (fabs(ideal) <= pi/2): nbest = 0 error = pow(ideal-to_rad(choices[0]),2.0) for i in range(0, 7): test = pow(ideal-to_rad(choices[i]),2.0) if (error > test): nbest = i error = test alpha = to_rad(choices[nbest]) elif (ideal < -pi/2): nbest = 0 error = pow(ideal-to_rad(choices[0]-90.0),2.0) for i in range(1, 3): test = pow(ideal-to_rad(choices[i]-90.0),2.0) if(error > test): nbest = i error = test alpha = to_rad(choices[nbest]-90.0) elif (ideal > pi/2): nbest = 4 error = pow(ideal-to_rad(choices[4]+90.0),2.0) for i in range(5, 7): test = pow(ideal-to_rad(choices[i]+90.0),2.0) if(error > test): nbest = i error = test alpha = to_rad((choices[nbest]+90.0)) else: alpha = 0 if which: user_alpha = self.alpha2.GetValue() else: user_alpha = self.alpha1.GetValue() if (user_alpha): alpha = to_rad(atof(user_alpha)) self.draw_panel.SetPrismAng(ideal, alpha, which) return alpha # ********************************************************************* def main(): class create_window(wxApp): def OnInit(self): frame = TirFrame(NULL, -1, "Tir2") frame.Show(true) self.SetTopWindow(frame) return TRUE app = create_window(0) app.MainLoop() if __name__ == '__main__': main()