source: inundation/pyvolution/vtk_realtime_visualiser.py @ 2202

Last change on this file since 2202 was 2196, checked in by jack, 19 years ago

Removed redundant dictionary
Added large and relevant class docstring.

File size: 7.3 KB
Line 
1# VTK-Based visualiser
2# intended to replace realtime_visualisation_new.py
3
4import threading
5from Queue import Queue, Empty
6import Tkinter
7import vtk
8from vtk.tk.vtkTkRenderWidget import vtkTkRenderWidget
9
10class Visualiser(threading.Thread):
11
12    """A VTK-powered visualiser, designed to replace the VPython one.
13    Intended to be run in its own thread.
14
15    Customisation options for the visualiser are as follows:
16
17    setup: Dictionary mapping quantity name -> boolean.
18    if setup[q] is true, draw the quantity when the visualiser starts.
19   
20    updating: Dictionary mapping quantity name -> boolean.
21    if updating[q] is true, update the rendering of this quantity each
22    timestep.
23   
24    qcolor: Dictionary mapping quantity name -> (float, float, float)
25    if the name of a quantity is found in qcolor, the colour specified (in
26    (r, g, b) from 0.0 to 1.0) is used for display of that quantity instead of
27    (0.5, 0.5, 0.5) (the default)
28       
29    scale_z: Dictionary mapping quantity name -> float
30    Override the z scaling of this quantity with the float specified.
31
32    default_scale_z: float
33    multiply all z coordinates by this amount unless an overriding value
34    exists in scale_z.
35    """
36
37    def __init__(self, domain, default_scale_z=1.0, rect=None, title='Test'):
38        threading.Thread.__init__(self)
39        # Initialise data structures. setup and updating are maps
40        # quantity name -> boolean, setup means to render it,
41        # update means to update with time.
42        # qcolor maps quantity name -> (float, float, float): the colour (r, g, b)
43        self.setup = {}
44        self.updating = {}
45        self.qcolor = {}
46        self.scale_z = {}
47        self.default_scale_z = default_scale_z
48        self.domain = domain
49        self.vertices = domain.vertex_coordinates
50
51        # Message queue
52        self.messages = Queue()
53
54        # Internal use - storage of vtk objects
55        self.grids = {}
56        self.actors = {}
57        self.polydata = {}
58        self.mappers = {}
59
60        # Default options
61        for x in self.domain.quantities:
62            self.setup[x] = False
63            self.updating[x] = False
64
65        # Bounding box.
66        if rect is None:
67            self.max_x = max(max(self.vertices[:,0]),max(self.vertices[:,2]),max(self.vertices[:,4]))
68            self.min_x = min(min(self.vertices[:,0]),min(self.vertices[:,2]),min(self.vertices[:,4]))
69            self.max_y = max(max(self.vertices[:,1]),max(self.vertices[:,3]),max(self.vertices[:,5]))
70            self.min_y = min(min(self.vertices[:,1]),min(self.vertices[:,3]),min(self.vertices[:,5]))
71        else:
72            self.max_x = rect[2]
73            self.min_x = rect[0]
74            self.max_y = rect[3]
75            self.min_y = rect[1]
76
77        self.range_x = self.max_x - self.min_x
78        self.range_y = self.max_y - self.min_y
79        self.range_xy = max(self.range_x, self.range_y)
80       
81    def run(self):
82        self.initialise_gui()
83        self.add_axes()
84        self.setup_all()
85        self.root.mainloop()
86
87    def initialise_gui(self):
88        """Prepare the GUI for the Visualiser, and set up the
89        renderer.
90       
91        """
92        # Using the TK VTK widget allows extendability
93        # should a gui need adding.
94        self.root = Tkinter.Tk()
95
96        # Message handling with after
97        self.root.after(100, self.handle_messages)
98       
99        self.renderWidget = vtkTkRenderWidget(self.root, width=400, height=400)
100        self.renderWidget.pack(expand='true', fill='both')
101        self.renderWindow = self.renderWidget.GetRenderWindow()
102        self.renderer = vtk.vtkRenderer()
103        self.renderWindow.AddRenderer(self.renderer)
104
105    def add_axes(self):
106        """Add axes to this Visualiser
107        """
108        pass
109
110    def setup_all(self):
111        """Draw in the data that is specified by setup or update
112        """
113
114        self.N_tri = len(self.domain.triangles)
115        self.cells = vtk.vtkCellArray()
116        self.vertices = self.domain.get_vertex_coordinates()
117        self.vert_index = {}
118
119        for n in range(len(self.vertices)):
120            for i in range(3):
121                self.vert_index[self.domain.triangles[n][i]] = self.vertices[n][i*2:i*2+2]
122
123        self.N_vert = len(self.vert_index.keys())
124
125        # Prepare the list of cells
126        for t in self.domain.triangles:
127            self.cells.InsertNextCell(3)
128            for i in range(3):
129                self.cells.InsertCellPoint(t[i])
130
131        # Set up the rendering of each quantity
132        for q in self.domain.quantities:
133            if self.setup[q] | self.updating[q]:
134                self.draw_quantity(q)
135
136    def draw_quantity(self, q):
137        qty_index = {}
138        for n in range(self.N_tri):
139            for v in range(3):
140                qty_index[self.domain.triangles[n][v]] = self.domain.quantities[q].vertex_values[n][v]
141
142        self.grids[q] = vtk.vtkPoints()
143        grid = self.grids[q]
144
145        if self.scale_z.has_key(q):
146            scale = self.scale_z[q]
147        else:
148            scale = self.default_scale_z
149               
150        for v in range(self.N_vert):
151            grid.InsertNextPoint(self.vert_index[v][0],
152                                 self.vert_index[v][1],
153                                 qty_index[v] * scale)
154
155        # Can't recycle vtkPolyData objects: Apparently they behave
156        # unusually if the points (i.e. vertex data) is set after
157        # the polys (i.e. triangle data)
158        self.polydata[q] = vtk.vtkPolyData()
159        polydata = self.polydata[q]
160       
161        polydata.SetPoints(grid)
162        polydata.SetPolys(self.cells)
163
164        if self.mappers.has_key(q):
165            mapper = self.mappers[q]
166            mapper.SetInput(polydata)
167            mapper.Update()
168        else:
169            self.mappers[q] = vtk.vtkPolyDataMapper()
170            mapper = self.mappers[q]
171            mapper.SetInput(polydata)
172
173        if not self.actors.has_key(q):
174            self.actors[q] = vtk.vtkActor()
175            actor = self.actors[q]
176            actor.SetMapper(mapper)
177
178            if self.qcolor.has_key(q):
179                actor.GetProperty().SetColor(self.qcolor[q])
180            else:
181                actor.GetProperty().SetColor(0.5, 0.5, 0.5)
182
183            self.renderer.AddActor(actor)
184
185    def handle_messages(self):
186        # Expandable to include more kinds of messages if need be
187        # This lookup table is copied because it is modified later
188        handlers = {'redraw': self.message_redraw}.copy()
189        try:
190            while True:
191                message = self.messages.get_nowait()
192                try:
193                    handlers[message]()
194                    # Guard against redrawing more than once per
195                    # call to handle_messages.
196                    if message == 'redraw':
197                        del handlers[message]
198                except KeyError:
199                    pass
200        except Empty:
201            pass
202        self.root.after(100, self.handle_messages)
203
204    def message_redraw(self):
205        """Re-render and repaint the rendered image.
206        This function should only be called from the message handler.
207        Use request_redraw to ask for a redraw"""
208
209        for q in self.domain.quantities:
210            if self.updating[q]:
211                self.draw_quantity(q)
212       
213        self.renderWindow.Render()
214        self.root.update_idletasks()
215
216    def request_redraw(self):
217        self.messages.put('redraw')
218
Note: See TracBrowser for help on using the repository browser.