source: anuga_core/source/anuga/visualiser/visualiser.py @ 7244

Last change on this file since 7244 was 6113, checked in by steve, 16 years ago

Added wireframe visualisation

File size: 11.3 KB
RevLine 
[3540]1from threading import Thread
[3873]2from Queue import Queue
[3549]3from Tkinter import Tk, Button, Frame, N, E, S, W
[3538]4from types import FunctionType, TupleType
[3813]5from vtk import vtkActor, vtkCubeAxesActor2D, vtkDelaunay2D, vtkFloatArray, vtkPoints, vtkPolyData, vtkPolyDataMapper, vtkRenderer
[3465]6from vtk.tk.vtkTkRenderWidget import vtkTkRenderWidget
7
8class Visualiser(Thread):
9    """Superclass of both the realtime and offline VTK visualisers
10    """
[3493]11    def __init__(self, source):
[3465]12        Thread.__init__(self)
13
[3493]14        self.source = source
[3465]15
16        # Structures for Height Based quantities
17        self.height_quantities = []
18        self.height_zScales = {}
19        self.height_dynamic = {}
[3493]20        self.height_offset = {}
[4267]21        self.height_opacity = {}
[6113]22        self.height_wireframe = {}
[3465]23
[3538]24        # Structures for colouring quantities
25        self.colours_height = {}
26
[3493]27        # Structures used for VTK
28        self.vtk_actors = {}
[3670]29        self.vtk_axesSet = False
30        self.vtk_drawAxes = False
[3493]31        self.vtk_mappers = {}
32        self.vtk_polyData = {}
33
[3958]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 = []
[3548]40
[3465]41    def run(self):
[3873]42        self.vtk_renderer = vtkRenderer()
43        self.setup_gui()
[3493]44        self.setup_grid()
[3873]45
[3958]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.
[3873]60
[3493]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)
[3873]65           
[3465]66        self.tk_root.mainloop()
67
[3958]68    def redraw_quantities(self):
[3873]69        """Redraw all dynamic quantities.
[3493]70        """
71        # Height quantities
72        for q in self.height_quantities:
[3873]73            if (self.height_dynamic[q]):
[3493]74                self.update_height_quantity(q, self.height_dynamic[q])
75                self.draw_height_quantity(q)
[3670]76        if self.vtk_drawAxes is True:
[3873]77            self.draw_axes()
78
79    # --- Axes --- #
[3670]80       
81    def render_axes(self):
82        """Intstruct the visualiser to render cube axes around the render.
83        """
84        self.vtk_drawAxes = True
[3873]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)
[3958]94            self.vtk_renderer.ResetCamera(self.get_3d_bounds())
[3670]95       
[3873]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,))
[3670]104        """
[3958]105        self.conf_axesAlterations.append((func, args))
[3873]106           
107    # --- Height Based Rendering --- #
108
[3493]109    def setup_grid(self):
110        """Create the vtkCellArray instance that represents the
111        triangles. Subclasses are expected to override this function
[3625]112        to read from their source as appropriate. The vtkCellArray should
113        be stored to self.vtk_cells.
[3465]114        """
[3493]115        pass
[3465]116
[6113]117    def render_quantity_height(self, quantityName, zScale=1.0, offset=0.0, opacity=1.0, dynamic=True, wireframe=False):
[3493]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
[4267]128        self.height_opacity[quantityName] = opacity
[6113]129        self.height_wireframe[quantityName] = wireframe
[3493]130
131    def update_height_quantity(self, quantityName, dynamic=True):
132        """Create a vtkPolyData object and store it in
[3625]133        self.vtk_polyData[quantityName]. Subclasses are expected to override this
[3493]134        function.
135        """
136        pass
137
[3670]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
[3493]145
146    def draw_height_quantity(self, quantityName):
147        """Use the vtkPolyData and prepare/update the rest of the VTK
148        rendering pipeline.
149        """
150        if self.vtk_mappers.has_key(quantityName):
151            mapper = self.vtk_mappers[quantityName]
152        else:
153            mapper = self.vtk_mappers[quantityName] = vtkPolyDataMapper()
154        mapper.SetInput(self.vtk_polyData[quantityName])
155        mapper.Update()
156
157        if not self.vtk_actors.has_key(quantityName):
158            actor = self.vtk_actors[quantityName] = vtkActor()
[4267]159            actor.GetProperty().SetOpacity(self.height_opacity[quantityName])
[6113]160            if self.height_wireframe[quantityName]:
161                actor.GetProperty().SetRepresentationToWireframe()
[3493]162            actor.SetMapper(mapper)
163            self.vtk_renderer.AddActor(actor)
[3873]164        else:
165            actor = self.vtk_actors[quantityName]
[3493]166
[3538]167        if self.colours_height.has_key(quantityName):
168            colour = self.colours_height[quantityName]
169            if type(colour) == TupleType:
170                if type(colour[0]) == FunctionType:
171                    # It's a function, so take colour[1] as the
172                    # lower bound on the scalar range and
173                    # colour[2] as the upper bound on the scalar
174                    # range.
175                    scalars = vtkFloatArray()
176                    map(scalars.InsertNextValue, colour[0](self.build_quantity_dict()))
177                    self.vtk_polyData[quantityName].GetPointData().SetScalars(scalars)
178                    mapper.SetScalarRange(colour[1:])
179                    mapper.Update()
180                else:
181                    # It's a 3-tuple representing an RGB value.
182                    actor.GetProperty().SetColor(colour)
183            else:
184                actor.GetProperty().SetColor(0.5, 0.5, 0.5)
185        else:
186            actor.GetProperty().SetColor(0.5, 0.5, 0.5)
187
[3465]188    # --- Colour Coding --- #
189
[3538]190    def build_quantity_dict(self):
[3625]191        """Build and return a dictionary mapping quantity name->Numeric array of vertex
[3538]192        values for that quantity. Subclasses are expected to override
193        this function."""
194        pass
195
196    def colour_height_quantity(self, quantityName, colour=(0.5, 0.5, 0.5)):
197        """Add colouring to a height based quantity.
198
199        The colour parameter can be one of the following:
200        - a 3-tuple of values in [0,1] to specify R, G, B values
201        - a 3-tuple of values:
202          - a function that takes a dictionary mapping quantity name->Numeric array of vertex values.
203            This function returns a list of vertex values to be used in the colour coding.
204          - a float for the lower bound on the colouring
205          - a float for the upper bound on the colouring
206        """
207        self.colours_height[quantityName] = colour
[3813]208
209    # --- Overlaid Polygons --- #
210
211    def overlay_polygon(self, coords, height=0.0, colour=(1.0, 0.0, 0.0)):
212        """Add a polygon to the output of the visualiser.
213
214        coords is a list of 2-tuples representing x and y coordinates.
215        These are triangulated by vtkDelaunay2D.
216
217        height is the z-value given to all points.
218
219        colour is the colour of the polygon, as a 3-tuple representing
220        r, g, b values between 0 and 1."""
[3958]221        self.conf_overlaidPolygons.append((coords, height, colour))
[3873]222
[3958]223    def overlay_polygon_internal(self, coords, height, colour):
[3873]224        """Add a polygon to the output of the visualiser.
225
226        coords is a list of 2-tuples representing x and y coordinates.
227        These are triangulated by vtkDelaunay2D.
228
229        height is the z-value given to all points.
230
231        colour is the colour of the polygon, as a 3-tuple representing
232        r, g, b values between 0 and 1.
233
234        This function should not be called from outside the visualiser thread.
235        Use overlay_polygon instead.
236   
237        """
[3813]238        points = vtkPoints()
239        for coord in coords:
240            points.InsertNextPoint(coord[0], coord[1], height)
241        profile = vtkPolyData()
242        profile.SetPoints(points)
243        delny = vtkDelaunay2D()
244        delny.SetInput(profile)
245        mesh = vtkPolyDataMapper()
246        mesh.SetInput(delny.GetOutput())
247        actor = vtkActor()
248        actor.SetMapper(mesh)
249        actor.GetProperty().SetColor(colour)
250        self.vtk_renderer.AddActor(actor)
251       
[3465]252    # --- Vector Fields --- #
253
254    # --- GUI Setup --- #
255
256    def setup_gui(self):
257        self.tk_root = Tk()
258        self.tk_root.title("Visualisation")
259        self.tk_root.after(100, self.redraw)
260        self.tk_root.bind("<Destroy>", self.destroyed)
[3549]261        self.tk_root.grid_rowconfigure(0, weight=1)
262        self.tk_root.grid_columnconfigure(0, weight=1)
[3465]263
264        self.tk_renderWidget = vtkTkRenderWidget(self.tk_root, width=400, height=400)
[3493]265        self.tk_renderWidget.grid(row=0, column=0, sticky=N+S+E+W)
[3549]266        self.tk_controlFrame = Frame(self.tk_root)
267        self.tk_controlFrame.grid(row=1, column=0, sticky=E+W)
268        self.tk_controlFrame.grid_rowconfigure(0, weight=1)
269        self.tk_controlFrame.grid_columnconfigure(0, weight=1)
270       
271        self.tk_quit = Button(self.tk_controlFrame, text="Quit", command=self.shutdown)
272        self.tk_quit.grid(row=0, column=0, sticky=E+W)
[3465]273        self.tk_renderWidget.GetRenderWindow().AddRenderer(self.vtk_renderer)
274
[3873]275    def alter_tkroot(self, func, args):
276        """Apply func, with arguments tuple args to the root tk window for this visualiser.
277        """
[3958]278        self.conf_tkAlterations.append((func, args))
[3873]279
[3465]280    # --- GUI Events --- #
281
282    def destroyed(self, event):
283        if event.widget == self.tk_root:
284            self.shutdown()
285
286    def redraw(self):
[3493]287        self.tk_renderWidget.GetRenderWindow().Render()
288        self.tk_root.update_idletasks()
289        self.tk_root.after(100, self.redraw)
[3465]290
291    def shutdown(self):
292        self.tk_root.withdraw()
[3547]293        self.tk_root.destroy()
Note: See TracBrowser for help on using the repository browser.