Changeset 3873


Ignore:
Timestamp:
Oct 26, 2006, 1:02:41 PM (19 years ago)
Author:
jack
Message:

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

Added a simple example to use the realtime visualiser.

Location:
anuga_core/source/anuga
Files:
4 edited
1 copied

Legend:

Unmodified
Added
Removed
  • anuga_core/source/anuga/examples/sww_file_visualiser_example.py

    r3813 r3873  
     1#!/usr/bin/env python
    12##########
    23# Demonstration of the VTK sww Visualiser
     
    78# Import the offline visualiser
    89from anuga.visualiser import OfflineVisualiser
     10from vtk import vtkCubeAxesActor2D
    911
    1012# The argument to OfflineVisualiser is the path to a sww file
     
    3133o.render_axes()
    3234
    33 # Draw a polygon (here, a triangle) at height 10
     35# Increase the number of labels on the axes
     36o.alter_axes(vtkCubeAxesActor2D.SetNumberOfLabels, (5,))
     37
     38# Draw a yellow polygon at height 10
    3439o.overlay_polygon([(20, 50), (40, 40), (50, 10), (30, 20), (10, 30)], 10, colour=(1.0, 1.0, 0.0))
    3540
     
    3944
    4045# Start the visualiser (in its own thread).
    41 o.run()
     46o.start()
     47
     48# Wait for the visualiser to terminate before shutting down.
     49o.join()
  • anuga_core/source/anuga/examples/visualise_rectangle.py

    r3870 r3873  
    11#!/usr/bin/env python
    2 """
    3   Example showing improved wet dry limiting.
    4   This script runs the same simulation twice and stores the outputs in
    5   old_limiter.sww and new_limiter.sww respectively.
    6  
    7   Authors: Steve Roberts
    8   October
    9 """
     2##########
     3# Demonstration of the VTK realtime Visualiser.
     4# Based on run_sw_rectangle.py
     5# Jack Kelly and Stephen Roberts
     6# October 2006
     7##########
    108
    11 #-----------------------------------------------------------------
    12 # Common structures
    13 #-----------------------------------------------------------------
     9# Import the offline visualiser
     10from anuga.visualiser import RealtimeVisualiser
     11from vtk import vtkCubeAxesActor2D
     12
    1413import time
    1514from Numeric import array
     
    1716from anuga.shallow_water import Reflective_boundary
    1817from anuga.abstract_2d_finite_volumes.mesh_factory import rectangular
     18
    1919
    2020class Set_Stage:
     
    3737
    3838yieldstep = 0.002
    39 finaltime = 0.06
     39finaltime = 1.0
    4040rect = [0.0, 0.0, 1.0, 1.0]
    4141
     42domain = Domain(points, vertices, boundary)
    4243
    43 #-----------------------------------------------------------------
    44 # Create domain for "old limiter" scenario
    45 #-----------------------------------------------------------------
    46 domain = Domain(points, vertices, boundary)
    47 domain.set_name('old_limiter_second_order')
    48 print 'Number of triangles =', len(domain)
     44# Turn on the visualisation. The argument to the realtime visualier
     45# is a domain object.
     46v = RealtimeVisualiser(domain)
    4947
    50 # Turn on the visualisation
    51 try:
    52     domain.initialise_visualiser()
    53 except:
    54     pass
     48# Specify the height-based-quantities to render.
     49# Remember to set dynamic=True for time-varying quantities
     50v.render_quantity_height("elevation", dynamic=False)
     51v.render_quantity_height("stage", dynamic=True)
    5552
     53# Colour the stage:
     54# Either with an RGB value as a 3-tuple of Floats,
     55#v.colour_height_quantity('stage', (0.0, 0.0, 0.8))
     56# Or with a function of the quantities at that point, such as the stage height:
     57# 0 and 1 are the minimum and maximum values of the stage.
     58v.colour_height_quantity('stage', (lambda q:q['stage'], 0, 1))
     59# Or with the magnitude of the momentum at that point:
     60# Needs the sqrt function from Numeric. Again, 0 and 10 define the colour range.
     61#v.colour_height_quantity('stage', (lambda q:sqrt((q['xmomentum'] ** 2) +
     62#                                                 (q['ymomentum'] ** 2)), 0, 10))
     63
     64# Draw some axes on the visualiser so we can see how big the wave is
     65v.render_axes()
     66
     67# Increase the number of labels on the axes
     68v.alter_axes(vtkCubeAxesActor2D.SetNumberOfLabels, (5,))
     69
     70# Draw a yellow polygon at height 10
     71v.overlay_polygon([(20, 50), (40, 40), (50, 10), (30, 20), (10, 30)], 10, colour=(1.0, 1.0, 0.0))
     72
     73# Start the visualiser (in its own thread).
     74v.start()
    5675
    5776#-----------------------------------------------------------------
     
    6382
    6483#-----------------------------------------------------------------
    65 # Values for old limiter
    66 #-----------------------------------------------------------------
    67 domain.default_order = 2
    68 domain.beta_w      = 0.9
    69 domain.beta_w_dry  = 0.9
    70 domain.beta_uh     = 0.9
    71 domain.beta_uh_dry = 0.9
    72 domain.beta_vh     = 0.9
    73 domain.beta_vh_dry = 0.9
    74 
    75 #-----------------------------------------------------------------
    7684# Evolve
    7785#-----------------------------------------------------------------
    7886t0 = time.time()
    7987for t in domain.evolve(yieldstep = yieldstep, finaltime = finaltime):
     88    v.update()
    8089    domain.write_time()
    81 
    82 print 'That took %.2f seconds' %(time.time()-t0)
    83 print 'Note the small timesteps and the irregular flow'
    84 #raw_input('press return to continue')
    85 
    86 
    87 #-----------------------------------------------------------------
    88 # Create domain for "new limiter" scenario (2 order)
    89 #-----------------------------------------------------------------
    90 domain = Domain(points, vertices, boundary)
    91 domain.set_name('new_limiter_second_order')
    92 print 'Number of triangles =', len(domain)
    93 
    94 # Turn on the visualisation
    95 try:
    96     domain.initialise_visualiser()
    97 except:
    98     pass
    99 
    100 #-----------------------------------------------------------------
    101 # Boundaries and Initial conditions
    102 #-----------------------------------------------------------------
    103 R = Reflective_boundary(domain)
    104 domain.set_boundary( {'left': R, 'right': R, 'bottom': R, 'top': R} )
    105 domain.set_quantity('stage', Set_Stage(0.2, 0.4, 0.25, 0.75, 1.0, 0.00))
    106 
    107 #-----------------------------------------------------------------
    108 # Values for new limiter
    109 #-----------------------------------------------------------------
    110 domain.set_default_order(2)
    111 domain.minimum_allowed_height = 0.001
    112 domain.beta_w      = 1.0
    113 domain.beta_w_dry  = 0.2
    114 domain.beta_uh     = 1.0
    115 domain.beta_uh_dry = 0.2
    116 domain.beta_vh     = 1.0
    117 domain.beta_vh_dry = 0.2
    118 
    119 #-----------------------------------------------------------------
    120 # Evolve
    121 #-----------------------------------------------------------------
    122 t0 = time.time()
    123 for t in domain.evolve(yieldstep = yieldstep, finaltime = finaltime):
    124     domain.write_time()
    125 
    126 print 'That took %.2f seconds' %(time.time()-t0)
    127 print 'Note more uniform and large timesteps'
    128 #raw_input('press return to continue')
    129 
    130 
    131 #-----------------------------------------------------------------
    132 # Create domain for "new limiter" scenario (1 order)
    133 #-----------------------------------------------------------------
    134 domain = Domain(points, vertices, boundary)
    135 domain.set_name('new_limiter_first_order')
    136 print 'Number of triangles =', len(domain)
    137 
    138 # Turn on the visualisation
    139 try:
    140     domain.initialise_visualiser()
    141 except:
    142     pass
    143 
    144 #-----------------------------------------------------------------
    145 # Boundaries and Initial conditions
    146 #-----------------------------------------------------------------
    147 R = Reflective_boundary(domain)
    148 domain.set_boundary( {'left': R, 'right': R, 'bottom': R, 'top': R} )
    149 domain.set_quantity('stage', Set_Stage(0.2, 0.4, 0.25, 0.75, 1.0, 0.00))
    150 
    151 #-----------------------------------------------------------------
    152 # Values for new limiter first order
    153 #-----------------------------------------------------------------
    154 domain.set_default_order(1)
    155 domain.minimum_allowed_height = 0.001
    156 domain.beta_w      = 1.0
    157 domain.beta_w_dry  = 0.2
    158 domain.beta_uh     = 1.0
    159 domain.beta_uh_dry = 0.2
    160 domain.beta_vh     = 1.0
    161 domain.beta_vh_dry = 0.2
    162 
    163 #-----------------------------------------------------------------
    164 # Evolve
    165 #-----------------------------------------------------------------
    166 t0 = time.time()
    167 for t in domain.evolve(yieldstep = yieldstep, finaltime = finaltime):
    168     domain.write_time()
     90# Unhook the visualiser from the evolve loop.
     91# It won't shutdown cleanly unless you do this.
     92v.evolveFinished()
    16993
    17094print 'That took %.2f seconds' %(time.time()-t0)
    17195
    172 
     96# Wait for the visualiser to be closed
     97v.join()
  • anuga_core/source/anuga/visualiser/offline.py

    r3670 r3873  
    11from Numeric import array, Float, ravel, zeros
    22from Scientific.IO.NetCDF import NetCDFFile
    3 from Tkinter import Button, E, W
     3from Tkinter import Button, E, Tk, W
    44from visualiser import Visualiser
    55from vtk import vtkCellArray, vtkPoints, vtkPolyData
     
    1313    structures for any dynamic height based quantities to render.
    1414    """
    15     def __init__(self, source):
     15    def __init__(self, source, frameDelay=100):
    1616        """The source parameter is assumed to be a NetCDF sww file.
     17        The frameDelay parameter is the number of milliseconds waited between frames.
    1718        """
    1819        Visualiser.__init__(self, source)
     
    2223        self.maxFrameNumber = fin.variables['time'].shape[0] - 1
    2324        fin.close()
     25
     26        self.frameDelay = frameDelay
    2427
    2528        self.xmin = None
     
    140143
    141144    def run(self):
    142         self.tk_root.after(100, self.animateForward)
     145        self.alter_tkroot(Tk.after, (self.frameDelay, self.animateForward))
    143146        Visualiser.run(self)
    144147
    145148    def restart(self):
    146149        self.frameNumber = 0
    147         self.redraw_quantities(True)
     150        self.redraw_quantities()
    148151        self.pause()
    149152
     
    153156        else:
    154157            self.frameNumber = 0
    155         self.redraw_quantities(True)
     158        self.redraw_quantities()
    156159        self.pause()
    157160
     
    159162        if self.frameNumber > 0:
    160163            self.frameNumber -= 1
    161             self.redraw_quantities(True)
     164            self.redraw_quantities()
    162165            self.pause()
    163166
     
    175178        self.paused = False
    176179        self.tk_pauseResume.config(text="Pause")
    177         self.tk_root.after(100, self.animateForward)
     180        self.tk_root.after(self.frameDelay, self.animateForward)
    178181
    179182    def forward(self):
     
    184187        if self.frameNumber < self.maxFrameNumber:
    185188            self.frameNumber += 1
    186             self.redraw_quantities(True)
     189            self.redraw_quantities()
    187190        else:
    188191            self.pause()
     
    193196        else:
    194197            self.frameNumber = self.maxFrameNumber
    195         self.redraw_quantities(True)
     198        self.redraw_quantities()
    196199        self.pause()
    197200
     
    199202        if self.paused is not True:
    200203            self.forward_step()
    201             self.tk_root.after(100, self.animateForward)
     204            self.tk_root.after(self.frameDelay, self.animateForward)
  • anuga_core/source/anuga/visualiser/realtime.py

    r3625 r3873  
    11from Numeric import Float, zeros
    2 from Tkinter import Button, E, W
     2from Tkinter import Button, E, Tk, W
    33from threading import Event
    44from visualiser import Visualiser
     
    77class RealtimeVisualiser(Visualiser):
    88    """A VTK-powered realtime visualiser which runs in its own thread.
     9    In addition to the functions provided by the standard visualiser,
     10    the following additional functions are provided:
     11
     12    update() - Sync the visualiser to the current state of the model.
     13    Should be called inside the evolve loop.
     14
     15    evolveFinished() - Clean up synchronisation constructs that tie the
     16    visualiser to the evolve loop. Call this after the evolve loop finishes
     17    to ensure a clean shutdown.
    918    """
    1019    def __init__(self, source):
     
    1423
    1524        self.running = True
     25
     26        self.xmin = None
     27        self.xmax = None
     28        self.ymin = None
     29        self.ymax = None
     30        self.zmin = None
     31        self.zmax = None
    1632
    1733        # Synchronisation Constructs
     
    2440
    2541    def run(self):
    26         self.tk_root.after(100, self.sync_idle.set)
     42        self.alter_tkroot(Tk.after, (100, self.sync_idle.set))
    2743        Visualiser.run(self)
    2844
     
    3551            self.vtk_cells.InsertNextCell(3)
    3652            for v in range(3):
    37                 self.vert_index[self.source.triangles[n][v]] = self.source.vertex_coordinates[n][i*2:i*2+2]
     53                self.vert_index[self.source.triangles[n][v]] = self.source.vertex_coordinates[n][v*2:v*2+2]
    3854                self.vtk_cells.InsertCellPoint(self.source.triangles[n][v])
    3955
     
    5268                                   qty_index[v] * self.height_zScales[quantityName]
    5369                                   + self.height_offset[quantityName])
     70            if self.xmin == None or self.xmin > self.vert_index[v][0]:
     71                self.xmin = self.vert_index[v][0]
     72            if self.xmax == None or self.xmax < self.vert_index[v][0]:
     73                self.xmax = self.vert_index[v][0]
     74            if self.ymin == None or self.ymin > self.vert_index[v][1]:
     75                self.ymin = self.vert_index[v][1]
     76            if self.ymax == None or self.ymax < self.vert_index[v][1]:
     77                self.ymax = self.vert_index[v][1]
     78            if self.zmin == None or self.zmin > qty_index[v] * self.height_zScales[quantityName] + self.height_offset[quantityName]:
     79                self.zmin = qty_index[v] * self.height_zScales[quantityName] + self.height_offset[quantityName]
     80            if self.zmax == None or self.zmax < qty_index[v] * self.height_zScales[quantityName] + self.height_offset[quantityName]:
     81                self.zmax = qty_index[v] * self.height_zScales[quantityName] + self.height_offset[quantityName]
    5482
    5583        polydata = self.vtk_polyData[quantityName] = vtkPolyData()
    5684        polydata.SetPoints(points)
    5785        polydata.SetPolys(self.vtk_cells)
     86
     87    def get_3d_bounds(self):
     88        return [self.xmin, self.xmax, self.ymin, self.ymax, self.zmin, self.zmax]
    5889       
    5990    def build_quantity_dict(self):
     
    86117        self.sync_unpaused.set()
    87118
     119    def redraw(self):
     120        if self.running:
     121            self.sync_redrawReady.wait()
     122            self.sync_redrawReady.clear()
     123            self.redraw_quantities()
     124            self.sync_idle.set()
     125        Visualiser.redraw(self)
     126
     127    def update(self):
     128        """Sync the visualiser to the domain. Call this in the evolve loop."""
     129        if self.running:
     130            self.sync_redrawReady.set()
     131            self.sync_idle.wait()
     132            self.sync_idle.clear()
     133            self.sync_unpaused.wait()
     134
     135    def evolveFinished(self):
     136        """Stop the visualiser from waiting on signals from the evolve loop.
     137        Call this just after the evolve loop to ensure a clean shutdown."""
     138        self.running = False
     139        self.sync_redrawReady.set()
  • anuga_core/source/anuga/visualiser/visualiser.py

    r3813 r3873  
    11from threading import Thread
     2from Queue import Queue
    23from Tkinter import Tk, Button, Frame, N, E, S, W
    34from types import FunctionType, TupleType
     
    2829        self.vtk_mappers = {}
    2930        self.vtk_polyData = {}
     31
     32        # A function queue to ensure all the visualiser configuration is done in the visualiser thread.
     33        # The queue will contain zero or more functions, with the arguments to that function immediately following
     34        # as a tuple.
     35        self.sync_configFuncs = Queue(-1)
     36
     37    def run(self):
    3038        self.vtk_renderer = vtkRenderer()
    31 
    3239        self.setup_gui()
    33 
    34     def run(self):
    3540        self.setup_grid()
     41
     42        # Normally, empty() is not reliable, however all configuration is done
     43        # before the visualiser is started and nothing is added to the queue after that.
     44        while not self.sync_configFuncs.empty():
     45            func = self.sync_configFuncs.get(True)
     46            args = self.sync_configFuncs.get(True)
     47            func(*args)
     48
    3649        # Draw Height Quantities
    3750        for q in self.height_quantities:
    3851            self.update_height_quantity(q, self.height_dynamic[q])
    3952            self.draw_height_quantity(q)
     53
     54        if self.vtk_drawAxes is True:
     55            self.tk_root.after(1, self.draw_axes)
     56           
    4057        self.tk_root.mainloop()
    4158
    4259    def redraw_quantities(self, dynamic_only=False):
    43         """Redraw all dynamic quantities, unless dynamic_only is True.
     60        """Redraw all dynamic quantities.
    4461        """
    4562        # Height quantities
    4663        for q in self.height_quantities:
    47             if (dynamic_only is False) or (self.height_dynamic[q]):
     64            if (self.height_dynamic[q]):
    4865                self.update_height_quantity(q, self.height_dynamic[q])
    4966                self.draw_height_quantity(q)
    5067        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)
     68            self.draw_axes()
     69
     70    # --- Axes --- #
    5671       
    57     # --- Height Based Rendering --- #
    58 
    5972    def render_axes(self):
    6073        """Intstruct the visualiser to render cube axes around the render.
    6174        """
     75        self.sync_configFuncs.put(self._render_axes, True)
     76        self.sync_configFuncs.put(())
     77
     78    def _render_axes(self):
     79        """Add the axes to the visualiser.
     80        """
    6281        self.vtk_drawAxes = True
    6382        self.vtk_axes = vtkCubeAxesActor2D()
     83
     84    def draw_axes(self):
     85        """Update the 3D bounds on the axes and add them to the pipeline if not yet connected.
     86        """
     87        self.vtk_axes.SetBounds(self.get_3d_bounds())
     88        if not self.vtk_axesSet:
     89            self.vtk_axesSet = True
     90            self.vtk_axes.SetCamera(self.vtk_renderer.GetActiveCamera())
     91            self.vtk_renderer.AddActor(self.vtk_axes)
    6492       
    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
     93    def alter_axes(self, func, args):
     94        """Attempt to apply the function 'func' with args tuple 'args' to the
     95        vtkCubeAxesActor2D instance set up by render_axes. This is done this way to ensure
     96        the axes setup is handled in the visualiser thread.
     97
     98        Example call:
     99        from vtk import vtkCubeAxesActor2D
     100        alter_axes(vtkCubeAxesActor2D.SetNumberOfPoints, (5,))
     101        """
     102        self.sync_configFuncs.put(self._alter_axes, True)
     103        self.sync_configFuncs.put((func,)+args, True)
     104
     105    def _alter_axes(self, func, *args):
     106        """Apply func, with args *args to the axes actor. This function should be called from
     107        within the visualiser thread after the axes have been initialised.
     108        """
     109        func(*((self.vtk_axes,) + args))
     110           
     111    # --- Height Based Rendering --- #
    71112
    72113    def setup_grid(self):
     
    120161            actor.SetMapper(mapper)
    121162            self.vtk_renderer.AddActor(actor)
     163        else:
     164            actor = self.vtk_actors[quantityName]
    122165
    123166        if self.colours_height.has_key(quantityName):
     
    175218        colour is the colour of the polygon, as a 3-tuple representing
    176219        r, g, b values between 0 and 1."""
     220        self.sync_configFuncs.put(self._overlay_polygon, True)
     221        self.sync_configFuncs.put((coords, height, colour), True)
     222
     223    def _overlay_polygon(self, coords, height, colour):
     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        """
    177238        points = vtkPoints()
    178239        for coord in coords:
     
    212273        self.tk_renderWidget.GetRenderWindow().AddRenderer(self.vtk_renderer)
    213274
     275    def alter_tkroot(self, func, args):
     276        """Apply func, with arguments tuple args to the root tk window for this visualiser.
     277        """
     278        self.sync_configFuncs.put(self._alter_tkroot, True)
     279        self.sync_configFuncs.put((func,)+args, True)
     280
     281    def _alter_tkroot(self, func, *args):
     282        """Apply func, with arguments tuple args to the root tk window for this visualiser.
     283        This function should only be called from within the visualiser after the tk root window
     284        has been created.
     285        """
     286        func(*((self.tk_root,) + args))
     287
    214288    # --- GUI Events --- #
    215289
Note: See TracChangeset for help on using the changeset viewer.