Difference between revisions of "ParaView/Plugin HowTo"

From KitwarePublic
Jump to navigationJump to search
m
Line 505: Line 505:


In ParaView 3.4, there's also a macro, ADD_PARAVIEW_VIEW_OPTIONS() which allows adding options pages for the custom view, accessible from Edit -> View Settings.  The example in ParaView3/Examples/Plugins/GUIView demonstrates this (until more information is put here).
In ParaView 3.4, there's also a macro, ADD_PARAVIEW_VIEW_OPTIONS() which allows adding options pages for the custom view, accessible from Edit -> View Settings.  The example in ParaView3/Examples/Plugins/GUIView demonstrates this (until more information is put here).
= Examples =
The ParaView CVS repository contains many examples in the Plugins directory. Additional examples are available on this wiki at the [[Plugin Examples]] entry.


{{ParaView/Template/Footer}}
{{ParaView/Template/Footer}}

Revision as of 15:23, 20 October 2008

Overview

ParaView comes with plethora of functionality bundled in: several readers, multitude of filters, quite a few different types of views etc. However, it is not uncommon for developers to want to add new functionality to ParaView for eg. to add support to their new file format, incorporate a new filter into paraview etc. ParaView makes it possible to add new functionlity by using an extensive plugin mechanism.

Plugins can be used to extend ParaView in several ways:

  • Add new readers, writers, filters
  • Add custom GUI components such as toolbar buttons to perform common tasks
  • Add new views in for display data

Examples for different types of plugins are provided with the ParaView source under Examples/Plugins/.

This document has major sections:

  • First section covers how to use existing plugins in ParaView.
  • Second section contains information for developers about writing new plugins for ParaView.

Using Plugins

Plugins are distributed as shared libraries (*.so on Unix, *.dylib on Mac, *.dll on Windows etc). For a plugin to be loadable in ParaView, it must be built with the same version of ParaView as it is expected to be deployed on. Plugins can be classified into two broad categories:

  • Server-side plugins
These are plugins that extend the algorithmic capabilities for ParaView eg. new filters, readers, writers etc. Since in ParaView data is processed on the server-side, these plugins need to be loaded on the server.
  • Client-side plugins
These are plugins that extend the ParaView GUI eg. property panels for new filters, toolbars, views etc. These plugins need to be loaded on the client.

Oftentimes a plugin has both server-side as well as client-side components to it eg. a plugin that adds a new filter and a property panel that goes with that filter. Such plugins need to be loaded both on the server as well as the client.

Generally, users don't have to worry whether a plugin is a server-side or client-side plugin. Simply load the plugin on the server as well as the client. ParaView will include relevant components from plugin on each of the processes.

There are two ways for loading plugins:

  • Using the GUI (Plugin Manager)
Plugins can be loaded into ParaView using the Plugin Manager accessible from Tools | Manage Plugins/Extensions menu. The Plugin Manager has two sections for loading local plugins and remote plugins (enabled only when connected to a server). To load a plugin on the local as well as remote side, simply browse to the plugin shared library. If the loading is successful, the plugin will appear in the list of loaded plugins. The Plugin manager also lists the paths it searched to load plugins automatically.
Figure 1: Plugin Manager when not connected to a remote server, showing loaded plugins on the local site.
Figure 2: Plugin Manager when connected to a server showing loaded plugins on the local as well as remote sites.
  • Using environment variable (Auto-loading plugins)
Plugins loaded using the Plugin Manager are not saved across sessions. Hence one has to reload those plugins every time ParaView is started. If one wants ParaView to automatically load a set of plugins on startup, one can use the PV_PLUGIN_PATH environment variable. PV_PLUGIN_PATH can be used to list a set of directories (separated by colon (:) or semi-colon (;)) which ParaView will search on startup to load plugins. This enviromnent variable needs to be set on both the client node to load local plugins as well as the remote server to load remote plugins.

Writing Plugins

This section covers writing and compiling different types of Plugins. To create a plugin, one must have their own build of ParaVeiw3. Binaries downloaded from www.paraview.org do not include necessary header files or import libraries (where applicable) for compiling plugins.

The beginning of a CMakeLists.txt file contains

FIND_PACKAGE(ParaView REQUIRED)
INCLUDE(${PARAVIEW_USE_FILE})

