source: anuga_core/source/anuga/visualiser/offline.py @ 4163

Last change on this file since 4163 was 3873, checked in by jack, 18 years ago

Fixed the visualiser to actually run properly in its own thread.

Added a simple example to use the realtime visualiser.

File size: 7.8 KB
RevLine 
[3537]1from Numeric import array, Float, ravel, zeros
[3493]2from Scientific.IO.NetCDF import NetCDFFile
[3873]3from Tkinter import Button, E, Tk, W
[3493]4from visualiser import Visualiser
5from vtk import vtkCellArray, vtkPoints, vtkPolyData
6
7class OfflineVisualiser(Visualiser):
8    """A VTK-powered offline visualiser which runs in its own thread.
[3538]9    In addition to the functions provided by the standard visualiser,
10    the following additional functions are provided:
11
12    precache_height_quantities() - Precache all the vtkpoints
13    structures for any dynamic height based quantities to render.
[3493]14    """
[3873]15    def __init__(self, source, frameDelay=100):
[3493]16        """The source parameter is assumed to be a NetCDF sww file.
[3873]17        The frameDelay parameter is the number of milliseconds waited between frames.
[3493]18        """
19        Visualiser.__init__(self, source)
20
[3536]21        self.frameNumber = 0
[3493]22        fin = NetCDFFile(self.source, 'r')
[3536]23        self.maxFrameNumber = fin.variables['time'].shape[0] - 1
[3493]24        fin.close()
25
[3873]26        self.frameDelay = frameDelay
27
[3670]28        self.xmin = None
29        self.xmax = None
30        self.ymin = None
31        self.ymax = None
32        self.zmin = None
33        self.zmax = None
34
[3536]35        self.vtk_heightQuantityCache = []
[3537]36        for i in range(self.maxFrameNumber + 1): # maxFrameNumber is zero indexed.
[3536]37            self.vtk_heightQuantityCache.append({})
38
[3548]39        self.paused = False
[3611]40       
[3493]41    def setup_grid(self):
42        fin = NetCDFFile(self.source, 'r')
43        self.vtk_cells = vtkCellArray()
44        N_tri = fin.variables['volumes'].shape[0]
45        for v in range(N_tri):
46            self.vtk_cells.InsertNextCell(3)
47            for i in range(3):
48                self.vtk_cells.InsertCellPoint(fin.variables['volumes'][v][i])
49        fin.close()
[3611]50       
[3493]51    def update_height_quantity(self, quantityName, dynamic=True):
[3536]52        polydata = self.vtk_polyData[quantityName] = vtkPolyData()
53        if dynamic is True:
54            if not self.vtk_heightQuantityCache[self.frameNumber].has_key(quantityName):
55                self.vtk_heightQuantityCache[self.frameNumber][quantityName]\
56                    = self.read_height_quantity(quantityName, True, self.frameNumber);
57            polydata.SetPoints(self.vtk_heightQuantityCache[self.frameNumber][quantityName])
58        else:
59            polydata.SetPoints(self.read_height_quantity(quantityName, False))
60        polydata.SetPolys(self.vtk_cells)
[3670]61
62    def get_3d_bounds(self):
63        return [self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax]
[3536]64           
65    def read_height_quantity(self, quantityName, dynamic=True, frameNumber=0):
66        """Read in a height based quantity from the NetCDF source file
67        and return a vtkPoints object. frameNumber is ignored if
68        dynamic is false."""
[3493]69        fin = NetCDFFile(self.source, 'r')
[3536]70        points = vtkPoints()
71        if dynamic is True:
72            N_vert = fin.variables[quantityName].shape[1]
73        else:
74            N_vert = len(fin.variables[quantityName])
[3537]75        x = ravel(array(fin.variables['x'], Float))
76        y = ravel(array(fin.variables['y'], Float))
[3536]77        if dynamic is True:
[3538]78            q = array(fin.variables[quantityName][frameNumber], Float)
[3536]79        else:
[3537]80            q = ravel(array(fin.variables[quantityName], Float))
[3524]81
[3536]82        q *= self.height_zScales[quantityName]
83        q += self.height_offset[quantityName]
[3524]84
[3536]85        for v in range(N_vert):
86            points.InsertNextPoint(x[v], y[v], q[v])
[3670]87            if self.xmin == None or self.xmin > x[v]:
88                self.xmin = x[v]
89            if self.xmax == None or self.xmax < x[v]:
90                self.xmax = x[v]
91            if self.ymin == None or self.ymin > y[v]:
92                self.ymin = y[v]
93            if self.ymax == None or self.ymax < y[v]:
94                self.ymax = y[v]
95            if self.zmin == None or self.zmin > q[v]:
96                self.zmin = q[v]
97            if self.zmax == None or self.zmax < q[v]:
98                self.zmax = q[v]
[3493]99        fin.close()
[3536]100        return points
[3493]101
[3538]102    def precache_height_quantities(self):
103        """Precache any height-based quantities. Call before rendering
104        beigns."""
105        for q in self.height_quantities:
106            if self.height_dynamic[q] is True:
107                print 'Precaching %s' % q
108                for i in range(self.maxFrameNumber + 1): # maxFrameNumber is zero-indexed
109                    print ' - Frame %d of %d' % (i, self.maxFrameNumber)
110                    self.vtk_heightQuantityCache[i][q]\
111                        = self.read_height_quantity(q, True, i)
112
113    def build_quantity_dict(self):
114        quantities = {}
115        fin = NetCDFFile(self.source, 'r')
116        for q in filter(lambda n:n != 'x' and n != 'y' and n != 'z' and n != 'time' and n != 'volumes', fin.variables.keys()):
117            if len(fin.variables[q].shape) == 1: # Not a time-varying quantity
118                quantities[q] = ravel(array(fin.variables[q], Float))
119            else: # Time-varying, get the current timestep data
120                quantities[q] = array(fin.variables[q][self.frameNumber], Float)
121        fin.close()
122        return quantities
123
[3493]124    def setup_gui(self):
125        Visualiser.setup_gui(self)
[3549]126        self.tk_quit.grid(row=0, column=0, columnspan=6, sticky=W+E)
127        self.tk_restart = Button(self.tk_controlFrame, text="<<<", command=self.restart)
[3493]128        self.tk_restart.grid(row=1, column=0, sticky=W+E)
[3549]129        self.tk_back10 = Button(self.tk_controlFrame, text="<<", command=self.back10)
[3493]130        self.tk_back10.grid(row=1, column=1, sticky=W+E)
[3549]131        self.tk_back = Button(self.tk_controlFrame, text="<", command=self.back)
[3493]132        self.tk_back.grid(row=1, column=2, sticky=W+E)
[3622]133        self.tk_pauseResume = Button(self.tk_controlFrame, text="Pause", command=self.pauseResume, width=15)
[3493]134        self.tk_pauseResume.grid(row=1, column=3, sticky=W+E)
[3549]135        self.tk_forward = Button(self.tk_controlFrame, text=">", command=self.forward)
[3493]136        self.tk_forward.grid(row=1, column=4, sticky=W+E)
[3549]137        self.tk_forward10 = Button(self.tk_controlFrame, text=">>", command=self.forward10)
[3493]138        self.tk_forward10.grid(row=1, column=5, sticky=W+E)
139
[3549]140        # Make the buttons stretch to fill all available space
141        for i in range(6):
142            self.tk_controlFrame.grid_columnconfigure(i, weight=1)
143
[3611]144    def run(self):
[3873]145        self.alter_tkroot(Tk.after, (self.frameDelay, self.animateForward))
[3611]146        Visualiser.run(self)
147
[3493]148    def restart(self):
[3536]149        self.frameNumber = 0
[3873]150        self.redraw_quantities()
[3622]151        self.pause()
[3493]152
153    def back10(self):
[3536]154        if self.frameNumber - 10 >= 0:
155            self.frameNumber -= 10
[3493]156        else:
[3536]157            self.frameNumber = 0
[3873]158        self.redraw_quantities()
[3622]159        self.pause()
[3493]160
161    def back(self):
[3536]162        if self.frameNumber > 0:
163            self.frameNumber -= 1
[3873]164            self.redraw_quantities()
[3622]165            self.pause()
[3493]166
167    def pauseResume(self):
[3548]168        if self.paused is True:
[3622]169            self.resume()
[3548]170        else:
[3622]171            self.pause()
[3493]172
[3622]173    def pause(self):
174        self.paused = True
175        self.tk_pauseResume.config(text="Resume")
176
177    def resume(self):
178        self.paused = False
179        self.tk_pauseResume.config(text="Pause")
[3873]180        self.tk_root.after(self.frameDelay, self.animateForward)
[3622]181
[3493]182    def forward(self):
[3622]183        self.forward_step()
184        self.pause()
185
186    def forward_step(self):
[3536]187        if self.frameNumber < self.maxFrameNumber:
188            self.frameNumber += 1
[3873]189            self.redraw_quantities()
[3622]190        else:
191            self.pause()
[3493]192
193    def forward10(self):
[3536]194        if self.frameNumber + 10 <= self.maxFrameNumber:
195            self.frameNumber += 10
[3493]196        else:
[3536]197            self.frameNumber = self.maxFrameNumber
[3873]198        self.redraw_quantities()
[3622]199        self.pause()
[3548]200
201    def animateForward(self):
202        if self.paused is not True:
[3622]203            self.forward_step()
[3873]204            self.tk_root.after(self.frameDelay, self.animateForward)
Note: See TracBrowser for help on using the repository browser.