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

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

Added axes support to the visualiser.

File size: 7.7 KB
Line 
1from threading import Thread
2from Tkinter import Tk, Button, Frame, N, E, S, W
3from types import FunctionType, TupleType
4from vtk import vtkActor, vtkCubeAxesActor2D, vtkFloatArray, vtkPolyDataMapper, vtkRenderer
5from vtk.tk.vtkTkRenderWidget import vtkTkRenderWidget
6
7class Visualiser(Thread):
8    """Superclass of both the realtime and offline VTK visualisers
9    """
10    def __init__(self, source):
11        Thread.__init__(self)
12
13        self.source = source
14
15        # Structures for Height Based quantities
16        self.height_quantities = []
17        self.height_zScales = {}
18        self.height_dynamic = {}
19        self.height_offset = {}
20
21        # Structures for colouring quantities
22        self.colours_height = {}
23
24        # Structures used for VTK
25        self.vtk_actors = {}
26        self.vtk_axesSet = False
27        self.vtk_drawAxes = False
28        self.vtk_mappers = {}
29        self.vtk_polyData = {}
30        self.vtk_renderer = vtkRenderer()
31
32        self.setup_gui()
33
34    def run(self):
35        self.setup_grid()
36        # Draw Height Quantities
37        for q in self.height_quantities:
38            self.update_height_quantity(q, self.height_dynamic[q])
39            self.draw_height_quantity(q)
40        self.tk_root.mainloop()
41
42    def redraw_quantities(self, dynamic_only=False):
43        """Redraw all dynamic quantities, unless dynamic_only is True.
44        """
45        # Height quantities
46        for q in self.height_quantities:
47            if (dynamic_only is False) or (self.height_dynamic[q]):
48                self.update_height_quantity(q, self.height_dynamic[q])
49                self.draw_height_quantity(q)
50        if self.vtk_drawAxes is True:
51            self.vtk_axes.SetBounds(self.get_3d_bounds())
52            if not self.vtk_axesSet:
53                self.vtk_axesSet = True
54                self.vtk_axes.SetCamera(self.vtk_renderer.GetActiveCamera())
55                self.vtk_renderer.AddActor(self.vtk_axes)
56       
57    # --- Height Based Rendering --- #
58
59    def render_axes(self):
60        """Intstruct the visualiser to render cube axes around the render.
61        """
62        self.vtk_drawAxes = True
63        self.vtk_axes = vtkCubeAxesActor2D()
64       
65    def get_axes(self):
66        """Return the vtkCubeAxesActor2D object used to render the axes.
67        This is to allow simple manipulation of the axes such as
68        get_axes().SetNumberOfLabels(5) or similar.
69        """
70        return self.vtk_axes
71
72    def setup_grid(self):
73        """Create the vtkCellArray instance that represents the
74        triangles. Subclasses are expected to override this function
75        to read from their source as appropriate. The vtkCellArray should
76        be stored to self.vtk_cells.
77        """
78        pass
79
80    def render_quantity_height(self, quantityName, zScale=1.0, offset=0.0, dynamic=True):
81        """Instruct the visualiser to render a quantity using the
82        value at a point as its height.  The value at each point is
83        multiplied by z_scale and is added to offset, and if
84        dynamic=False, the quantity is not recalculated on each
85        update.
86        """
87        self.height_quantities.append(quantityName)
88        self.height_zScales[quantityName] = zScale
89        self.height_offset[quantityName] = offset
90        self.height_dynamic[quantityName] = dynamic
91
92    def update_height_quantity(self, quantityName, dynamic=True):
93        """Create a vtkPolyData object and store it in
94        self.vtk_polyData[quantityName]. Subclasses are expected to override this
95        function.
96        """
97        pass
98
99    def get_3d_bounds(self):
100        """Get the minimum and maximum bounds for the x, y and z directions.
101        Return as a list of double in the order (xmin, xmax, ymin, ymax, zmin, zmax),
102        suitable for passing to vtkCubeAxesActor2D::SetRanges(). Subclasses are expected
103        to override this function.
104        """
105        pass
106
107    def draw_height_quantity(self, quantityName):
108        """Use the vtkPolyData and prepare/update the rest of the VTK
109        rendering pipeline.
110        """
111        if self.vtk_mappers.has_key(quantityName):
112            mapper = self.vtk_mappers[quantityName]
113        else:
114            mapper = self.vtk_mappers[quantityName] = vtkPolyDataMapper()
115        mapper.SetInput(self.vtk_polyData[quantityName])
116        mapper.Update()
117
118        if not self.vtk_actors.has_key(quantityName):
119            actor = self.vtk_actors[quantityName] = vtkActor()
120            actor.SetMapper(mapper)
121            self.vtk_renderer.AddActor(actor)
122
123        if self.colours_height.has_key(quantityName):
124            colour = self.colours_height[quantityName]
125            if type(colour) == TupleType:
126                if type(colour[0]) == FunctionType:
127                    # It's a function, so take colour[1] as the
128                    # lower bound on the scalar range and
129                    # colour[2] as the upper bound on the scalar
130                    # range.
131                    scalars = vtkFloatArray()
132                    map(scalars.InsertNextValue, colour[0](self.build_quantity_dict()))
133                    self.vtk_polyData[quantityName].GetPointData().SetScalars(scalars)
134                    mapper.SetScalarRange(colour[1:])
135                    mapper.Update()
136                else:
137                    # It's a 3-tuple representing an RGB value.
138                    actor.GetProperty().SetColor(colour)
139            else:
140                actor.GetProperty().SetColor(0.5, 0.5, 0.5)
141        else:
142            actor.GetProperty().SetColor(0.5, 0.5, 0.5)
143
144    # --- Colour Coding --- #
145
146    def build_quantity_dict(self):
147        """Build and return a dictionary mapping quantity name->Numeric array of vertex
148        values for that quantity. Subclasses are expected to override
149        this function."""
150        pass
151
152    def colour_height_quantity(self, quantityName, colour=(0.5, 0.5, 0.5)):
153        """Add colouring to a height based quantity.
154
155        The colour parameter can be one of the following:
156        - a 3-tuple of values in [0,1] to specify R, G, B values
157        - a 3-tuple of values:
158          - a function that takes a dictionary mapping quantity name->Numeric array of vertex values.
159            This function returns a list of vertex values to be used in the colour coding.
160          - a float for the lower bound on the colouring
161          - a float for the upper bound on the colouring
162        """
163        self.colours_height[quantityName] = colour
164           
165    # --- Vector Fields --- #
166
167    # --- GUI Setup --- #
168
169    def setup_gui(self):
170        self.tk_root = Tk()
171        self.tk_root.title("Visualisation")
172        self.tk_root.after(100, self.redraw)
173        self.tk_root.bind("<Destroy>", self.destroyed)
174        self.tk_root.grid_rowconfigure(0, weight=1)
175        self.tk_root.grid_columnconfigure(0, weight=1)
176
177        self.tk_renderWidget = vtkTkRenderWidget(self.tk_root, width=400, height=400)
178        self.tk_renderWidget.grid(row=0, column=0, sticky=N+S+E+W)
179        self.tk_controlFrame = Frame(self.tk_root)
180        self.tk_controlFrame.grid(row=1, column=0, sticky=E+W)
181        self.tk_controlFrame.grid_rowconfigure(0, weight=1)
182        self.tk_controlFrame.grid_columnconfigure(0, weight=1)
183       
184        self.tk_quit = Button(self.tk_controlFrame, text="Quit", command=self.shutdown)
185        self.tk_quit.grid(row=0, column=0, sticky=E+W)
186        self.tk_renderWidget.GetRenderWindow().AddRenderer(self.vtk_renderer)
187
188    # --- GUI Events --- #
189
190    def destroyed(self, event):
191        if event.widget == self.tk_root:
192            self.shutdown()
193
194    def redraw(self):
195        self.tk_renderWidget.GetRenderWindow().Render()
196        self.tk_root.update_idletasks()
197        self.tk_root.after(100, self.redraw)
198
199    def shutdown(self):
200        self.tk_root.withdraw()
201        self.tk_root.destroy()
Note: See TracBrowser for help on using the repository browser.