Where CMake will ask for the ParaView_DIR which you point to your ParaView build. The PARAVIEW_USE_FILE includes build parameters and macros for building plugins.

Adding a Filter

In this plugin, we want to add a new filter ParaView. The filter has to be a VTK-based algorithm, written as following the standard procedures for writing VTK algorithms. Generally for such cases where we are adding a new VTK class to ParaView (be it a filter, reader or a writer), we need to do the following tasks:

  • Write a Server Manager Configuration XML which describes the Proxy interface for the new VTK class. Basically, this defines the interface for the client to create and modify instances of the new class on the server side. Please refer to the ParaView Guide for details about writing these server-manager xmls.
  • Write a configuration XML for the GUI to make ParaView GUI aware of this new class, if applicable. For filters, this is optional, since ParaView automatically recognizes filters added through plugins and lists them in the Alphabetical sub-menu. One may use the GUI configuration xml to add the new filter to a specific category in the Filters menu, or add a new category etc. For readers and writers, this is required since ParaView GUI needs to know what extensions your reader/writer supports etc.

For this example, refer to Examples/Plugins/Filter in the ParaView source. Let's say we have written a new vtkMyElevationFilter (vtkMyElevationFilter.h|cxx), which extends the functionality of the vtkElevationFilter and we want to package that as a plugin for ParaView. For starters, we simply want to use this filter in ParaView (not doing anything fancy with Filters menu categories etc.). As described, we need to write the server manager configuration XML (MyElevationFilter.xml). Once that's done, we write a CMakeLists.txt file to package this into a plugin.

This CMakeLists.txt simply needs to include the following lines:

 # Locate ParaView build and then import CMake configuration, 
 # macros etc. from it.
 FIND_PACKAGE(ParaView REQUIRED)
 INCLUDE(${PARAVIEW_USE_FILE})
 
 # Use the ADD_PARAVIEW_PLUGIN macro to build a plugin
 ADD_PARAVIEW_PLUGIN(
   MyElevation                 #<--Name for the plugin
  "1.0"                        #<--Version string
   SERVER_MANAGER_XML MyElevationFilter.xml  #<-- server manager xml
   SERVER_MANAGER_SOURCES vtkMyElevationFilter.cxx #<-- source files for the new classes
 )

Then using cmake and a build system, one can build a plugin for this new filter. Once this plugin is loaded the filter will appear under the "Alphabetical" list in the Filters menu.

Adding Categories to the Filters Menu

Now suppose we want to add a new category to the Filters menu, called "Extensions" and then show this filter in that submenu. In that case, we'll need a GUI configuration xml to tell the ParaView GUI to create the category. This GUI configuration xml will look as such:

  <ParaViewFilters>
    <Category name="Extensions" menu_label="&amp;Extensions">
      <!-- adds a new category and then adds our filter to it -->
      <Filter name="MyElevationFilter" />
    </Category>
  </ParaViewFilters>

If the name of the category is same as an already existsing category eg. Data Analysis, then the filter gets added to the existing category.

