source: inundation/pmesh/pmesh.py @ 2282

Last change on this file since 2282 was 1625, checked in by ole, 20 years ago
File size: 75.3 KB
Line 
1
2import  Pmw, AppShell, math, time, string, marshal
3from toolbarbutton import ToolBarButton
4import tkFileDialog
5from   tkSimpleDialog import Dialog
6import mesh
7from Tkinter import  FALSE,TRUE, Frame,X, LEFT,YES,BOTH,ALL,Widget,CURRENT, Label,W, Entry, E, StringVar, END, Checkbutton, Radiobutton, IntVar, DISABLED, NORMAL
8#from cursornames import TLC,TRC, BLC, BRC, TS, RS, LS, BS
9from tkMessageBox import showerror, _show, QUESTION,YESNOCANCEL
10import types
11import visualmesh
12import os
13import profile
14import load_mesh.loadASCII
15from alpha_shape.alpha_shape import AlphaError
16
17# CONSTANTS
18VERT_SELECT_ADDING_SEG_COLOR = 'orange'
19SELECT_COLOR = 'red'
20SEG_COLOUR = 'blue'
21TRIANGLE_COLOUR = 'green'
22APPLICATION_NAME = 'Pmesh'
23SET_COLOUR = 'red'
24DEFAULT_ATTRIBUTE = 'elevation'
25
26#for alpha shapes
27NO_SELECTION = 0
28AUTO = 1
29SET_ALPHA = 2
30
31
32class Draw(AppShell.AppShell):
33    usecommandarea = 1
34    appname        = APPLICATION_NAME
35    frameWidth     = 840
36    frameHeight    = 600
37   
38   
39    def createButtons(self):
40        """
41        Add buttons to the bottom of the GUI
42        """
43        self.buttonAdd('Postscript',
44              helpMessage='Save current drawing (as PostScript)',
45              statusMessage='',
46              command=self.ipostscript)
47        self.buttonAdd('Clear', helpMessage='Delete the mesh',
48              statusMessage='', command=self.clearMesh)
49        self.buttonAdd('Close', helpMessage='Close Screen',
50              statusMessage='', command=self.close)
51       
52    def createBase(self):
53        """
54        Create the GUI framework.  Set up the GUI
55        """
56        self.toolbar = self.createcomponent('toolbar', (), None,
57                  Frame, (self.interior(),), background="gray90")
58        self.toolbar.pack(fill=X)
59
60        self.scrolledcanvas =  self.createcomponent('ScrolledCanvas', (), None,
61                                       Pmw.ScrolledCanvas, (self.interior(),)
62                                       ,borderframe = 1
63                                       ,labelpos = 'n'
64                                       )
65        self.scrolledcanvas.configure(hscrollmode = 'dynamic')
66        self.scrolledcanvas.configure(vscrollmode = 'dynamic')
67        self.scrolledcanvas.pack(side=LEFT, expand=YES, fill=BOTH)
68        self.canvas = self.scrolledcanvas.component('canvas')
69        self.canvas.configure( background="white" )
70       
71        self.canvas.pack(side=LEFT, expand=YES, fill=BOTH)
72
73        Widget.bind(self.canvas, "<Button-1>", self.mouseDown)
74        Widget.bind(self.canvas, "<Button3-ButtonRelease>", self.rightMouseUp)
75        Widget.bind(self.canvas, "<Button2-ButtonRelease>",self.DeleteSelectedMeshObject)
76        # "<Delete>" didn't work..
77        #Widget.bind(self.canvas, "<Delete>", self.DeleteSelectedMeshObject)
78
79        #self.root.bind("<KeyPress>", self.setRegular)
80        #self.root.bind("<KeyRelease>", self.setRegular)
81       
82        self.scrolledcanvas.resizescrollregion()
83
84#     def setRegular(self, event):
85#         if event.type == '2' and event.keysym == 'Shift_L':
86#             self.regular = TRUE
87#         else:
88#             self.regular = FALSE
89
90    def createMenus(self):
91        """
92        Add menus to the top of the GUI
93        """
94        self.menuBar.deletemenuitems('File',0)
95        self.menuBar.addmenuitem('File', 'command', 'New mesh',
96                                 label='New', command=self.clearMesh)
97        self.menuBar.addmenuitem('File', 'command', 'Open mesh',
98                                 label='Open...', command=self.importFile)
99        self.menuBar.addmenuitem('File', 'command', 'Save mesh',
100                                 label='Save', command=self.saveDrawing)
101        self.menuBar.addmenuitem('File', 'command', 'Save mesh',
102                                 label='SaveAs...', command=self.saveAsDrawing)
103       
104        self.menuBar.addmenuitem('File', 'separator')
105        self.menuBar.addmenuitem('File', 'command',
106                                 'Add ungenerated file from arcGIS',
107                                 label='Add ungenerated file...',
108                                 command=self.ImportUngenerate)
109       
110        self.menuBar.addmenuitem('File', 'command',
111                                 'Export ASCII obj',
112                                 label='Export ASCII obj',
113                                 command=self.exportObj)
114       
115        self.menuBar.addmenuitem('File', 'command',
116                                 'Export ASCII segment outline',
117                                 label='Export ASCII segment outline...',
118                                 command=self.exportASCIIsegmentoutlinefile)
119       
120        self.menuBar.addmenuitem('File', 'command',
121                                 'Export ASCII xya file',
122                                 label='Export ASCII xya file...',
123                                 command=self.exportPointsFile)
124       
125        self.menuBar.addmenuitem('File', 'command',
126                                 'add Segments to connect all vertices'  ,
127                                 label='join vertices',
128                                 command=self.joinVertices)
129        self.menuBar.addmenuitem('File', 'command',
130                                 'add Segments to form alpha shape'  ,
131                                 label='Auto segment',
132                                 command=self.autoSegment)
133        self.menuBar.addmenuitem('File', 'command',
134                           'modify the alpha boundary by applying filters',
135                                 label='filter alpha boundary',
136                                 command=self.autoSegmentFilter)
137        self.menuBar.addmenuitem('File', 'command', 'Normalise mesh',
138                                 label='Normalise mesh', command=self.normaliseMesh)
139        self.menuBar.addmenuitem('File', 'command', 'Normalise mesh for glutobj',
140                                 label='Normalise mesh for glutobj', command=self.normalise4ObjMesh)
141        self.menuBar.addmenuitem('File', 'separator')
142        self.menuBar.addmenuitem('File', 'command', '',
143                                 label='Print geo reference', command=self.printGeoReference)
144        self.menuBar.addmenuitem('File', 'separator')
145        self.menuBar.addmenuitem('File', 'command', 'Exit program',
146                                 label='Exit', command=self.quit)
147
148    def createTools(self):
149        """
150        Add buttons to the top of the GUI
151        """
152        self.mouseDownFunc = {}
153        self.modeClass = {}
154        ToolBarButton(self, self.toolbar, 'sep', 'sep.gif',
155                      width=10, state='disabled')
156        for key, balloon, mouseDownFunc, Mode in [
157            ('pointer','Edit drawing eventually.  Right now this does nothing', self.drag, None)
158            ,('vertex',    'Vertex mode', self.drawVertex, mesh.Vertex)
159            ,('segment', 'Segment mode',self.selectSegmentPoint, mesh.Segment)
160            ,('hole', 'hole mode',self.drawHole, mesh.Hole)
161            ,('region', 'region mode',self.drawRegion, mesh.Region)
162            ]:
163            t = ToolBarButton(self, self.toolbar, key, '%s.gif' % key,
164                          command=self.selectFunc, balloonhelp=balloon,
165                               statushelp='')
166            t.cycle("DrawMode")
167            if key == 'pointer': #FIXME- this is specified in line 1062 as well
168                                 # self.selectFunc('pointer')
169                self.curFunc  = self.drawVertex
170                t.setInitialSunkenButton("DrawMode")
171            self.modeClass[key] = Mode
172            # for actions that occur when the mouse goes down
173            self.mouseDownFunc[key] = mouseDownFunc
174         
175    def createZooms(self):
176        """
177        Add zoom buttons to the top of the GUI
178        """
179        ToolBarButton(self, self.toolbar, 'sep', 'sep.gif', width=10,
180                      state='disabled')
181        zoom = '0.5'
182        ToolBarButton(self, self.toolbar, zoom, 'zoom%s.gif' %
183                      zoom, command=self.selectZoom,
184                      balloonhelp='*%s zoom' % zoom,
185                      statushelp='')
186           
187        ToolBarButton(self, self.toolbar,'1.0', 'zoomToMesh.gif',
188                      command=self.ResizeToFitWrapper,
189                      balloonhelp='Zooms to mesh size',
190                      statushelp='')
191        zoom = '2'
192        ToolBarButton(self, self.toolbar, zoom, 'zoom%s.gif' %
193                      zoom, command=self.selectZoom,
194                      balloonhelp='*%s zoom' % zoom,
195                      statushelp='')
196
197    def createEdits(self):
198        """
199        Add Edit buttons to the top of the GUI
200        """
201        ToolBarButton(self, self.toolbar, 'sep', 'sep.gif', width=10,
202                      state='disabled')
203        for key, func, balloon in [
204                ('addVertex', self.windowAddVertex, 'add Vertex'),
205                ('edit', self.windowEdit, 'edit selected object'),
206                ('default', self.windowDefault, 'set default value for selected mode'),
207                ('joinVer', self.joinVerticesButton, 'add Segments to connect all vertices'),     
208             #   ('autoSegHull', self.autoSegmentHullButton, 'add Segments to form alpha shape, using Hull'), 
209             #   ('autoSeg', self.autoSegmentButton, 'add Segments to form alpha shape'),
210                ('autoSegGiveAlpha', self.autoSegmentGiveAlphaButton, 'add Segments to form alpha shape, specify alpha'),
211                ('meshGen', self.windowMeshGen, 'Generate Mesh')]:
212            ToolBarButton(self, self.toolbar, key, '%s.gif' % key,
213                          command=func, balloonhelp=balloon,
214                               statushelp='' )
215
216
217
218    def createSetTools(self):
219        """
220        Add set tool buttons to the top of the GUI
221        """
222        ToolBarButton(self, self.toolbar, 'sep', 'sep.gif', width=10,
223                      state='disabled')
224        for key, func, balloon in [
225                ('threshold', self.threshold, 'threshold the set'),
226                ('Courant_threshold', self.Courant_threshold, 'Courant_threshold the set'),
227                ('gradient_threshold', self.gradient_threshold, 'gradient_threshold the set'),
228                ('smooth', self.smooth_polySet, 'smooth the polygons'),
229                ('polyset', self.triangles_to_polySet, 'make a poly set out of selected triangles')]:     
230                #('refineSet', self.refineSet, 'Refine the set')]:
231            ToolBarButton(self, self.toolbar, key, '%s.gif' %key,
232                          command=func, balloonhelp=balloon,
233                               statushelp='' )
234
235    def createSetIcons(self):
236        """
237        Add Edit buttons to the top of the GUI
238        """
239        ToolBarButton(self, self.toolbar, 'sep', 'sep.gif', width=10,
240                      state='disabled')
241        for key, func, balloon in [
242                ('selectAllTriangles', self.selectAllTriangles, 'select all'),
243                ('none', self.clearSelection, 'clear selection')]:
244            ToolBarButton(self, self.toolbar, key, '%s.gif' %key,
245                          command=func, balloonhelp=balloon,
246                               statushelp='' )
247
248    def createVisualiseIcons(self):
249        """
250        Add Edit buttons to the top of the GUI
251        """
252        ToolBarButton(self, self.toolbar, 'sep', 'sep.gif', width=10,
253                      state='disabled')
254        for key, func, balloon in [
255                ('visualise', self.visualise, 'Visualise mesh triangles'),
256                ('unvisualise', self.unvisualise, 'Do not visualise mesh triangles (for large meshes)')]:
257            ToolBarButton(self, self.toolbar, key, '%s.gif' %key,
258                          command=func, balloonhelp=balloon,
259                               statushelp='' )
260
261
262    def refineSet(self,parent):
263        self.mesh.refineSet(self.selSet)
264        self.visualiseMesh(self.mesh)
265
266
267    def setStructureNumber(self,parent):
268        dialog =  setStructureNumberDialog(self.canvas)
269        if dialog.numberOK:
270            self.structureSize = dialog.number
271
272    def erode(self, parent):
273#Not implimented
274        self.canvas.delete(ALL)
275        self.mesh.erode(self.selSet,structureSize=self.structureSize)
276        self.visualiseMesh(self.mesh)
277
278
279    def dilate(self, parent):
280#Not implimented
281        self.canvas.delete(ALL)
282        self.mesh.dilate(self.selSet,structureSize=self.structureSize)
283        self.visualiseMesh(self.mesh)
284
285    def general_threshold(self,parent,function,function_description):
286        """
287        add a vertex using a window and entering x y values.
288
289        the parent attribute isn't used by this function.
290        need to userstand toolbarbutton.py to know how to
291        get rid of it.
292        """
293        if self.selSet == 'None':
294            self.selectAllTriangles(parent)
295
296        dialog = GeneralThresholdDialog(self.canvas,self.mesh.attributeTitles,function_description)
297        if dialog.minmaxValuesOk:
298            self.canvas.delete(ALL)
299            min = dialog.min
300            max = dialog.max
301            attribute_name = dialog.attribute_name
302            self.mesh.general_threshold(self.selSet,min=min,max=max,attribute_name = attribute_name,function=function)
303            self.visualiseMesh(self.mesh)
304
305    def threshold(self, parent):
306        """
307        add a vertex using a window and entering x y values.
308
309        the parent attribute isn't used by this function.
310        need to userstand toolbarbutton.py to know how to
311        get rid of it.
312        """
313        function = self.mesh.av_att
314        function_description = 'average attribute of triangle'
315        self.general_threshold(parent,function,function_description)
316
317
318    def Courant_threshold(self, parent):
319        """
320        add a vertex using a window and entering x y values.
321
322        the parent attribute isn't used by this function.
323        need to userstand toolbarbutton.py to know how to
324        get rid of it.
325        """
326        function = self.mesh.Courant_ratio
327        function_description = 'average attribute/area of triangle'
328        self.general_threshold(parent,function,function_description)
329
330    def gradient_threshold(self, parent):
331        """
332        add a vertex using a window and entering x y values.
333
334        the parent attribute isn't used by this function.
335        need to userstand toolbarbutton.py to know how to
336        get rid of it.
337        """
338        function = self.mesh.Gradient
339        function_description = 'average gradient of triangle'
340        self.general_threshold(parent,function,function_description)
341
342    def smooth_polySet(self,parent):
343        dialog = SmoothDialog(self.canvas)
344        if dialog.valueOK:
345            min_radius = dialog.min_radius
346            self._smooth_polySet(min_radius)
347
348    def _smooth_polySet(self,min_radius):
349        userVertices,userSegments,alphaSegments = \
350            self.mesh.smooth_polySet(min_radius=min_radius)
351
352        self.mesh.userVertices=[]
353        self.mesh.userSegments=[]
354        self.mesh.alphaSegments=[]
355        self.canvas.delete(ALL)
356        event = None
357        print 'len(userVertices.keys())'
358        print len(userVertices.keys())
359        print 'len(userSegments.keys())'
360        print len(userSegments.keys())
361        print 'len(alphaSegments.keys())'
362        print len(alphaSegments.keys())
363
364        #######
365        point_keys = {}
366        for vert in userVertices.keys():
367            assert not point_keys.has_key((vert.x,vert.y))
368            point_keys[(vert.x,vert.y)]=vert
369        assert len(point_keys.keys())==len(userVertices.keys())
370        #######
371
372        for v in userVertices.keys():
373            x = v.x*self.SCALE
374            y = v.y*self.SCALE
375            userVertices[(v.x,v.y)]=self.drawVertex(x,y,event)
376
377        for line in userSegments.keys():
378            v0 = userVertices[line[0]]
379            v1 = userVertices[line[1]]
380            segment = self.drawSegment(v0,v1)
381            segment.set_tag(userSegments[line].tag)
382
383        for line in alphaSegments.keys():
384            v0 = userVertices[line[0]]
385            v1 = userVertices[line[1]]
386            segment = self.drawSegment(v0,v1)
387            segment.set_tag(alphaSegments[line].tag)
388        self.visualiseMesh(self.mesh)
389
390
391    def triangles_to_polySet(self,parent):
392        userVertices,userSegments,alphaSegments = \
393            self.mesh.triangles_to_polySet(self.selSet)
394
395        self.mesh.userVertices=[]
396        self.canvas.delete(ALL)
397
398        event = None
399        print 'len(userVertices.keys())'
400        print len(userVertices.keys())
401        print 'len(userSegments.keys())'
402        print len(userSegments.keys())
403        print 'len(alphaSegments.keys())'
404        print len(alphaSegments.keys())
405
406
407        #######
408        point_keys = {}
409        for vert in userVertices.keys():
410            assert not point_keys.has_key((vert.x,vert.y))
411            point_keys[(vert.x,vert.y)]=vert
412        assert len(point_keys.keys())==len(userVertices.keys())
413        #######
414
415        for v in userVertices.keys():
416            if userVertices[v] is True:
417                x = v.x*self.SCALE
418                y = v.y*self.SCALE
419                userVertices[(v.x,v.y)]=self.drawVertex(x,y,event)
420
421        for line in userSegments.keys():
422            v0 = userVertices[line[0]]
423            v1 = userVertices[line[1]]
424            segment = self.drawSegment(v0,v1)
425            segment.set_tag(userSegments[line].tag)
426
427        for line in alphaSegments.keys():
428            v0 = userVertices[line[0]]
429            v1 = userVertices[line[1]]
430            segment = self.drawSegment(v0,v1)
431            segment.set_tag(alphaSegments[line].tag)
432        self.visualiseMesh(self.mesh)
433        #self.smooth_polySet(parent)
434
435    def selectTriangles(self,setName):
436        """
437        """
438        self.canvas.delete(ALL)
439        self.selSet = setName
440        self.visualiseMesh(self.mesh)
441
442    def selectAllTriangles(self,parent):
443        """
444        selected all triangles in the mesh
445        """
446        self.canvas.delete(ALL)
447        self.selSet = self.mesh.selectAllTriangles()
448        self.visualiseMesh(self.mesh)
449
450    def clearSelection(self,parent):
451    #FIXME looks like self.clearSelections - change name (Peter)
452        """
453        """
454        self.canvas.delete(ALL)
455        self.selSet = self.mesh.clearSelection()
456        self.visualiseMesh(self.mesh)
457
458    def visualise(self,parent):
459        self.canvas.delete(ALL)
460        self.Visualise = True
461        self.visualiseMesh(self.mesh)
462
463    def unvisualise(self,parent):
464        self.canvas.delete(ALL)
465        self.Visualise = False
466        self.visualiseMesh(self.mesh)
467
468    def createMesh(self):
469        """
470        Build the data structures for storing the mesh objects
471        """
472        self.mesh = mesh.Mesh()
473       
474        self.Vertices = visualmesh.vPoints(mesh.Vertex)
475        self.Segments = visualmesh.vSegments(mesh.Segment)
476        self.Holes = visualmesh.vPoints(mesh.Hole)
477        self.Regions = visualmesh.vRegions(mesh.Region)
478        self.UserMesh = visualmesh.vMesh([self.Vertices,self.Segments,self.Holes,self.Regions])
479       
480        self.Triangles = visualmesh.vTriangles(mesh.Triangle)
481        self.selSet='None'
482
483
484    def deleteMesh(self):
485        """
486        Delete the data structures for storing the mesh objects
487        """
488        self.mesh = None
489        self.Vertices = None
490        self.Segments = None
491        self.Triangles = None
492       
493    def addCylinders(self):
494        """
495        Automatically add some verts and segs to the mesh.Used in Debugging
496        """
497        from coordinate_transforms.geo_reference import Geo_reference,DEFAULT_ZONE
498        offset_x = 30
499        offset_y = 40
500        v1 = self.drawVertex(0-offset_x,0-offset_y,None)
501        v2 = self.drawVertex(50-offset_x,2-offset_y,None)
502        v3 = self.drawVertex(70-offset_x,30-offset_y,None)
503        v4 = self.drawVertex(25-offset_x,50-offset_y,None)
504        v5 = self.drawVertex(-10-offset_x,20-offset_y,None)
505        #v5 = self.drawVertex(25,50,None)
506        s1 = self.drawSegment(v1,v2)
507        s1.set_tag("s1")
508        s2 = self.drawSegment(v2,v3)
509        s2.set_tag("s2")
510        s3 = self.drawSegment(v3,v4)
511        s3.set_tag("s3")
512        s4 = self.drawSegment(v4,v5)
513        s4.set_tag("s4")
514        s5 = self.drawSegment(v1,v5)
515        s5.set_tag("s5")
516
517        x_origin = 10-offset_x
518        y_origin = 20-offset_y
519        r = 10
520        pi = math.pi
521        num_of_cuts = 100
522        cuts = []
523        factor = 2* math.pi/num_of_cuts
524        for cut in range(num_of_cuts):
525             cuts.append(cut*factor)
526       
527        for radius in cuts:
528            x = x_origin + r * math.cos(radius)
529            y = y_origin + r * math.sin(radius)
530            v = self.drawVertex(x,y,None)
531            if not radius == 0.0:   # FIXME
532                self.drawSegment(v,v_old)
533            else:
534                v_first = v
535            v_old = v
536        self.drawSegment(v,v_first)
537        region = self.drawRegion(x_origin, y_origin, 0)
538        region.setTag("setheight5")
539       
540       
541        x_origin = 30-offset_x
542        y_origin = 30-offset_y
543        r = 5
544        pi = math.pi
545        num_of_cuts = 100
546        cuts = []
547        factor = 2* math.pi/num_of_cuts
548        for cut in range(num_of_cuts):
549             cuts.append(cut*factor)
550       
551        for radius in cuts:
552            x = x_origin + r * math.cos(radius)
553            y = y_origin + r * math.sin(radius)
554            v = self.drawVertex(x,y,None)
555            if not radius == 0.0:   # FIXME
556                self.drawSegment(v,v_old)
557            else:
558                v_first = v
559            v_old = v
560        self.drawSegment(v,v_first)
561        region = self.drawRegion(x_origin, y_origin, 0)
562        region.setTag("setheight10")
563        self.mesh.geo_reference = Geo_reference(zone=DEFAULT_ZONE,
564                                                xllcorner=offset_x,
565                                                yllcorner=offset_y)
566           
567        #Since the new vertex may be off screen
568        self.scrolledcanvas.resizescrollregion()
569
570        # generate the mesh
571        minAngle = 30.0
572        numTriangles = 20000
573        self.clearSelections()
574        self.canvas.delete(ALL)
575        self.mesh = self.MeshGenAreaNumTriangles (minAngle,
576                                          numTriangles,
577                                          self.mesh)
578        self.UserMeshChanged = False
579        self.visualiseMesh(self.mesh)
580        print "Mesh Generation finished"
581           
582    def selectFunc(self, tag):
583        """
584        Change the current mode class
585        When changing from one mode to another
586        """
587        self.mouseDownCurFunc = self.mouseDownFunc[tag]
588        self.curModeClass = self.modeClass[tag]
589        self.clearSelections()
590        self.canvas.config(cursor='arrow')
591#        I can make it arrow, but it will change back to pointer, after
592#        adding an object
593#        if self.curFunc == self.func['pointer']:
594#            self.canvas.config(cursor='arrow')
595#         else:
596#             self.canvas.config(cursor='crosshair')
597
598    def clearSelections(self):
599        """
600        deselect objects that have been selected
601        """
602        if self.selMeshObject:
603            self.deselectMeshObject(self.selMeshObject, self.selMeshTag)       
604        if self.selVertex:
605            self.deselectVertex(self.selVertex, self.selVertexTag)
606         
607       
608    def selectZoom(self, tag):
609        """
610        Zoom in or out of the current mesh view
611        """
612        fraction = string.atof(tag)
613        self.SCALE *= fraction
614        self.scrolledcanvas.scale(ALL, 0, 0, fraction, fraction)
615
616        # Redraw all of the vertices, holes and regions,
617        #so the squares representing vertices
618        # don't get bigger
619        vertices = self.mesh.getUserVertices()
620        holes = self.mesh.getHoles()
621        regions = self.mesh.getRegions()
622        MeshObjects  = vertices + holes + regions
623
624        # make a list of tags to delete
625        guiIDs = [getattr(MeshObjects[i],'guiID') for i in xrange(len(MeshObjects))]
626        apply(self.canvas.delete, guiIDs)
627        for obj in MeshObjects:
628            if self.selVertex == obj:
629                obj.draw(self.canvas,obj.guiID,  scale =self.SCALE ,colour= VERT_SELECT_ADDING_SEG_COLOR)
630            elif self.selMeshObject == obj:
631                obj.draw(self.canvas,obj.guiID,  scale =self.SCALE ,colour= SELECT_COLOR)
632            else:
633                obj.draw(self.canvas,obj.guiID,  scale =self.SCALE ) 
634        top, bottom = self.scrolledcanvas.xview()
635        xcenter  = (top + bottom)/2
636        xdiff =  xcenter - top 
637        xcnew = xcenter - xdiff/fraction
638       
639        top, bottom = self.scrolledcanvas.yview()
640        ycenter = (top + bottom)/2
641        ydiff = ycenter - top
642        ycnew = ycenter - ydiff/fraction
643       
644        self.scrolledcanvas.resizescrollregion()
645        # update so the moveto calls will work...
646        self.scrolledcanvas.update()
647        # but calling update now does make things jerky
648        self.canvas.xview_moveto(xcnew)
649        self.canvas.yview_moveto(ycnew)
650       
651   
652    def windowAddVertex (self, parent):
653        """
654        add a vertex using a window and entering x y values.
655
656        the parent attribute isn't used by this function.
657        need to userstand toolbarbutton.py to know how to
658        get rid of it.
659        """
660       
661        dialog = AddVertexDialog(self.canvas)
662        if dialog.xyValuesOk:
663            print dialog.x
664            print dialog.y
665            self.drawVertex(dialog.x*self.SCALE,dialog.y*self.SCALE,None)
666            #Since the new vertex may be off screen
667            self.ResizeToFit()
668        else:
669            print "bad values"
670   
671    def windowDefault (self, parent):
672        """
673       
674        the parent attribute isn't used by this function.
675        need to userstand toolbarbutton.py to know how to
676        get rid of it.
677        """
678        # self.UserMesh is a vMesh instance
679        self.UserMesh.defaultWindow(self.canvas, self.curModeClass)
680   
681    def windowEdit (self, parent):
682        """
683
684        the parent attribute isn't used by this function.
685        need to userstand toolbarbutton.py to know how to
686        get rid of it.
687        """
688        if self.selMeshObject:   
689            self.UserMeshChanged = self.UserMesh.editWindow(self.canvas,
690                                     self.selMeshObject,
691                                     self.UserMeshChanged)
692   
693    def autoSegmentButton (self, parent):
694        self.autoSegment()
695
696       
697    def autoSegmentGiveAlphaButton (self, parent):
698        dialog = AutoSegmentDialog(self.canvas, self.meshLastAlpha)
699        if dialog.use_optimum.get() == SET_ALPHA:
700            if dialog.alphaValueOk:
701                self.autoSegment(alpha = dialog.alpha,
702                                 raw_boundary=dialog.raw_boundary.get(),
703                                 remove_holes=dialog.remove_holes.get(),
704                                 smooth_indents=dialog.smooth_indents.get(),
705                                 expand_pinch=dialog.expand_pinch.get())
706            else:
707                 showerror('pMesh',
708                      'Bad alpha value.')
709        else:
710            self.autoSegment(raw_boundary=dialog.raw_boundary.get(),
711                             remove_holes=dialog.remove_holes.get(),
712                             smooth_indents=dialog.smooth_indents.get(),
713                             expand_pinch=dialog.expand_pinch.get())
714           
715       
716    def autoSegment (self, alpha = None,
717                                 raw_boundary=True,
718                                 remove_holes=False,
719                                 smooth_indents=False,
720                                 expand_pinch=False ):
721        """
722        add Segments to bound all vertices
723       
724        """
725        if len(self.mesh.getUserVertices()) >= 3:
726            try:
727                newsegs, ObjectsToVisuallyDelete, self.meshLastAlpha = \
728                     self.mesh.autoSegment(alpha=alpha,
729                                           remove_holes=remove_holes,
730                                           smooth_indents=smooth_indents,
731                                           expand_pinch=expand_pinch)
732            except AlphaError:
733                showerror('pMesh',
734                          'Unable to autosegment.')
735            else:
736
737                for drawOb in ObjectsToVisuallyDelete:
738                    self.UserMesh.unvisualise(drawOb, self.canvas)
739               
740                for segment in newsegs:
741                    self.serial +=1
742                    self.uniqueID = 'M*%d' % self.serial
743                    self.Segments.visualise(segment,
744                                            self.uniqueID,
745                                            self.canvas,
746                                            self.SCALE)
747           
748        else:
749            showerror('pMesh',
750                      'Three or more vetices are needed to be able to autosegment.')
751
752
753    def autoSegmentFilter (self):
754        dialog = AutoSegmentFilterDialog(self.canvas)
755        newsegs, ObjectsToVisuallyDelete, self.meshLastAlpha = \
756                 self.mesh.autoSegmentFilter(raw_boundary=dialog.raw_boundary.get(),
757                             remove_holes=dialog.remove_holes.get(),
758                             smooth_indents=dialog.smooth_indents.get(),
759                             expand_pinch=dialog.expand_pinch.get())
760        #print "newsegs",newsegs
761        #print "ObjectsToVisuallyDelete",ObjectsToVisuallyDelete
762           
763        for drawOb in ObjectsToVisuallyDelete:
764            self.UserMesh.unvisualise(drawOb, self.canvas)
765               
766        for segment in newsegs:
767            self.serial +=1
768            self.uniqueID = 'M*%d' % self.serial
769            self.Segments.visualise(segment,
770                                    self.uniqueID,
771                                    self.canvas,
772                                    self.SCALE)
773       
774    def joinVerticesButton (self, parent):
775        self.joinVertices()
776       
777    def joinVertices (self):
778        """
779        add Segments to connect all vertices
780       
781        the parent attribute isn't used by this function.
782        need to userstand toolbarbutton.py to know how to
783        get rid of it.
784        """
785        if len(self.mesh.getUserVertices()) >= 3:
786            newsegs = self.mesh.joinVertices()
787            for segment in newsegs:
788                self.serial +=1
789                self.uniqueID = 'M*%d' % self.serial
790                self.Segments.visualise(segment,
791                                        self.uniqueID,
792                                        self.canvas,
793                                        self.SCALE)
794        else:
795            showerror('pMesh',
796                      'Three or more vetices are needed to be able to join vertices.')
797       
798    def windowMeshGen (self, parent):
799        """
800        The parent attribute isn't used by this function.
801        need to understand toolbarbutton.py to know how to
802        get rid of it.
803        """
804        # Put exceptions round things.
805        #catch failure in self.mesh.generateMesh
806        dialog = MeshGenDialog(self.canvas,
807                               self.MeshMinAngle,
808                               self.MeshMaxArea,
809                               self.MeshnumTriangles,
810                               self.MeshMaxAreaLast)
811       
812        if dialog.ValuesOk:
813            print dialog.minAngle
814            print dialog.maxArea
815           
816            self.clearSelections()
817            self.canvas.delete(ALL)
818            if dialog.goodMaxArea == True:
819                self.MeshMinAngle = dialog.minAngle
820                self.MeshMaxArea = dialog.maxArea
821                self.MeshMaxAreaLast = True
822               
823                self.mesh = self.MeshGenAreaAngle (dialog.minAngle,
824                                          dialog.maxArea,
825                                          self.mesh)
826            elif dialog.goodNumTriangles == True:
827                self.MeshMinAngle = dialog.minAngle
828                self.MeshnumTriangles  = dialog.numTriangles
829                self.MeshMaxAreaLast = False
830               
831                self.mesh = self.MeshGenAreaNumTriangles (dialog.minAngle,
832                                          dialog.numTriangles,
833                                          self.mesh)
834            else:
835                pass
836            print "userMeshChanged = False"
837            self.UserMeshChanged = False
838            self.visualiseMesh(self.mesh)
839            print "Mesh Generation finished"
840           
841    def MeshGenAreaAngle (self, minAngle, maxArea, mesh):
842        """
843        Generate a mesh, given a minAngle and max area
844        """
845        tempMesh = mesh
846        try:
847            tempMesh.generateMesh(mode = "pzq"+str(minAngle)
848                                  +"a"+str(maxArea)
849                                  +"a") #So areas for regions will be used
850        except AttributeError : # can't catch PyEval_RestoreThread
851            # This doesn't catch tempMesh.generateMesh failing
852            tempMesh = mesh
853        return tempMesh
854       
855
856    def MeshGenAreaNumTriangles (self, minAngle, numTriangles, mesh):
857        """
858        Generate a mesh, given a minAngle and rough # of triangles
859        """
860        #get big triangles
861        #calc area
862        #calc max triangle area
863        #
864        tempMesh = mesh
865        try:
866            tempMesh.generateMesh("pzq1")
867        except AttributeError : # can't catch PyEval_RestoreThread
868            # This doesn't catch tempMesh.generateMesh failing
869            pass
870        meshArea = 0
871        for triangle in tempMesh.getTriangulation():
872             meshArea += triangle.calcArea()
873        print "meshArea: ", meshArea
874
875        maxArea = meshArea/numTriangles
876
877       
878        return self.MeshGenAreaAngle (minAngle,
879                                      maxArea,
880                                      self.mesh)
881       
882    def mouseDown(self, event):
883        """
884        On a mouse down event, depending on the current state,
885        either add a vertex or a seg etc
886        """
887        self.curObject = None
888        self.lastx = self.startx = self.canvas.canvasx(event.x)
889        #The screen canvas has y 'flipped'.  -1* unflips it
890        self.lasty = self.starty = -1*self.canvas.canvasy(event.y)
891        print "----------------------"
892        self.mouseDownCurFunc( self.lastx,
893                               self.lasty,event) #!!! remove the event?
894                                                 # do last
895   
896    def rightMouseUp(self, event):
897        """
898        On a right mouse button up event select the nearest object.
899        """
900        found=False
901        if event.widget.find_withtag(CURRENT): # if there's a widget with a tag
902            [tag,string] = self.canvas.gettags(CURRENT) # get a list of them
903            print "tag",tag  #tags ('M*1008', 'current')
904            if tag[:2] == 'M*':   #!!! this can be removed when there are
905                #    only mesh objects on screen
906                #key, value = string.split(tag, '*')
907                objectID = tag
908                print "Found!! objectID:", objectID
909               
910                meshObjects = self.getAllUserMeshObjects()
911                # It may be a triangle, which is ignored
912                if meshObjects.hasKey(objectID):
913                    selMeshObject = meshObjects.getMeshObject(objectID)
914                    found = True
915                    print "Found! selMeshObject", selMeshObject
916                    #Only select one object at a time
917                    if self.selMeshObject:
918                        self.deselectMeshObject(self.selMeshObject, self.selMeshTag)
919                    self.selectMeshObject(selMeshObject,objectID)
920
921    def getAllUserMeshObjects(self):
922        return self.UserMesh
923       
924    def DeleteSelectedMeshObject(self, event):
925        """
926        if an object is selected, delete it.
927        """
928        if self.selMeshObject:
929            #an object is selected
930            #first deselect the vertex, for selecting a segment
931            if self.selVertex:
932                self.deselectVertex(self.selVertex, self.selVertexTag)
933            ObjectsToVisuallyDelete = self.mesh.deleteMeshObject (self.selMeshObject)
934            for drawOb in ObjectsToVisuallyDelete:
935                self.UserMesh.unvisualise(drawOb, self.canvas)
936               
937            self.selMeshObject = None
938            self.selMeshTag = None
939           
940    def selectMeshObject(self, meshObject, objectID):
941        """
942        selected a mesh object.
943        """
944        self.canvas.delete(objectID)
945        self.selMeshObject = meshObject
946        self.selMeshTag = objectID
947        meshObject.draw(self.canvas,objectID, scale =self.SCALE ,colour = SELECT_COLOR)
948   
949    def deselectMeshObject(self, meshObject, objectID):
950        """
951        deselected a mesh object.
952        """
953        self.canvas.delete(objectID)
954        self.selMeshObject = None
955        self.selMeshTag = None
956        if isinstance(meshObject, mesh.Segment):
957            meshObject.draw(self.canvas,objectID,
958                        scale =self.SCALE ,colour = SEG_COLOUR)
959        else:
960            meshObject.draw(self.canvas,objectID,
961                        scale =self.SCALE )
962           
963    def drag(self,x,y,event):
964        """
965        Hack function.  called when in select and left mouse goes down
966        """
967        pass
968   
969   
970    def drawEastingNorthingVertex(self,x,y,event):
971        """
972        draw a vertex object, plus add it to the mesh data structure
973
974        event isn't used
975        """
976        self.serial +=1
977        self.uniqueID = 'M*%d' % self.serial
978        #x_scaled =  self.SCALE*x
979        #y_scaled = -1*self.SCALE*y
980        #print "x y:", x,y
981        vert = self.Vertices.draw(x-self.mesh.geo_reference.get_xllcorner,
982                                  y-self.mesh.geo_reference.get_yllcorner,
983                                  self.mesh,
984                                  self.uniqueID,
985                                  self.SCALE,
986                                  self.canvas,
987                                  event) #FIXME why is event passed on.
988        self.UserMeshChanged = True
989        return vert
990   
991    def drawVertex(self,x,y,event):
992        """
993        draw a vertex object, plus add it to the mesh data structure
994
995        event isn't used
996        """
997        self.serial +=1
998        self.uniqueID = 'M*%d' % self.serial
999        #x_scaled =  self.SCALE*x
1000        #y_scaled = -1*self.SCALE*y
1001        #print "x y:", x,y
1002        vert = self.Vertices.draw(x,y,self.mesh,self.uniqueID,self.SCALE,self.canvas,event)
1003        self.UserMeshChanged = True
1004        return vert
1005     
1006    def drawHole(self,x,y,event):
1007        """
1008        draw a hole object, plus add it to the mesh data structure
1009
1010        event isn't used
1011        """
1012        self.serial +=1
1013        self.uniqueID = 'M*%d' % self.serial
1014        self.userMeshChanged = True
1015        hole = self.Holes.draw(x,y,self.mesh,self.uniqueID,self.SCALE,self.canvas,event)
1016        return hole   
1017   
1018    def drawRegion(self,x,y,event):
1019        """
1020        draw a region object, plus add it to the mesh data structure
1021
1022        event isn't used
1023        """
1024        self.serial +=1
1025        self.uniqueID = 'M*%d' % self.serial
1026        region = self.Regions.draw(x,y,self.mesh,self.uniqueID,self.SCALE,self.canvas,event)
1027        return region
1028   
1029    def selectSegmentPoint(self,x,y, event):
1030        """
1031        logic when selecting a vertex object to add a segment
1032        """
1033        found=False
1034        if event.widget.find_withtag(CURRENT): # if there's a widget with a tag
1035            [tag,string] = self.canvas.gettags(CURRENT) # get a list of them
1036            print "tag",tag  #tags ('M*1008', 'current')
1037            objectID = tag
1038            #print "Found!! objectID:", objectID
1039            if self.Vertices.hasKey(objectID): #isinstance(self.meshObjects[objectID],mesh.Vertex):
1040                vertex = self.Vertices.getMeshObject(objectID)
1041                found = True
1042                print "Found! vertex", vertex
1043           
1044        if found and self.selVertex == vertex:
1045            print "The selected vertex has already been selected"
1046            #The selected vertex has already been selected
1047            # therefore deselect it
1048            self.deselectVertex(self.selVertex, self.selVertexTag)
1049            found = False
1050                 
1051        if found: 
1052            #A vertex has been selected!
1053            if self.selVertex:
1054                if self.mesh.isUserSegmentNew(self.selVertex,vertex):
1055                    #vertex is the 2nd vertex
1056                    self.drawSegment(vertex,self.selVertex)
1057                    self.deselectVertex(self.selVertex, self.selVertexTag)
1058                    self.selectVertex(vertex,objectID)
1059            else:
1060                print "vertex is the 1st vertex" 
1061                #vertex is the 1st vertex
1062                self.selectVertex(vertex,objectID)
1063        else:
1064            print " There are no widgets.  This happen's too much"
1065                   
1066
1067    def selectVertex(self, vertex,objectID):
1068        """
1069        select a vertex object when adding a segment
1070        """
1071        self.canvas.delete(objectID)
1072        self.selVertex = vertex
1073        self.selVertexTag = objectID
1074        vertex.draw(self.canvas,objectID, scale =self.SCALE ,colour = VERT_SELECT_ADDING_SEG_COLOR)
1075   
1076    def deselectVertex(self, vertex,objectID):
1077        """
1078        deselect a vertex object when adding a segment
1079        """
1080        self.canvas.delete(objectID)
1081        self.selVertex = None
1082        self.selVertexTag = None
1083        vertex.draw(self.canvas,objectID,  scale =self.SCALE )
1084         
1085    def drawSegment(self,v1,v2):
1086        """
1087        Create a seg object, draw it and add it to the mesh data structure
1088        """
1089        self.serial +=1
1090        self.uniqueID = 'M*%d' % self.serial
1091        self.userMeshChanged = True
1092        seg = self.Segments.draw(v1,v2,self.mesh,self.uniqueID,self.SCALE,self.canvas,None)
1093        return seg
1094    def printGeoReference(self):
1095        try:
1096            print "geo reference", self.mesh.geo_reference
1097        except:
1098            print "no geo reference"
1099       
1100    def visualiseMesh(self,mesh):
1101        """
1102        visualise vertices, segments, triangulation, holes
1103        """
1104        if self.Visualise:
1105            for triangle in mesh.getTriangulation():
1106                self.serial +=1
1107                self.uniqueID = 'M*%d' % self.serial
1108                self.Triangles.visualise(triangle,
1109                                        self.uniqueID,
1110                                        self.canvas,
1111                                        self.SCALE)
1112
1113        if self.Visualise:
1114            Triangles = mesh.sets[mesh.setID[self.selSet]]
1115            for triangle in Triangles:
1116                triangle.draw(self.canvas,1,
1117                              scale = self.SCALE,
1118                              colour = SET_COLOUR)
1119
1120        for segment in mesh.getUserSegments():
1121            self.serial +=1
1122            self.uniqueID = 'M*%d' % self.serial
1123            self.Segments.visualise(segment,
1124                                    self.uniqueID,
1125                                    self.canvas,
1126                                    self.SCALE)
1127        for vertex in mesh.getUserVertices():
1128            self.serial +=1
1129            self.uniqueID = 'M*%d' % self.serial
1130            self.Vertices.visualise(vertex,
1131                                    self.uniqueID,
1132                                    self.canvas,
1133                                    self.SCALE)
1134           
1135        for hole in mesh.getHoles():
1136            self.serial +=1
1137            self.uniqueID = 'M*%d' % self.serial
1138            self.Holes.visualise(hole,
1139                                    self.uniqueID,
1140                                    self.canvas,
1141                                    self.SCALE)   
1142        for region in mesh.getRegions():
1143            self.serial +=1
1144            self.uniqueID = 'M*%d' % self.serial
1145            self.Regions.visualise(region,
1146                                    self.uniqueID,
1147                                    self.canvas,
1148                                    self.SCALE)
1149    def normalise4ObjMesh(self):
1150        if self.mesh:
1151            self.clearSelections()
1152            self.canvas.delete(ALL)
1153            self.mesh.normaliseMesh(400,-200,20)
1154            self.visualiseMesh(self.mesh)
1155            self.ResizeToFit()
1156            self.ResizeToFit()
1157           
1158    def normaliseMesh(self):
1159        if self.mesh:
1160            self.clearSelections()
1161            self.canvas.delete(ALL)
1162            self.mesh.normaliseMesh(1,0,1)
1163            self.visualiseMesh(self.mesh)
1164            self.ResizeToFit()
1165            self.ResizeToFit()
1166           
1167       
1168    def clearMesh(self):
1169        """Clear the current mesh object, and the canvas """
1170       
1171        self.clearSelections()
1172        self.canvas.delete(ALL)
1173        self.deleteMesh()
1174        self.initData()
1175        self.createMesh()
1176
1177    def exportObj(self):
1178        fileType = "obj"
1179        fileTypeDesc = "obj mesh"
1180       
1181        ofile = tkFileDialog.asksaveasfilename(filetypes=[(fileTypeDesc, fileType),
1182                                                ("All Files", "*")])
1183        if ofile:
1184            addOn = "." + fileType
1185            jumpback = - len(addOn)
1186            if ofile[jumpback:] != addOn: 
1187                ofile = ofile + addOn
1188            try:
1189                self.mesh.exportASCIIobj(ofile)
1190            except IOError:
1191                showerror('Export ASCII file',
1192                                   'Can not write to file.')
1193            except RuntimeError:
1194                showerror('Export ASCII file',
1195                                   'No triangulation to export.')
1196
1197   
1198
1199    def ImportUngenerate(self):
1200        ofile = tkFileDialog.askopenfilename(initialdir=self.currentPath,
1201                                             filetypes=[ ("ungenerated polygon information", "txt"),
1202                                           ("All Files", "*")])
1203        if ofile == "":
1204            # The user cancelled the loading action
1205            return
1206       
1207        try:
1208            self.clearSelections()
1209            self.canvas.delete(ALL)
1210            dict = mesh.importUngenerateFile(ofile)
1211            self.mesh.addVertsSegs(dict)
1212           
1213        except SyntaxError: 
1214            #this is assuming that the SyntaxError is thrown in
1215            #loadxyafile
1216            showerror('File error',
1217                      ofile + ' is not in the correct format.')
1218        except IOError: 
1219            #!!! this error type can not be thrown?
1220            showerror('File error',
1221                      'file ' + ofile + ' could not be found.')
1222        except RuntimeError: 
1223            showerror('File error',
1224                  'file ' + ofile + ' has an unknown file type.')
1225   
1226        self.visualiseMesh(self.mesh)
1227        self.ResizeToFit()
1228       
1229    def exportASCIIsegmentoutlinefile(self):
1230       
1231        ofile = tkFileDialog.asksaveasfilename(initialdir=self.currentPath,
1232                                               filetypes=[("mesh", "*.tsh *.msh"),
1233                                             ("All Files", "*")])
1234           
1235        if ofile:
1236            # .tsh is the default file format
1237            if (ofile[-4:] == ".tsh" or ofile[-4:] == ".msh"): 
1238                self.currentFilePathName = ofile
1239            else:
1240                self.currentFilePathName = ofile + ".tsh"
1241               
1242            try:
1243                self.mesh.exportASCIIsegmentoutlinefile(ofile)
1244            except IOError: #FIXME should this function be throwing any errors?
1245                showerror('Export ASCII file',
1246                                   'Can not write to file.')
1247
1248    def exportPointsFile(self):
1249        ofile = tkFileDialog.asksaveasfilename(initialdir=self.currentPath,
1250                                         filetypes=[("point files",
1251                                                     "*.xya *.pts"),
1252                                                ("All Files", "*")])
1253        if ofile:
1254            # .xya is the default file format
1255            if (ofile[-4:] == ".xya" or ofile[-4:] == ".pts"): 
1256                self.currentFilePathName = ofile
1257            else:
1258                self.currentFilePathName = ofile + ".xya"
1259               
1260            try:
1261                self.mesh.exportPointsFile(ofile)
1262            except IOError:
1263                showerror('Export ASCII file',
1264                                   'Can not write to file.')
1265                   
1266    def importFile(self):
1267        """
1268        import mesh data from a variety of formats (currently 2!)
1269        """
1270        print "self.currentPath",self.currentPath
1271        ofile = tkFileDialog.askopenfilename(initialdir=self.currentPath,
1272                                             filetypes=[ ("text Mesh", "*.tsh *.msh"),
1273                                                         ("points", "*.xya *.pts"),
1274                                           ("All Files", "*")])
1275        if ofile == "":
1276            # The user cancelled the loading action
1277            return
1278       
1279        try:
1280            newmesh = mesh.importMeshFromFile(ofile)
1281            self.currentPath, dummy = os.path.split(ofile)
1282            #print "be good self.currentPath",self.currentPath
1283            self.currentFilePathName = ofile
1284            self.clearMesh()
1285            self.mesh = newmesh
1286
1287            #FIXME - to speed things up, don't visualise the mesh
1288            # use ResizeToFitWrapper
1289            self.visualiseMesh(self.mesh)
1290            self.ResizeToFit()
1291       
1292        except IOError: 
1293            #!!! this error type can not be thrown?
1294            showerror('File error',
1295                      'file ' + ofile + ' could not be loaded.')
1296
1297        except RuntimeError: 
1298            showerror('File error',
1299                  'file ' + ofile + ' has an unknown file type.')
1300        # Could not get the file name to showup in the title
1301        #appname =  ofile + " - " + APPLICATION_NAME
1302        #print "appname",appname
1303       
1304        except load_mesh.loadASCII.TitleAmountError: 
1305            showerror('File error',
1306                  'file ' + ofile + ' has a bad title line (first line).')
1307
1308   
1309    def ResizeToFitWrapper(self, Parent):
1310        """
1311        The parent attribute isn't used by this function.
1312        need to understand toolbarbutton.py to know how to
1313        get rid of it.
1314        """
1315        self.ResizeToFit()
1316       
1317    def ResizeToFit(self):
1318        """Visualise the mesh so it fits in the window"""
1319        if self.mesh.getUserVertices() == []:
1320            return #There are no vertices!
1321        # Resize the window
1322        self.scrolledcanvas.resizescrollregion()
1323        # I need this so the xview values are correct
1324        self.scrolledcanvas.update()
1325       
1326        xtop, xbottom = self.scrolledcanvas.xview()
1327        ytop, ybottom = self.scrolledcanvas.yview()
1328        xdiff = xbottom-xtop
1329        ydiff = ybottom-ytop
1330        if xdiff == 1 and xdiff == 1:
1331            #The mesh might be too small.
1332            #Make it too large, then resize
1333            #!!! Recursive hack.  Should be a better way
1334            fraction = 50
1335            self.SCALE *= fraction
1336            self.scrolledcanvas.scale(ALL, 0, 0, fraction, fraction)
1337            self.ResizeToFit()
1338        else:
1339            # without 0.99 some of the mesh may be off screen
1340            fraction = 0.99*min(xdiff,ydiff) 
1341            self.selectZoom(fraction)
1342           
1343    def saveDrawing(self):
1344        """
1345        Save the current drawing
1346        """
1347        #print "dsg!!! self.currentFilePathName ",self.currentFilePathName
1348        if (self.currentFilePathName[-4:] != ".tsh" or
1349            self.currentFilePathName[-4:] != ".msh"):
1350            # force user to choose a name
1351            self.saveAsDrawing()
1352        else:
1353            self.exportASCIItriangulationfile(self.currentFilePathName)
1354
1355    def saveAsDrawing(self):
1356        """
1357        Save the current drawing, prompting for a file name
1358        """
1359        ofile = tkFileDialog.asksaveasfilename(initialdir=self.currentPath,
1360                                               filetypes=[("mesh", "*.tsh *.msh"),
1361                                             ("All Files", "*")])
1362           
1363        if ofile:
1364            # .tsh is the default file format
1365            if (ofile[-4:] == ".tsh" or ofile[-4:] == ".msh"): 
1366                self.currentFilePathName = ofile
1367            else:
1368                self.currentFilePathName = ofile + ".tsh"
1369            self.exportASCIItriangulationfile(self.currentFilePathName)
1370
1371    def exportASCIItriangulationfile(self,currentFilePathName):
1372        """
1373        Have a warning prompt when saving a mesh where the generated mesh is
1374        different from the user mesh - eg boundary tags that aren't carried
1375        thru. Warning ~"Mesh not generated after changes.  Generate mesh?  "
1376        - cancel, don't gen, don't save.  Yes - generate mesh, go to save
1377        screen.  No - goto save screen.  To implement this need to know when
1378        the user has done a change, and the mesh hasn't been generated.  If
1379        there is no generated mesh do not prompt.
1380        """
1381        #print "self.UserMeshChanged",self.UserMeshChanged
1382        #print "self.mesh.isTriangulation()",self.mesh.isTriangulation()
1383        if (self.UserMeshChanged) and self.mesh.isTriangulation():
1384           
1385            m = _show("Warning",
1386                                   "A triangulation has not been generated, after mesh changes.  Generate triangulation before saving?",
1387                                   icon=QUESTION, 
1388                                   type=YESNOCANCEL)
1389            if m == "no":
1390                self.mesh.export_mesh_file(currentFilePathName)
1391                self.UserMeshChanged = False
1392            elif m == "cancel":
1393                pass
1394            elif m == "yes":
1395                self.windowMeshGen(None)
1396                self.mesh.export_mesh_file(currentFilePathName)
1397        else:
1398            self.mesh.export_mesh_file(currentFilePathName)
1399            self.UserMeshChanged = False
1400           
1401    def initData(self):
1402        """
1403        Initialise various lists and flags
1404        """
1405        self.serial      = 1000
1406        self.currentFilePathName = 'untitled'
1407
1408        # these are attributes I've added
1409        self.SCALE       = 1
1410        self.selVertex   = None     #The last vertex selected, in draw seg mode
1411        self.selVertexTag   = None     # The selected vertex drawn object tag
1412        self.mesh        = None
1413        self.MeshMinAngle = 20
1414        self.MeshMaxArea = 200
1415        self.MeshnumTriangles = 20
1416        self.MeshMaxAreaLast = False
1417        self.selMeshObject = None  # The mesh object selected in the current mode
1418        self.selMeshTag = None
1419        mesh.Segment.set_default_tag("")
1420        self.UserMeshChanged = False
1421        self.meshLastAlpha = None
1422       
1423        self.Visualise = True #Is the mesh shown or not?
1424   
1425    def ipostscript(self):
1426        """
1427        Print the canvas as a postscript file
1428        """
1429        ofile = tkFileDialog.asksaveasfilename(initialdir=self.currentPath,
1430                                               filetypes=[("postscript", "ps"),
1431                                                          ("All Files", "*")]) 
1432        if ofile:
1433            if ofile[-3:] != ".ps": 
1434                ofile = ofile + ".ps"
1435            postscript = self.canvas.postscript()
1436            fd = open(ofile, 'w')
1437            fd.write(postscript)
1438            fd.close()
1439       
1440    def close(self):
1441        self.quit()
1442       
1443    def createInterface(self):
1444        """
1445        Call all functions that create the GUI interface
1446        """
1447        self.initData()
1448        self.createMesh()
1449        AppShell.AppShell.createInterface(self)
1450        self.createButtons()
1451        self.createMenus()
1452        self.createBase()
1453        self.createTools()
1454        self.createZooms()
1455        self.createEdits()
1456        self.createSetTools()
1457        self.createSetIcons()
1458        self.createVisualiseIcons()
1459        #self.addCylinders() # !!!DSG start pmesh with a triangle
1460        self.selectFunc('pointer')
1461        self.currentPath = os.getcwd() 
1462
1463    def loadtestmesh(self,ofile):
1464        """
1465        debugging script to test loading a file
1466        """
1467        fd = open(ofile)
1468        a = mesh.Vertex (-10.0, 0.0)
1469        d = mesh.Vertex (0.0, 4.0)
1470        f = mesh.Vertex (4.0,0.0)
1471        g = mesh.Vertex (-5.0,5.0)
1472       
1473        s1 = mesh.Segment(a,d)
1474        s2 = mesh.Segment(d,f)
1475        s3 = mesh.Segment(a,f)
1476
1477        r1 = mesh.Region(0.3, 0.3)
1478       
1479        m = mesh.Mesh(userVertices=[a,d,f,g], userSegments=[s1,s2,s3], regions=[r1] )
1480       
1481        fd.close()
1482        print 'returning m'
1483        return oadtestmesh(ofile)
1484         
1485class  AddVertexDialog(Dialog):
1486    """
1487    Dialog box for adding a vertex by entering co-ordindates
1488    """
1489    def body(self, master):
1490        """
1491        GUI description
1492        """
1493        self.title("Add New Vertex")
1494       
1495        Label(master, text='X position:').grid(row=0, sticky=W)
1496        Label(master, text='Y position:').grid(row=1, sticky=W)
1497
1498        self.xstr   = Entry(master, width = 16, name ="entry")
1499        self.ystr  = Entry(master, width = 16)
1500       
1501        self.xstr.grid(row=0, column=1, sticky=W)
1502        self.ystr.grid(row=1, column=1, sticky=W)
1503        self.xstr.focus_force()
1504        self.= 0
1505        self.= 0
1506        self.xyValuesOk = False
1507 
1508
1509    def apply(self):
1510        """
1511        check entered values
1512        """
1513        try:
1514            self.x = float(self.xstr.get())
1515            self.y = float(self.ystr.get())
1516            self.xyValuesOk = True
1517           
1518        except ValueError:
1519            showerror('Bad Vertex values',
1520                                   'X Y values are not numbers.')
1521       
1522
1523class  AutoSegmentDialog(Dialog):
1524    """
1525    Dialog box for adding segments
1526    """
1527    def __init__(self, parent, alpha):
1528        self.alpha = alpha
1529        Dialog.__init__(self, parent)
1530       
1531    def body(self, master):
1532        """
1533        GUI description
1534        """
1535        self.title("Automatically Add Segments")
1536
1537        self.use_optimum = IntVar()
1538        self.use_optimum.set(AUTO) # should initialise the radio buttons.
1539                                   #  It doesn't
1540                                   
1541        #self.use_optimum.set(NO_SELECTION)
1542        self.ck = Radiobutton(master, value = AUTO, variable=self.use_optimum) 
1543        self.ck.grid(row=1, column=0)
1544        Label(master, text='Use optimum alpha').grid(row=1, column=1, sticky=W)
1545
1546        self.ck2 = Radiobutton(master, value = SET_ALPHA,
1547                               variable=self.use_optimum) 
1548        self.ck2.grid(row=2, column=0)
1549       
1550        Label(master, text='alpha:').grid(row=2, column=1, sticky=W)
1551        if (self.alpha):
1552            alphaVar = StringVar()
1553            alphaVar.set(self.alpha)
1554            self.alpha_str  = Entry(master,
1555                                     textvariable = alphaVar,
1556                                     width = 16, name ="entry") 
1557        else: 
1558            self.alpha_str = Entry(master, width = 16, name ="entry")
1559       
1560        self.alpha_str.grid(row=2, column=3, sticky=W)
1561
1562        #boundary type buttons
1563        self.raw_boundary = IntVar()
1564        self.remove_holes = IntVar()
1565        self.smooth_indents = IntVar()
1566        self.expand_pinch = IntVar()
1567        self.ck3 = Checkbutton(master, state=NORMAL,
1568                               variable=self.raw_boundary) 
1569        self.ck3.grid(row=3, column=0)
1570        Label(master, text='Raw boundary').grid(row=3, column=1, sticky=W)
1571        #
1572        self.ck4 = Checkbutton(master, state=NORMAL,
1573                               variable=self.remove_holes) 
1574        self.ck4.grid(row=4, column=0)
1575        Label(master, text='Remove small holes').grid(row=4,column=1, sticky=W)
1576        #
1577        self.ck5 = Checkbutton(master,state=NORMAL,
1578                               variable=self.smooth_indents) 
1579        self.ck5.grid(row=5, column=0)
1580        Label(master,
1581              text='Remove sharp indents').grid(row=5, column=1, sticky=W)
1582        #
1583        self.ck6 = Checkbutton(master,state=NORMAL,
1584                               variable=self.expand_pinch) 
1585        self.ck6.grid(row=6, column=0)
1586        Label(master,
1587              text='Remove pinch off').grid(row=6, column=1,  sticky=W)
1588
1589       
1590        self.alpha  = 0
1591        self.alphaValueOk = False
1592       
1593
1594    def apply(self):
1595        """
1596        check entered values
1597        """
1598        try:
1599            self.alpha = float(self.alpha_str.get())
1600            self.alphaValueOk = True
1601           
1602        except ValueError:
1603            pass
1604            #showerror('Bad Alpha value',
1605            #                       'Alpha is negative.')
1606
1607
1608class  AutoSegmentFilterDialog(Dialog):
1609    """
1610    Dialog box for adding segments
1611    """
1612    def __init__(self, parent):
1613        Dialog.__init__(self, parent)
1614       
1615    def body(self, master):
1616        """
1617        GUI description
1618        """
1619        self.title("Automatically Add Segments")
1620
1621        self.use_optimum = IntVar()
1622        self.use_optimum.set(AUTO) # should initialise the radio buttons.
1623                                   #  It doesn't
1624        self.boundary_type = IntVar()
1625                     
1626        #boundary type buttons
1627        self.raw_boundary = IntVar()
1628        self.remove_holes = IntVar()
1629        self.smooth_indents = IntVar()
1630        self.expand_pinch = IntVar()
1631       
1632        self.ck3 = Checkbutton(master, state=NORMAL,
1633                               variable=self.raw_boundary) 
1634        self.ck3.grid(row=3, column=0)
1635        Label(master, text='Raw boundary').grid(row=3, column=1, sticky=W)
1636        #
1637        self.ck4 = Checkbutton(master, state=NORMAL,
1638                               variable=self.remove_holes) 
1639        self.ck4.grid(row=4, column=0)
1640        Label(master, text='Remove small holes').grid(row=4,column=1, sticky=W)
1641        #
1642        self.ck5 = Checkbutton(master,state=NORMAL,
1643                               variable=self.smooth_indents) 
1644        self.ck5.grid(row=5, column=0)
1645        Label(master,
1646              text='Remove sharp indents').grid(row=5, column=1, sticky=W)
1647        #
1648        self.ck6 = Checkbutton(master,state=NORMAL,
1649                               variable=self.expand_pinch) 
1650        self.ck6.grid(row=6, column=0)
1651        Label(master,
1652              text='Remove pinch off').grid(row=6, column=1,  sticky=W)
1653       
1654       
1655
1656
1657class  MeshGenDialog(Dialog):
1658    """
1659    Dialog box for generating a mesh
1660    """
1661    # initial values, hard coded.
1662    # should be values associated with the current mesh
1663    lastMinAngle = 20
1664    lastMaxArea  = 100
1665
1666
1667    def __init__(self,
1668                 parent,
1669                 minAngle,
1670                 maxArea,
1671                 numTriangles,
1672                 MeshMaxAreaLast):
1673        self.minAngle = minAngle
1674        self.maxArea = maxArea
1675        self.numTriangles = numTriangles
1676        self.MeshMaxAreaLast = MeshMaxAreaLast
1677
1678        Dialog.__init__(self, parent)
1679
1680       
1681    def body(self, master):
1682        """
1683        GUI description
1684        """
1685        self.title("Generate Mesh")
1686       
1687        Label(master,
1688              text='Minimum Angle(0 - 40):').grid(row=0, sticky=W)
1689        Label(master,
1690              text='Angles>33 may not converge').grid(row=1, sticky=W)
1691        Label(master, text='Maximum Area:').grid(row=2, sticky=W)
1692        Label(master, text='OR # of triangles:').grid(row=3, sticky=W)
1693
1694
1695        minAngleVar = StringVar()
1696        minAngleVar.set(self.minAngle)
1697        self.minAnglestr   = Entry(master,
1698                                   width = 16,
1699                                   textvariable = minAngleVar,
1700                                   takefocus=1)
1701        if (self.MeshMaxAreaLast):
1702            maxAreaVar = StringVar()
1703            maxAreaVar.set(self.maxArea)
1704            self.maxAreastr  = Entry(master,
1705                                     textvariable = maxAreaVar,
1706                                     width = 16)   
1707            self.numTrianglesstr  = Entry(master,
1708                                          width = 16) 
1709        else: 
1710            self.maxAreastr  = Entry(master,
1711                                     width = 16)
1712            self.maxAreastr.focus_force()
1713            numTrianglesVar = StringVar()
1714            numTrianglesVar.set(self.numTriangles)   
1715            self.numTrianglesstr  = Entry(master,
1716                                          textvariable = numTrianglesVar,
1717                                          width = 16) 
1718
1719
1720        self.minAnglestr.grid(row=0, column=1, sticky=W)
1721        self.maxAreastr.grid(row=2, column=1, sticky=W)
1722        self.numTrianglesstr.grid(row=3, column=1, sticky=W)
1723
1724        self.numTriangles = 0
1725        self.minAngle  = 0
1726        self.maxArea  = 0
1727        self.ValuesOk = False
1728
1729
1730    def apply(self):
1731        """
1732        check entered values
1733        """
1734        self.goodMaxArea = self.goodNumTriangles = True
1735        self.ValuesOk = True
1736        try:
1737            self.minAngle = float(self.minAnglestr.get())
1738            MeshGenDialog.lastMinAngle =self.minAngle
1739        except ValueError:
1740            self.ValuesOk = False
1741            showerror('Bad mesh generation values',
1742                                   ' Values are not numbers.')
1743       
1744        try:   
1745            self.maxArea = float(self.maxAreastr.get())
1746            MeshGenDialog.lastMaxArea =self.maxArea
1747        except ValueError:
1748            self.goodMaxArea = False
1749         
1750        try:   
1751            self.numTriangles = int(self.numTrianglesstr.get())
1752            MeshGenDialog.lastNumTriangles =self.numTriangles
1753        except ValueError:
1754            self.goodNumTriangles= False
1755
1756        if self.goodMaxArea == False and self.goodNumTriangles == False:
1757            self.ValuesOk = False
1758            showerror('Bad mesh generation values',
1759                      'Values are not numbers.')
1760
1761        if self.goodMaxArea == True and self.goodNumTriangles == True:
1762            self.ValuesOk = False
1763            showerror('Bad mesh generation values',
1764                      'Give a maximum area OR number of triangles, not both.')
1765
1766        try: 
1767            # value checking
1768            if self.minAngle <0.0 or self.minAngle >40.0:
1769                raise IOError
1770            if self.goodMaxArea == True and self.maxArea <0.0:
1771                raise IOError
1772            if self.goodNumTriangles == True and self.numTriangles <=0:
1773                raise IOError
1774           
1775        except IOError:
1776            self.ValuesOk = False
1777            showerror('Bad mesh generation values',
1778                                   'Values are out of range.')
1779class  GeneralThresholdDialog(Dialog):
1780    """
1781    Dialog box for thresholding a set by entering minimum
1782    and maximum values
1783    """
1784    def __init__(self,
1785                 parent,
1786                 attribute_titles,
1787                 function_description):
1788        self.attribute_titles=attribute_titles
1789        self.function_description=function_description
1790
1791        Dialog.__init__(self, parent)
1792
1793
1794    def body(self, master):
1795        """
1796        GUI description
1797        """
1798        self.title("Threshold selected set")
1799        blurb1 = 'Threshold selected set between minimum'
1800        blurb2 = 'and maximum ' + self.function_description
1801
1802        Label(master,text=blurb1).grid(row=0, sticky=W)
1803        Label(master,text=blurb2).grid(row=1, sticky=W)
1804
1805        Label(master, text='minimum attribute:').grid(row=2, sticky=W)
1806        Label(master, text='maximum attribute:').grid(row=3, sticky=W)
1807        Label(master, text='attribute name').grid(row=4, sticky=W)
1808
1809
1810        nameVar = StringVar()
1811        nameVar.set('elevation')
1812
1813        self.minstr = Entry(master, width = 16, name ="entry")
1814        self.maxstr = Entry(master, width = 16)
1815        self.attstr = Entry(master, width = 16,textvariable = nameVar)
1816       
1817        self.minstr.grid(row=2, column=1, sticky=W)
1818        self.maxstr.grid(row=3, column=1, sticky=W)
1819        self.attstr.grid(row=4, column=1, sticky=W)
1820        self.minstr.focus_force()
1821        self.min  = 0
1822        self.max  = 0
1823        self.attribute_name = 'elevation'
1824        self.minmaxValuesOk = False
1825       
1826    def apply(self):
1827        self.minmaxValuesOk = True
1828        try:
1829            self.min = float(self.minstr.get())
1830            self.max = float(self.maxstr.get())
1831        except ValueError:
1832            self.minmaxValuesOk = False
1833            showerror('Bad mesh generation values',
1834                                   ' Values are not numbers.')
1835        try:
1836            self.attribute_titles.index(self.attstr.get())#dodgey.
1837            self.attribute_name = self.attstr.get()
1838        except ValueError:
1839            self.attribute_name = None
1840            showerror('Bad attribute name',
1841                                   'Using h = 1')
1842
1843class  ThresholdDialog(Dialog):
1844    """
1845    Dialog box for thresholding a set by entering minimum
1846    and maximum values
1847    """
1848    def __init__(self,
1849                 parent,
1850                 attribute_titles):
1851        self.attribute_titles=attribute_titles
1852        Dialog.__init__(self, parent)
1853
1854
1855    def body(self, master):
1856        """
1857        GUI description
1858        """
1859        self.title("Threshold selected set")
1860       
1861        Label(master, text='minimum attribute:').grid(row=0, sticky=W)
1862        Label(master, text='maximum attribute:').grid(row=1, sticky=W)
1863        Label(master, text='attribute name').grid(row=2, sticky=W)
1864
1865
1866        nameVar = StringVar()
1867        nameVar.set('elevation')
1868
1869        self.minstr   = Entry(master, width = 16, name ="entry")
1870        self.maxstr   = Entry(master, width = 16)
1871        self.attstr   = Entry(master, width = 16,textvariable = nameVar)
1872       
1873        self.minstr.grid(row=0, column=1, sticky=W)
1874        self.maxstr.grid(row=1, column=1, sticky=W)
1875        self.attstr.grid(row=2, column=1, sticky=W)
1876        self.minstr.focus_force()
1877        self.min  = 0
1878        self.max  = 0
1879        self.attribute_name = 'elevation'
1880        self.minmaxValuesOk = False
1881       
1882    def apply(self):
1883        self.minmaxValuesOk = True
1884        try:
1885            self.min = float(self.minstr.get())
1886            self.max = float(self.maxstr.get())
1887        except ValueError:
1888            self.minmaxValuesOk = False
1889            showerror('Bad mesh generation values',
1890                                   ' Values are not numbers.')
1891        try:
1892            self.attribute_titles.index(self.attstr.get())#dodgey.
1893            self.attribute_name = self.attstr.get()
1894        except ValueError:
1895            self.minmaxValuesOk = False
1896            showerror('Bad attribute name',
1897                                   ' Attribute not in mesh.')
1898
1899
1900class  Courant_ThresholdDialog(Dialog):
1901    """
1902    Dialog box for thresholding a set by entering minimum
1903    and maximum values
1904    """
1905    def __init__(self,
1906                 parent,
1907                 attribute_titles):
1908        self.attribute_titles=attribute_titles
1909        Dialog.__init__(self, parent)
1910
1911
1912    def body(self, master):
1913        """
1914        GUI description
1915        """
1916        self.title("Courant_Threshold selected set")
1917       
1918        Label(master, text='minimum attribute:').grid(row=0, sticky=W)
1919        Label(master, text='maximum attribute:').grid(row=1, sticky=W)
1920        Label(master, text='attribute name').grid(row=2, sticky=W)
1921
1922
1923        nameVar = StringVar()
1924        nameVar.set('elevation')
1925
1926        self.minstr   = Entry(master, width = 16, name ="entry")
1927        self.maxstr   = Entry(master, width = 16)
1928        self.attstr   = Entry(master, width = 16,textvariable = nameVar)
1929       
1930        self.minstr.grid(row=0, column=1, sticky=W)
1931        self.maxstr.grid(row=1, column=1, sticky=W)
1932        self.attstr.grid(row=2, column=1, sticky=W)
1933        self.minstr.focus_force()
1934        self.min  = 0
1935        self.max  = 0
1936        self.attribute_name = 'elevation'
1937        self.minmaxValuesOk = False
1938       
1939    def apply(self):
1940        self.minmaxValuesOk = True
1941        try:
1942            self.min = float(self.minstr.get())
1943            self.max = float(self.maxstr.get())
1944        except ValueError:
1945            self.minmaxValuesOk = False
1946            showerror('Bad mesh generation values',
1947                                   ' Values are not numbers.')
1948        try:
1949            self.attribute_titles.index(self.attstr.get())#dodgey.
1950            self.attribute_name = self.attstr.get()
1951        except ValueError:
1952            self.minmaxValuesOk = False
1953            showerror('Bad attribute name',
1954                                   ' Attribute not in mesh.')
1955
1956
1957class SmoothDialog(Dialog):
1958    """
1959    Dialog box for setting the number of triangles
1960    used to make up dilation or erosion
1961    """
1962    def body(self, master):
1963        """
1964        GUI description
1965        """
1966        self.title("Enter the minimum radius to remove")
1967       
1968        Label(master, text='radius:').grid(row=0, sticky=W)
1969
1970        self.min_radius = Entry(master, width = 16, name ="entry")
1971       
1972        self.min_radius.grid(row=0, column=1, sticky=W)
1973        self.min_radius.focus_force()
1974        self.min = 2.
1975        self.valueOK = False
1976       
1977    def apply(self):
1978        self.valueOK = True
1979        try:
1980            self.min = float(self.min_radius.get())
1981            self.min_radius = self.min
1982        except ValueError:
1983            self.valueOK = False
1984            showerror('Bad Number',
1985                                   ' Value not a number')
1986
1987class  setStructureNumberDialog(Dialog):
1988    """
1989    Dialog box for setting the number of triangles
1990    used to make up dilation or erosion
1991    """
1992    def body(self, master):
1993        """
1994        GUI description
1995        """
1996        self.title("Set number of elements effected by morphing sets")
1997       
1998        Label(master, text='number:').grid(row=0, sticky=W)
1999
2000        self.number = Entry(master, width = 16, name ="entry")
2001       
2002        self.number.grid(row=0, column=1, sticky=W)
2003        self.number.focus_force()
2004        self.number = 0
2005        self.numberOk = False
2006       
2007    def apply(self):
2008        self.numberOk = True
2009        try:
2010            self.number = int(self.number.get())
2011        except ValueError:
2012            self.numberOK = False
2013            showerror('Bad mesh generation values',
2014                                   ' Values are not numbers.')
2015
2016
2017     
2018if __name__ == '__main__':
2019    draw = Draw()
2020    draw.run()
2021    #profile.run('draw.run()', 'pmeshprof')   
2022
Note: See TracBrowser for help on using the repository browser.