source: trunk/anuga_core/anuga/visualiser/visualiser.py @ 9679

Last change on this file since 9679 was 8649, checked in by steve, 12 years ago

Testing visualising height quantity

File size: 11.7 KB
Line 
1from threading import Thread
2from Queue import Queue
3from Tkinter import Tk, Button, Frame, N, E, S, W
4from types import FunctionType, TupleType
5from vtk import vtkActor, vtkCubeAxesActor2D, vtkDelaunay2D, vtkFloatArray, vtkPoints, vtkPolyData, vtkPolyDataMapper, vtkRenderer
6from vtk.tk.vtkTkRenderWidget import vtkTkRenderWidget
7
8class Visualiser(Thread):
9    """Superclass of both the realtime and offline VTK visualisers
10    """
11    def __init__(self, source):
12        Thread.__init__(self)
13
14        self.source = source
15
16        # Structures for Height Based quantities
17        self.height_quantities = []
18        self.height_zScales = {}
19        self.height_dynamic = {}
20        self.height_offset = {}
21        self.height_opacity = {}
22        self.height_wireframe = {}
23
24        # Structures for colouring quantities
25        self.colours_height = {}
26
27        # Structures used for VTK
28        self.vtk_actors = {}
29        self.vtk_axesSet = False
30        self.vtk_drawAxes = False
31        self.vtk_mappers = {}
32        self.vtk_polyData = {}
33
34        # A list of operations to be performed on the cube axes. Type: [(func, (args))]
35        self.conf_axesAlterations = []
36        # A list of all polygons to overlay. Type: [([coords], height, (colour)]
37        self.conf_overlaidPolygons = []
38        # A list of alterations to be performed on the Tk root. Type: [(func, (args))]
39        self.conf_tkAlterations = []
40
41    def run(self):
42        self.vtk_renderer = vtkRenderer()
43        self.setup_gui()
44        self.setup_grid()
45
46        # Handle any deferred configuration
47        # Overlaid polygons
48        for args in self.conf_overlaidPolygons:
49            self.overlay_polygon_internal(*args)
50        # Draw (and maybe alter) the axes
51        if self.vtk_drawAxes:
52            self.vtk_axes = vtkCubeAxesActor2D()
53            # Perform all of the alterations required, by applying func to the vtk_axes instance (with the given args).
54            for func, args in self.conf_axesAlterations:
55                func(*((self.vtk_axes,) + args))
56        # Alter the Tk root as necessary.
57        for func, args in self.conf_tkAlterations:
58            func(*((self.tk_root,) + args))
59        # Finished with deferred configuration.
60
61        # Draw Height Quantities
62        for q in self.height_quantities:
63            self.update_height_quantity(q, self.height_dynamic[q])
64            self.draw_height_quantity(q)
65           
66        self.tk_root.mainloop()
67
68    def redraw_quantities(self):
69        """Redraw all dynamic quantities.
70        """
71        # Height quantities
72        for q in self.height_quantities:
73            if (self.height_dynamic[q]):
74                self.update_height_quantity(q, self.height_dynamic[q])
75                self.draw_height_quantity(q)
76        if self.vtk_drawAxes is True:
77            self.draw_axes()
78
79    # --- Axes --- #
80       
81    def render_axes(self):
82        """Intstruct the visualiser to render cube axes around the render.
83        """
84        self.vtk_drawAxes = True
85
86    def draw_axes(self):
87        """Update the 3D bounds on the axes and add them to the pipeline if not yet connected.
88        """
89        self.vtk_axes.SetBounds(self.get_3d_bounds())
90        if not self.vtk_axesSet:
91            self.vtk_axesSet = True
92            self.vtk_axes.SetCamera(self.vtk_renderer.GetActiveCamera())
93            self.vtk_renderer.AddActor(self.vtk_axes)
94            self.vtk_renderer.ResetCamera(self.get_3d_bounds())
95       
96    def alter_axes(self, func, args):
97        """Attempt to apply the function 'func' with args tuple 'args' to the
98        vtkCubeAxesActor2D instance set up by render_axes. This is done this way to ensure
99        the axes setup is handled in the visualiser thread.
100
101        Example call:
102        from vtk import vtkCubeAxesActor2D
103        alter_axes(vtkCubeAxesActor2D.SetNumberOfPoints, (5,))
104        """
105        self.conf_axesAlterations.append((func, args))
106           
107    # --- Height Based Rendering --- #
108
109    def setup_grid(self):
110        """Create the vtkCellArray instance that represents the
111        triangles. Subclasses are expected to override this function
112        to read from their source as appropriate. The vtkCellArray should
113        be stored to self.vtk_cells.
114        """
115        pass
116
117    def render_quantity_height(self, quantityName, zScale=1.0, offset=0.0, opacity=1.0, dynamic=True, wireframe=False):
118        """Instruct the visualiser to render a quantity using the
119        value at a point as its height.  The value at each point is
120        multiplied by z_scale and is added to offset, and if
121        dynamic=False, the quantity is not recalculated on each
122        update.
123        """
124        self.height_quantities.append(quantityName)
125        self.height_zScales[quantityName] = zScale
126        self.height_offset[quantityName] = offset
127        self.height_dynamic[quantityName] = dynamic
128        self.height_opacity[quantityName] = opacity
129        self.height_wireframe[quantityName] = wireframe
130
131    def update_height_quantity(self, quantityName, dynamic=True):
132        """Create a vtkPolyData object and store it in
133        self.vtk_polyData[quantityName]. Subclasses are expected to override this
134        function.
135        """
136        pass
137
138    def get_3d_bounds(self):
139        """Get the minimum and maximum bounds for the x, y and z directions.
140        Return as a list of double in the order (xmin, xmax, ymin, ymax, zmin, zmax),
141        suitable for passing to vtkCubeAxesActor2D::SetRanges(). Subclasses are expected
142        to override this function.
143        """
144        pass
145
146
147
148    def store_height_quantity(self, quantityName, fileName=None):
149
150        if fileName == None:
151            fileName = quantityName + '.vtk'
152
153        quantity_polyData = self.vtk_polyData[quantityName]
154
155        import vtk
156        w = vtk.vtkPolyDataWriter()
157        #print quantity_polyData
158        w.SetInput(quantity_polyData)
159        w.SetFileName(fileName)
160        w.Write()
161
162
163
164    def draw_height_quantity(self, quantityName):
165        """Use the vtkPolyData and prepare/update the rest of the VTK
166        rendering pipeline.
167        """
168        if self.vtk_mappers.has_key(quantityName):
169            mapper = self.vtk_mappers[quantityName]
170        else:
171            mapper = self.vtk_mappers[quantityName] = vtkPolyDataMapper()
172        mapper.SetInput(self.vtk_polyData[quantityName])
173        mapper.Update()
174
175        if not self.vtk_actors.has_key(quantityName):
176            actor = self.vtk_actors[quantityName] = vtkActor()
177            actor.GetProperty().SetOpacity(self.height_opacity[quantityName])
178            if self.height_wireframe[quantityName]:
179                actor.GetProperty().SetRepresentationToWireframe()
180            actor.SetMapper(mapper)
181            self.vtk_renderer.AddActor(actor)
182        else:
183            actor = self.vtk_actors[quantityName]
184
185        if self.colours_height.has_key(quantityName):
186            colour = self.colours_height[quantityName]
187            if type(colour) == TupleType:
188                if type(colour[0]) == FunctionType:
189                    # It's a function, so take colour[1] as the
190                    # lower bound on the scalar range and
191                    # colour[2] as the upper bound on the scalar
192                    # range.
193                    scalars = vtkFloatArray()
194
195                    map(scalars.InsertNextValue, colour[0](self.build_quantity_dict()))
196                    self.vtk_polyData[quantityName].GetPointData().SetScalars(scalars)
197                    mapper.SetScalarRange(colour[1:])
198                    mapper.Update()
199                else:
200                    # It's a 3-tuple representing an RGB value.
201                    actor.GetProperty().SetColor(colour)
202            else:
203                actor.GetProperty().SetColor(0.5, 0.5, 0.5)
204        else:
205            actor.GetProperty().SetColor(0.5, 0.5, 0.5)
206
207    # --- Colour Coding --- #
208
209    def build_quantity_dict(self):
210        """Build and return a dictionary mapping quantity name->Numeric array of vertex
211        values for that quantity. Subclasses are expected to override
212        this function."""
213        pass
214
215    def colour_height_quantity(self, quantityName, colour=(0.5, 0.5, 0.5)):
216        """Add colouring to a height based quantity.
217
218        The colour parameter can be one of the following:
219        - a 3-tuple of values in [0,1] to specify R, G, B values
220        - a 3-tuple of values:
221          - a function that takes a dictionary mapping quantity name->Numeric array of vertex values.
222            This function returns a list of vertex values to be used in the colour coding.
223          - a float for the lower bound on the colouring
224          - a float for the upper bound on the colouring
225        """
226        self.colours_height[quantityName] = colour
227
228    # --- Overlaid Polygons --- #
229
230    def overlay_polygon(self, coords, height=0.0, colour=(1.0, 0.0, 0.0)):
231        """Add a polygon to the output of the visualiser.
232
233        coords is a list of 2-tuples representing x and y coordinates.
234        These are triangulated by vtkDelaunay2D.
235
236        height is the z-value given to all points.
237
238        colour is the colour of the polygon, as a 3-tuple representing
239        r, g, b values between 0 and 1."""
240        self.conf_overlaidPolygons.append((coords, height, colour))
241
242    def overlay_polygon_internal(self, coords, height, colour):
243        """Add a polygon to the output of the visualiser.
244
245        coords is a list of 2-tuples representing x and y coordinates.
246        These are triangulated by vtkDelaunay2D.
247
248        height is the z-value given to all points.
249
250        colour is the colour of the polygon, as a 3-tuple representing
251        r, g, b values between 0 and 1.
252
253        This function should not be called from outside the visualiser thread.
254        Use overlay_polygon instead.
255   
256        """
257        points = vtkPoints()
258        for coord in coords:
259            points.InsertNextPoint(coord[0], coord[1], height)
260        profile = vtkPolyData()
261        profile.SetPoints(points)
262        delny = vtkDelaunay2D()
263        delny.SetInput(profile)
264        mesh = vtkPolyDataMapper()
265        mesh.SetInput(delny.GetOutput())
266        actor = vtkActor()
267        actor.SetMapper(mesh)
268        actor.GetProperty().SetColor(colour)
269        self.vtk_renderer.AddActor(actor)
270       
271    # --- Vector Fields --- #
272
273    # --- GUI Setup --- #
274
275    def setup_gui(self):
276        self.tk_root = Tk()
277        self.tk_root.title("Visualisation")
278        self.tk_root.after(100, self.redraw)
279        self.tk_root.bind("<Destroy>", self.destroyed)
280        self.tk_root.grid_rowconfigure(0, weight=1)
281        self.tk_root.grid_columnconfigure(0, weight=1)
282
283        self.tk_renderWidget = vtkTkRenderWidget(self.tk_root, width=400, height=400)
284        self.tk_renderWidget.grid(row=0, column=0, sticky=N+S+E+W)
285        self.tk_controlFrame = Frame(self.tk_root)
286        self.tk_controlFrame.grid(row=1, column=0, sticky=E+W)
287        self.tk_controlFrame.grid_rowconfigure(0, weight=1)
288        self.tk_controlFrame.grid_columnconfigure(0, weight=1)
289       
290        self.tk_quit = Button(self.tk_controlFrame, text="Quit", command=self.shutdown)
291        self.tk_quit.grid(row=0, column=0, sticky=E+W)
292        self.tk_renderWidget.GetRenderWindow().AddRenderer(self.vtk_renderer)
293
294    def alter_tkroot(self, func, args):
295        """Apply func, with arguments tuple args to the root tk window for this visualiser.
296        """
297        self.conf_tkAlterations.append((func, args))
298
299    # --- GUI Events --- #
300
301    def destroyed(self, event):
302        if event.widget == self.tk_root:
303            self.shutdown()
304
305    def redraw(self):
306        self.tk_renderWidget.GetRenderWindow().Render()
307        self.tk_root.update_idletasks()
308        self.tk_root.after(100, self.redraw)
309
310    def shutdown(self):
311        self.tk_root.withdraw()
312        self.tk_root.destroy()
Note: See TracBrowser for help on using the repository browser.