The CMakeLists.txt must change to include this new xml (let's call it MyElevationGUI.xml) as follows:

 FIND_PACKAGE(ParaView REQUIRED)
 INCLUDE(${PARAVIEW_USE_FILE})
 
 ADD_PARAVIEW_PLUGIN(MyElevation "1.0"
   SERVER_MANAGER_XML MyElevationFilter.xml 
   SERVER_MANAGER_SOURCES vtkMyElevationFilter.cxx
   GUI_RESOURCE_FILES MyElevationGUI.xml)

Adding Icons

You can see that some filters in the Filters menu (eg. Clip) have icons associated with them. It's possible for the plugin to add icons for filters it adds as well. For that you need to write a Qt resource file (say MyElevation.qrc) as follows:

  <RCC>
    <qresource prefix="/MyIcons" >
      <file>MyElevationIcon.png</file>
    </qresource>
  </RCC>

The GUI configuration xml now refers to the icon provided by this resource as follows:

  <ParaViewFilters>
    <Category name="Extensions" menu_label="&amp;Extensions">
      <!-- adds a new category and then adds our filter to it -->
      <Filter name="MyElevationFilter" icon=":/MyIcons/MyElevationIcon.png" />
    </Category>
  </ParaViewFilters>

Finally, the CMakeLists.txt file much change to include our MyElevation.qrc file as follows:

FIND_PACKAGE(ParaView REQUIRED)
INCLUDE(${PARAVIEW_USE_FILE})

ADD_PARAVIEW_PLUGIN(MyElevation "1.0"
  SERVER_MANAGER_XML MyElevationFilter.xml 
  SERVER_MANAGER_SOURCES vtkMyElevationFilter.cxx
  GUI_RESOURCES MyElevation.qrc
  GUI_RESOURCE_FILES MyElevationGUI.xml)

Enabling a filter in VTK

Sometimes, the filter that one wants to add to ParaView is already available in VTK, it's just not exposed through the ParaView GUI. For such filters too, one can create a plugin as with a new filter. In this case too we need the server manager configuration xml for the filter describing it's API and the optional GUI xml to add the filter to any specific category.

For example, let's say we simply want to expose the vtkCellDerivatives in VTK. Then first, we'll write the server manager configuration XML (call it vtkCellDerivatives.xml), similar to what we would have done for adding a new filter. Please refer to the ParaView Guide for details about writing this XML.

Now we can compile this into a plugin using the following CMakeLists.txt

FIND_PACKAGE(ParaView REQUIRED)
INCLUDE(${PARAVIEW_USE_FILE})

ADD_PARAVIEW_PLUGIN(CellDerivatives "1.0"
  SERVER_MANAGER_XML CellDerivatives.xml)

However, this would require ParaView built from source (as described earlier). Alternatively, we can simply load the XML from the Plugins Manager (just select the file type to be *.xml in the file open dialog).

Similarly compiled Qt resources (*.bqrc) can be loaded at runtime. *.bqrc is a binary file containing resources which can include icons, the GUI configuration xmls for adding catergories etc. A .bqrc can be made from a .qrc by running the rcc utility provided by Qt:

 rcc -binary -o myfile.bqrc myfile.qrc.

Adding a Reader

Adding a new reader to a plugin is similar to adding a filter except that instead of the GUI configuration xml describing categories in the filter menu, we require the xml to define what file extensions this reader can handle. This xml (MyReaderGUI.xml) looks like this:

  <ParaViewReaders>
    <Reader name="MyPNGReader" extensions="mypng"
            file_description="My PNG Files">
    </Reader>
  </ParaViewReaders>

And the CMakeLists.txt looks as follows where vtkMyPNGReader.cxx is the source for the reader, MyPNGReader.xml is the server manager configuration xml, and MyReaderGUI.xml is the GUI configuration xml described above:

 FIND_PACKAGE(ParaView REQUIRED)
 INCLUDE(${PARAVIEW_USE_FILE})
 ADD_PARAVIEW_PLUGIN(MyReader "1.0" 
   SERVER_MANAGER_XML MyPNGReader.xml
   SERVER_MANAGER_SOURCES vtkMyPNGReader.cxx 
   GUI_RESOURCE_FILES MyReaderGUI.xml)

If you want your reader to work correctly with a file series, please refer to file series animation for details.

Adding a Writer

Similar to a reader, for a writer we need to tell ParaView what extensions this writer supports. This can be done using the GUI XML as follows:

  <ParaViewWriters>
    <Writer name="MyTIFFWriter"
            extensions="tif"
            file_description="My Tiff Files">
    </Writer>
  </ParaViewWriters>

Adding a Toolbar

Filters, reader and writers are by far the most common ways for extending ParaView. However, ParaView plugin functionality goes far beyond that. The following sections cover some of these advanced plugins that can be written.

Applications use toolbars to provide easy access to commonly used functionality. It is possible to have plugins that add new toolbars to ParaView. The plugin developer implements his own C++ code to handle the callback for each button on the toolbar. Hence one can do virtually any operation using the toolbar plugin with some understanding of the ParaView Server Manager framework and the ParaView GUI components.

Please refer to Examples/Plugins/SourceToolbar for this section. There we are adding a toolbar with two buttons to create a sphere and a cylinder source. For adding a toolbar, one needs to implement a subclass for QActionGroup which adds the QActions for each of the toolbar button and then implements the handler for the callback when the user clicks any of the buttons. In the example SourceToobarActions.h|cxx is the QActionGroup subclass that adds the two tool buttons.

To build the plugin, the CMakeLists.txt file is:

 # We need to wrap for Qt stuff such as signals/slots etc. to work correctly.
 QT4_WRAP_CPP(MOC_SRCS SourceToolbarActions.h)
 
 # This is a macro for adding QActionGroup subclasses automatically as toolbars.
 ADD_PARAVIEW_ACTION_GROUP(IFACES IFACE_SRCS 
                         CLASS_NAME SourceToolbarActions
                         GROUP_NAME "ToolBar/SourceToolbar")
 
 # Now create a plugin for the toolbar. Here we pass IFACES and IFACE_SRCS
 # which are filled up by the above macro with relevant entries
 ADD_PARAVIEW_PLUGIN(SourceToolbar "1.0"
                   GUI_INTERFACES ${IFACES}
                   SOURCES ${MOC_SRCS} ${IFACE_SRCS} 
                   SourceToolbarActions.cxx)

For the GROUP_NAME, we are using ToolBar/SourceToolbar; here ToolBar is a keyword which implies that the action group is a toolbar (and shows up under View | Toolbars menu) with the name SourceToolbar. When the plugin is loaded, this toolbar will show up with two buttons.

Adding an object panel

Object Panels are the panels for editing object properties.

ParaView3 contains automatic panel generation code which is suitable for most objects. If you find your object doesn't have a good auto-generated panel, you can make your own.

To make your own, there is an explanation found on CustomObjectPanels

Now let's say we have our own panel we want to make for a ConeSource. In this example, we'll just add a simple label saying that this panel came from the plugin. In ConePanel.h:

 #include "pqAutoGeneratedObjectPanel.h"
 #include <QLabel>
 #include <QLayout>
 
 class ConePanel : public pqAutoGeneratedObjectPanel
 {
   Q_OBJECT
 public:
   ConePanel(pqProxy* pxy, QWidget* p)
     : pqAutoGeneratedObjectPanel(pxy, p)
   {
     this->layout()->addWidget(new QLabel("This is from a plugin", this));
   }
 };

Then in our CMakeLists.txt file:

FIND_PACKAGE(ParaView REQUIRED)
INCLUDE(${PARAVIEW_USE_FILE})
QT4_WRAP_CPP(MOC_SRCS ConePanel.h)
ADD_PARAVIEW_OBJECT_PANEL(IFACES IFACE_SRCS 
                         CLASS_NAME ConePanel
                         XML_NAME ConeSource XML_GROUP sources)
ADD_PARAVIEW_PLUGIN(GUIConePanel "1.0"
                    GUI_INTERFACES ${IFACES}
                    SOURCES ${MOC_SRCS} ${IFACE_SRCS})

Adding components to Display Panel (decorating display panels)

Display panel is the panel shown on the Display tab in the Object Inspector. It is possible to add GUI components to existing display panels.

In this example we want to add a GUI element to the display panel shown for the spread sheet view to size of data that is fetched to the client at one time referred to as the Block Size.

For that we write the implementation in QObject subclass (say MySpreadsheetDecorator) with a constructor that takes in the pqDisplayPanel which is to be decorated.

...
class MySpreadsheetDecorator : public QObject
{
...
public:
  MySpreadsheetDecorator(pqDisplayPanel* panel);
  virtual ~MySpreadsheetDecorator();
...
};

In the constructor, we have access to the panel, hence we can get the layout from it and add custom widgets to it. In this case, it would be a spin-box or a line edit to enter the block size. pqDisplayPanel::getRepresentation() provides access to the representation being shown on the panel. We can use pqPropertyLinks to link the "BlockSize" property on the representation with the spin-box for the block size so that when the widget is changed by the user, the property changes and vice-versa.

Now the CMakeLists.txt to package this plugin looks like follows:

 QT4_WRAP_CPP(MOC_SRCS MySpreadsheetDecorator.h)
 
 # This is the macro to add a display panel decorator.
 # It needs the class name, and the panel types we are decorating. It fills up 
 # IFACES and IFACE_SRCS with proper values as needed by ADD_PARAVIEW_PLUGIN macro.
 ADD_PARAVIEW_DISPLAY_PANEL_DECORATOR(
   IFACES IFACE_SRCS 
   CLASS_NAME MySpreadsheetDecorator
   PANEL_TYPES pqSpreadSheetDisplayEditor 
       # <-- This identifies the panel type(s) to decorate
       # Our decorator will only be instantiated for the panel types indicated here
 )
 
 # create a plugin
 ADD_PARAVIEW_PLUGIN(MySpreadsheetDecorator "1.0" 
   GUI_INTERFACES ${IFACES} 
   SOURCES MySpreadsheetDecorator.cxx ${MOC_SRCS} ${IFACE_SRCS})

An example panel decorator is available under Examples/Plugins/DisplayPanelDecorator in the ParaView source.

Autostart Plugins

This refers to a plugin which needs to be notified when ParaView starts up or the plugin is loaded which ever happens later and then notified when ParaView quits. Example is in Examples/Plugins/Autostart in the ParaView source. For such a plugin, we need to provide a QObject subclass (pqMyApplicationStarter) with methods that need to be called on startup and shutdown.

...
class pqMyApplicationStarter : public QObject
{
...
public:
  // Callback for startup.
  // This cannot take any arguments
  void onStartup();

  // Callback for shutdown.
  // This cannot take any arguments
  void onShutdown();
...
};

The CMakeLists.txt looks as follows:

 QT4_WRAP_CPP(MOC_SRCS pqMyApplicationStarter.h)
 
 # Macro for auto-start plugins. We specify the class name
 # and the methods to call on startup and shutdown on an instance of that class.
 # It fills IFACES and IFACE_SRCS with proper values as needed by ADD_PARAVIEW_PLUGIN macro.
 ADD_PARAVIEW_AUTO_START(IFACES IFACE_SRCS 
   CLASS_NAME pqMyApplicationStarter # the class name for our class
   STARTUP onStartup   # specify the method to call on startup
   SHUTDOWN onShutdown # specify the method to call on shutdown
 )
 
 # Create a plugin for this starter 
 ADD_PARAVIEW_PLUGIN(Autostart "1.0" 
   GUI_INTERFACES ${IFACES} 
   SOURCES pqMyApplicationStarter.cxx ${MOC_SRCS} ${IFACE_SRCS})

Adding a custom view

ParaView contains a render view for rendering 3d images. It also contains chart views to visualize data in line charts and histogram charts. You may want to create another custom view that does your own view of the data.

For this example, we'll just make a simple Qt widget with labels showing the displays that have been added to the view.

To make a custom view, we need both client and server side plugins.

For our server side, we simply have:

 <ServerManagerConfiguration>
  <ProxyGroup name="displays">
   <GenericViewDisplayProxy name="MyDisplay"
     base_proxygroup="displays" base_proxyname="GenericViewDisplay">
   </GenericViewDisplayProxy>
  </ProxyGroup>
  <ProxyGroup name="views">
   <ViewModuleProxy name="MyViewViewModule"
     base_proxygroup="rendermodules" base_proxyname="ViewModule"
     display_name="MyDisplay">
   </ViewModuleProxy>
  </ProxyGroup>
  <ProxyGroup name="filters">
   <SourceProxy name="MyExtractEdges" class="vtkExtractEdges"
     label="My Extract Edges">
     <InputProperty
       name="Input"
       command="SetInputConnection">
         <ProxyGroupDomain name="groups">
           <Group name="sources"/>
           <Group name="filters"/>
         </ProxyGroupDomain>
         <DataTypeDomain name="input_type">
           <DataType value="vtkDataSet"/>
         </DataTypeDomain>
     </InputProperty>
     <Hints>
       <View type="MyView"/>
     </Hints>
   </SourceProxy>
  </ProxyGroup>
 </ServerManagerConfiguration>

We define "MyDisplay" as a simple display proxy, and "MyViewModule" as a simple view module. We have our own filter "MyExtractEdges" with a hint saying it prefers to be shown in a view of type "MyView." So if we create a MyExtractEdges in ParaView3, it'll automatically be shown in our custom view.

We build the server plugin with a CMakeLists.txt file as:

FIND_PACKAGE(ParaView REQUIRED)
INCLUDE(${PARAVIEW_USE_FILE})
ADD_PARAVIEW_PLUGIN(SMMyView "1.0" SERVER_MANAGER_XML MyViewSM.xml)


Our client side plugin will contain an extension of pqGenericViewModule. We can let ParaView give us a display panel for these displays, or we can make our own deriving from pqDisplayPanel. In this example, we'll make a simple display panel.

We implement MyView in MyView.h:

 #include "pqGenericViewModule.h"
 #include <QMap>
 #include <QLabel>
 #include <QVBoxLayout>
 #include <vtkSMProxy.h>
 #include <pqDisplay.h>
 #include <pqServer.h>
 #include <pqPipelineSource.h>
 
 /// a simple view that shows a QLabel with the display's name in the view
 class MyView : public pqGenericViewModule
 {
  Q_OBJECT
 public:
  MyView(const QString& viewtypemodule, const QString& group, const QString& name,
         vtkSMAbstractViewModuleProxy* viewmodule, pqServer* server, QObject* p)
     : pqGenericViewModule(viewtypemodule, group, name, viewmodule, server, p)
    {
    this->MyWidget = new QWidget;
    new QVBoxLayout(this->MyWidget);
 
    // connect to display creation so we can show them in our view
    this->connect(this, SIGNAL(displayAdded(pqDisplay*)),
      SLOT(onDisplayAdded(pqDisplay*)));
    this->connect(this, SIGNAL(displayRemoved(pqDisplay*)),
      SLOT(onDisplayRemoved(pqDisplay*)));
 
    }
  ~MyView()
    {
    delete this->MyWidget;
    }
 
  /// we don't support save images
  bool saveImage(int, int, const QString& ) { return false; }
  vtkImageData* captureImage(int) { return NULL; }
 
  /// return the QWidget to give to ParaView's view manager
  QWidget* getWidget()
    {
    return this->MyWidget;
    }
  /// returns whether this view can display the given source
  bool canDisplaySource(pqPipelineSource* source) const
    {
    if(!source ||
       this->getServer()->GetConnectionID() != source->getServer()->GetConnectionID() ||
       QString("MyExtractEdges") != source->getProxy()->GetXMLName())
      {
      return false;
      }
    return true;
    }
 
 protected slots:
  void onDisplayAdded(pqDisplay* d)
    {
    QString text = QString("Display (%1)").arg(d->getProxy()->GetSelfIDAsString());
    QLabel* label = new QLabel(text, this->MyWidget);
    this->MyWidget->layout()->addWidget(label);
    this->Labels.insert(d, label);
    }
 
  void onDisplayRemoved(pqDisplay* d)
    {
    QLabel* label = this->Labels.take(d);
    if(label)
      {
      this->MyWidget->layout()->removeWidget(label);
      delete label;
      }
    }
 
 protected:
 
  QWidget* MyWidget;
  QMap<pqDisplay*, QLabel*> Labels;
 
 };

And MyDisplay.h is:

 #include "pqDisplayPanel.h"
 #include <QVBoxLayout>
 #include <QLabel>
 
 class MyDisplay : public pqDisplayPanel
 {
   Q_OBJECT
 public:
   MyDisplay(pqDisplay* display, QWidget* p)
     : pqDisplayPanel(display, p)
     {
     QVBoxLayout* l = new QVBoxLayout(this);
     l->addWidget(new QLabel("From Plugin", this));
     }
 };

The CMakeLists.txt file to build the client plugin would be:

FIND_PACKAGE(ParaView REQUIRED)
INCLUDE(${PARAVIEW_USE_FILE})

QT4_WRAP_CPP(MOC_SRCS MyView.h MyDisplay.h)
 
ADD_PARAVIEW_VIEW_MODULE(IFACES IFACE_SRCS VIEW_TYPE MyView VIEW_XML_GROUP views
                         DISPLAY_XML MyDisplay DISPLAY_PANEL MyDisplay)

ADD_PARAVIEW_PLUGIN(GUIMyView "1.0" GUI_INTERFACES ${IFACES}
                    SOURCES ${MOC_SRCS} ${IFACE_SRCS})

We load the plugins in ParaView, and we create something like a Cone, then create a "My Extract Edges" filter. The multiview manager will create a new view and the label "Display (151)".

In ParaView 3.4, there's also a macro, ADD_PARAVIEW_VIEW_OPTIONS() which allows adding options pages for the custom view, accessible from Edit -> View Settings. The example in ParaView3/Examples/Plugins/GUIView demonstrates this (until more information is put here).

Examples

The ParaView CVS repository contains many examples in the Plugins directory. Additional examples are available on this wiki at the Plugin Examples entry.



ParaView: [Welcome | Site Map]