How to Create A New Module
for SCIRun
(UNIX version, updated June 6, 2000)
Table of contents
This document assumes that you have programming experience, understand what a SCIRun module is and know how to connect multiple modules together to solve a problem, and know how to manipulate a UNIX file system.
Before you can effectively create and use a new SCIRun module, you must first have a working knowledge of what a module is on both a high and a low level.
From the most abstract level, a module is a single algorithm with inputs and outputs. On the nuts-and-bolts level it is bunch of files and directories somewhere in your file system. The key to effectively creating and using a module is knowing how those two levels relate to each other.
The rest of this document will be organized in a high-level vs. low-level manner in an attempt to leverage your abstract knowledge of what a module is, so that you can then understand modules from the lowest level. Once you understand the nuts and bolts of a module, you will be able to easily create your own!
Having used SCIRun and connected modules together, you know that modules are organized in a hierarchy: At the lowest level is a single module; Modules that have some arbitrary relation to each other (e.g. they have the same input data type, or they read data from disk) can be grouped together into a category. Categories that are also some how related can be grouped into a package (Modules cannot exist outside of a category or outside of a package. So, if you want to create a module, you must also choose, or create, a category and package for the module.)
The module hierarchy is embodied in SCIRun as a series of pull down menus at the top of the SCIRun network canvas. Each menu item after "File" is a SCIRun package. If you click on the BioPSE package, a drop-down list of the categories that exist within that package is shown. If you then select one of the categories, another list is shown which represents the modules in that category.
So, on a high level, modules are organized in a tree structure. We can illustrate this using the BioPSE package as an example:
BioPSE --- Example -- GeomPortTest
|
|- FEM ------ ApplyBC
| |- BuildFEMatrix
| |- ...
|
|- ...
This hierarchy also exists beyond the menu structure in the PSE. The modules are organized in a similar way inside the file structure in your PSE source tree:
SCIRun -- src -- BioPSE -- Modules -- Example -- GeomPortTest.cc
|
|- FEM ------ ApplyBC.cc
| |- BuildFEMatrix.cc
| |- ...
|
|- ...
So, if you created a new module and inserted it into the BioPSE package (and into the Example category), the new trees would look like this:
BioPSE --- Example -- GeomPortTest
| |- YOUR_NEW_MODULE
|
|- FEM ------ ApplyBC
| |- BuildFEMatrix
| |- ...
|
|- ...
SCIRun -- src -- BioPSE -- Modules -- Example -- GeomPortTest.cc
| |- YOUR_NEW_MODULE.cc
|
|- FEM ------ ApplyBC.cc
| |- BuildFEMatrix.cc
| |- ...
|
|- ...
You can see that the abstract notions of package, category and module directly correspond to a particular type of file: A package is a directory within the SCIRun/src tree, a category is a directory beneath a package directory and a single module is a single .cc file (In the SCIRun .cc files are C++ files) inside a category directory.
So, is making a module as easy as making a couple of directories and writing some C++ code? Yes and no. No, because there are other files in the BioPSE tree, Not just directories and C++ files. Yes, because creating this tree structure (along with those other files) can be done with the click of a button via SCIRun.
The next sections will describe, in more detail, how to create all the files needed to make a package directory.
Creating a hierarchy automatically
Since you're probably like most other humans, you're too lazy to read through the rest of this document, and just want to get on with making a new module. Well, you're in luck. There is a way to create modules, and the associated hierarchy of files, automatically. All you need to use these facilities are three names: package, category and module.
From within the "File" menu at the top of the SCIRun network canvas, select "New" and then "Module". A dialog will pop up asking for the three names mentioned above. Once you click the "Ok" button, SCIRun generates all the files, with the correct hierarchy, that are necessary. It may also modify existing files if you insert into an existing hierarchy.
There are three possibilities for choosing names: 1)new package and new category, 2)existing package and new category, and 3)existing package and existing category.
If you type in a package name or category name that already exists, the module will be inserted into the proper hierarchy. If you type names that do not already exist, then a new (whole or partial) hierarchy will be created and/or inserted.
It is illegal to choose names that are blank or are names of already existing files that are not part of valid SCIRun hierarchies. It is also illegal to have two modules with the same name inside the same category. You may have modules with the same name so long as they are in different categories. You may also have categories with the same name so long as they are in different packages.
Once you've created a new module, you will see a dialog that tells you that you must rebuild SCIRun before you can use it. In some cases you must first re-configure and then rebuild. To rebuild SCIRun, you should simply run "gmake" from the SCIRun/src directory. There is no need to "gmake clean" first. To re-configure, run:
configure --enable-package="OLD-PACKAGES NEW-PACKAGE"
Where OLD-PACKAGES are the packages that you already had enabled, and NEW-PACKAGE is the package you just created.
If you've attempted to create a new module with this facility and it returned an error or warning, see the trouble shooting section below.
Ah. You're not lazy. Wanting to know the nuts and bolts of a system is the sign of a truly great programmer. Here is the full directory structure for a SCIRun package:
package -- Modules ------ category1 -- module1.cc
| | |- module2.cc
| | |- ...
| | |- sub.mk
| |
| |- category2 ...
| |- ...
| |- sub.mk
|
|- GUI ---------- moduleA.tcl
| |- moduleB.tcl
| |- ...
| |- tclIndex
| |- sub.mk
|
|- share -------- share.h
| |- DllEntry.cc
|
|- Datatypes ---- group1 -- datatype1.cc
| | |- datatype1.h
| | |- datatype2.cc
| | |- ...
| | |- sub.mk
| |
| |- group2 ...
| |- ...
| |- sub.mk
|
|- ThirdParty ...
|
|- components.xml
|- sub.mk
Whew! So there are 5 directories (Modules, GUI, share, Datatypes and ThirdParty) inside a package, and there are two non-directory files inside a package (components.xml and sub.mk). It is possible for a package to have superfluous directories and files without affecting the integrity of a package. You should, however, not remove any of the listed files, even if they are empty, as future versions of the SCIRun may require them.
You are already familiar with the "Modules" directory which houses the category directories, and subsequently the modules themselves.
The "GUI" directory holds all the user interface files. Currently, all the user interfaces in SCIRun are written in tcl. The tcl files should all have a certain structure in order to work with SCIRun. You can see examples of the structure in the appendix. New modules have no user interface file by default.
The "share" directory is a mechanism for remaining platform independent. It holds information needed by windows machines for building .dll files. Although the share directory is not strictly needed for UNIX platforms, it does make life easier when porting your modules to windows.
The "Datatypes" directory holds information about new SCIRun data types that you define. See "How to Create New Datatypes for SCIRun" for more details on this part of the hierarchy.
The "ThirdParty" directory is an empty directory that may be used to store third party sources or libraries that your new modules may want to link with. You can safely ignore this directory, if you have no need for third party code.
The "components.xml" file is a file that contains information about the organization and characteristics (input/output types, author, date created, etc.) of the modules in the package. See the appendix for examples of a components.xml file.
The "sub.mk" files that are scattered throughout the hierarchy are used to create makefiles for each directory. They include information such as sub directories, files to include in the build, and libraries to be linked against. The appendix has skeleton files for each type of sub.mk in the tree.
If you choose to generate or edit these files by hand rather than using the automatic module maker, you will want to consider conforming to file formats that the module maker uses, so that others can still use the module maker with these files after you're done with them.
Each of the sub.mk and components.xml files have a format that the module maker uses. This format allows the module maker to properly go through each file and insert paragraphs or lines.
The first element of this format is a comment line of the form:
#[INSERT NEW ????? HERE] for sub.mk files and <!-- INSERT NEW ??????? HERE --> for components.xml files.
The second element of this format is the context in which these lines are set.
If either element of the format is disturbed, the module maker will no longer be able to properly do it's job, and may even break files.
Here is an example of modifying a sub.mk file within a category directory to add a module:
before:
SRCS += \
$(SRCDIR)/ColorMapKey.cc\
$(SRCDIR)/CuttingPlane.cc\
$(SRCDIR)/GenAxes.cc\
#[INSERT NEW MODULE HERE]
after:
SRCS += \
$(SRCDIR)/ColorMapKey.cc\
$(SRCDIR)/CuttingPlane.cc\
$(SRCDIR)/GenAxes.cc\
$(SRCDIR)/NEW_MODULE_HERE.cc\
#[INSERT NEW MODULE HERE]
One important thing to note here is that the new module was inserted above the line where the [INSERT NEW MODULE HERE] comment line existed. It is also important to note that the end of the newly inserted line includes a back slash as the last character.
The same format rules apply to sub.mk files within a packages "Modules" directory.
Modification of the components.xml file is similar:
before:
<dataflow-component name="MeshReader">
<meta>
<authors>
<author>Steven G. Parker</author>
</authors>
<version-date>July 1994</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_MeshReader</creationFunction>
</implementation>
</dataflow-component>
<!-- INSERT NEW Readers COMPONENT HERE -->
after:
<dataflow-component name="MeshReader">
<meta>
<authors>
<author>Steven G. Parker</author>
</authors>
<version-date>July 1994</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_MeshReader</creationFunction>
</implementation>
</dataflow-component>
<dataflow-component name="MatrixReader">
<meta>
<authors>
<author>Steven G. Parker</author>
</authors>
<version-date>July 1994</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_MatrixReader</creationFunction>
</implementation>
</dataflow-component>
<!-- INSERT NEW Readers COMPONENT HERE -->
If you want to add a user interface element to your module, you will have to know tcl, and be familiar with the communication scheme in SCIRun which allows you to import and export information from the tcl code into your module code. Both these topics are beyond the scope of this document.
You will also have to create a .tcl file and place it into the GUI directory. Please see the appendix for a skeleton .tcl file for a SCIRun module user interface.
Adding ports and functionality
Once you have created your new module, and created or chosen a package for it, you must give it functionality. To function properly, each module needs to do three things: Get data, operate on the data, generate result data.
The first step is to decide how your module will get it's input data, and what it will do with the output data that it generates. There are two methods of getting data into a module (input ports and read from disk) and two methods for outputting data (output ports and write to disk).
Modules that read data from disk, or write data to disk are considered, in SCIRun, to be "readers" or "writers" respectively. These types of modules are very closely tied to specific SCIRun data types. Their use and convention is discussed in the "How to Create New Data Types for SCIRun" document and will not be discussed here.
The other type of module is the kind that takes data into an input port, runs some algorithm on the data, and then sends output data to the output port. This is the most common type of module, and perhaps the most useful. In order to create such a module, you will need to add lines of code to the module's .cc file.
You will need to identify the SCIRun data type that you will use for each port, input and output. Then add #include statements to the top of the file for the header files that define the data type(s) you want. For example, suppose you want to have a Field input port and a Field output port. You would add the following lines to the top of your module's .cc file:
#include <Dataflow/Ports/FieldPort.h>
The data types can come from any package or core directory within your SCIRun source tree (i.e. they don't have to come from src/Dataflow).
When a module begins it's execution cycle, it calls the execute() member function of your module. It is in this function that you need to add lines of code to extract data from the input ports, run your module's algorithm on that data, and send the result to the output port:
void
MyModule::execute()
{
FieldIPort* ifieldport = (FieldIPort *)get_iport("Input Field");
FieldHandle fieldin;
if (!ifieldport) {
error( "Unable to initialize iport 'Input Field'.");
return;
}
if (!(ifieldport->get(fieldin) && fieldin.get_rep())) {
error( "No handle or representation." );
return;
}
if( is_it_necessary_to_execute ) {
// See the next section on how to determine is_it_necessary_to_execute
fieldout_ = do_something_based_on_the_input_field( fieldin );
}
// If output data get the port and send it down stream.
if ( fieldout_.get_rep() ) {
// Get a handle to the output field port.
FieldOPort* ofp = (FieldOPort *) get_oport("Output VectorMagnitude");
if (!ofp) {
error("Unable to initialize oport 'Output Field'.");
return;
}
// Send the data downstream
ofp->send(fieldout_);
}
After creating a module you need to make it work. There are two areas that deserve attention:
GUI variable interface,
Excute only when needed,
GUI variable interface
The GUI variable interface consists of two parts: the declaration of the variable on the Tcl/Tk side and the declaration on the C++ side. When declaring a variable that would be used for user input on the Tcl/Tk side the variable must be declared as "global" variable and use the module's name as a prefix. This is accomplished by using "$this" as a prefix. Putting this all together would yield:
method set_defaults {} {
global $this-myvariable
...
which would be declared in the Tcl/Tk method set_defaults. This would be repeated for all variables that would be used for user input within the GUI. When matched with their counter part declaration on the C++ side each variable will be saved in the .net file. Variables that do not need to be saved do not
The matching C++ variable decalaration has two parts; declaration in the class definition and its initalization in the class constructor. There are three types of Gui variables; strings, doubles, and ints. There types are:
GuiString GuiDouble GuiInt
respectively. Each variable declared must be have the prefix "gui_" and end with "_" with the varaible name in between. Further, the name should be exactly the same as the Tcl/Tk name. Putting this all together would yield:
class MyModule : public Module
{
...
protected:
GuiString gui_myvariable_;
The variable would be initialized in the definition of the constrcutor of the containing class:
MyModule::MyModule(GuiContext* ctx)
: Module("MyModule", ... ),
gui_myvariable_(ctx->subVar("myvariable", false))
Where the value in quotes, e.g. "myvariable" would exactly match the variable name of the Tcl/tk side but without the "$this-" prefix.
The next C++ declaration is another member variable that wil be used to hold the state of the gui variable. This would be either a std::string, double, or int. Again it would use the exact same name as the gui variable but without the "gui_" prefix. This would yield:
std::string myvariable_;
If needed, the variable would also be initialized in the definition of the constrcutor of the containing class. The inital vallue should a value that would never be used in the normal course of ececuting the module such as, "-1".
This completes the needed Tcl/Tk GUI variable declarations.
Excute only when needed
The next part of constructing a module is to determine whether it really needs to do anything when the execute method is called. Often a module will execute due to an upstream or downstream module executing, or because the user forces the module to execute. However, if the incoming data and the gui variables have not changed then there is no need for the module to do any work other than to resend the data.
Assume the new module has one input and one output port each containing a field. The first step would be to declare two member variables within the class that will be used as state variables:
class MyModule : public Module
{
...
protected:
GuiString gui_myvariable_;
FieldHandle fieldout_;
int fGeneration_;
The "fieldout_" member variable would hold the resulting output field handle while the "fGeneration_" member variable would hold the generation number of the input field. Each would be initalize to be 0 and -1 respectively. This would make the constructor be:
MyModule::MyModule(GuiContext* ctx)
: Module("MyModule", ... ),
gui_myvariable_(ctx->subVar("myvariable", false)),
myvariable_(-1),
fieldout_(0),
fGeneration_(-1)
For each output handle there needs to be a member variable to hold its handle. At the same for each input handle there needs to be a member variable to hold its generation number.
At this point code can now be added that checks the state of the output field, gui variable, and input field. If the output field does exist, which it will not initally, or if the gui variable has changed, or if the input field's generation has changed then operate on the input field.
void
MyModule::execute()
{
FieldIPort* ifieldport = (FieldIPort *)get_iport("Input Field");
FieldHandle fieldin;
if (!ifieldport) {
error( "Unable to initialize iport 'Input Field'.");
return;
}
if (!(ifieldport->get(fieldin) && fieldin.get_rep())) {
error( "No handle or representation." );
return;
}
// If no output data, a change in the gui variable or a changed in the
// input field generation number then do something.
if( !fieldout_.get_rep() ||
gui_myvariable_.get() != myvaraible_ ||
fGeneration_ != fieldin->generation )
{
// Save the state of the gui variable and the input field.
myvariable_ = gui_myvariable_.get();
fGeneration_ = fieldin->generation;
fieldout_ = do_something_based_on_the_input_field( myvariable_, fieldin );
}
// If output data get the port and send it down stream.
if ( fieldout_.get_rep() ) {
// Get a handle to the output field port.
FieldOPort* ofp = (FieldOPort *) get_oport("Output VectorMagnitude");
if (!ofp) {
error("Unable to initialize oport 'Output Field'.");
return;
}
// Send the data downstream
ofp->send(fieldout_);
}
Looking at this code shows that it will operate on the input field only when necessary. Further, if nothing needs to be done the previous output field handle which has been saved will be sent downstream.
Final part of building a module is to allow it to operate on a variety of field types. These types include scalar, vector, and tensor. This can be done using Dynamic Compilation which is explained in a later chapter.
What you're still reading? You know, the more you read, the more I have to write - arrgh!?!? Alright smarty pants, you asked for it...
Keen readers will wonder how a package _really_ works. After all, a bunch of source code and fancy schmancy directory trees do not an application make! Good eye.
There is a hidden aspect of the SCIRun package that hasn't been discussed yet: what does a package's source files get compiled into, and how does this object, or objects, interface with SCIRun?
When a package is built, the end result has a couple of possibilities: 1) a single, large, shared object library, or 2) a handful of small shared object libraries. You decide at configure time which kind of libraries you want (--enable-largesos for large libraries, nothing for small libraries).
The libraries that contain modules must all export a function of the following form:
Module* make_MODULE-NAME()
for each module that is inside it.
So, in reality, any .so library that has Module-derived classes defined within it, and also exports these "make" functions, could, in a way, be considered a SCIRun package - regardless of whether or not it came from the hierarchy described above or not. But, in order for the package to be useful to SCIRun, you would still need to have a components.xml file and possibly a GUI directory for the .tcl files, if any.
When SCIRun starts it searches the enabled packages (defined at configure time: --enable-package="...") for components.xml files. It parses those that it finds and builds the package menus. The .xml file tells SCIRun where each module's .tcl file be found, which library(s) to load, and tells it what the name of each module's make function is. If an entry in the .xml file cannot be confirmed (i.e. can't find the indicated make function), the associated module will not be listed. Packages that have no valid modules are also not listed.
If you attempted to automatically generate a module and got back an error or warning message, you can search the following table for the error you saw, and find the associated solution:
| Severity | Message | Solution |
| ERROR | One or more of the entries was left must be filled in. | It is illegal to use blank names for packages, categories,or modules. Retype a non-blank name in the offending field(s). |
| WARNING | Package PACKAGE does entries was left blank. All entries must be filled in. | The package name you typed does not exist. Did you intend to create a new package? Did you miss type the package name? All names are case sensitive. If you want to create a new package press "Ok". Otherwise press "Cancel". |
| ERROR | The name PACKAGE is already in use by a non-package file. | The name you supplied for the package is the name of a non-directory file. Choose a different name. |
| ERROR | The file PACKAGE does not appear to be a valid package or is somehow corrupt... | he name you supplied for the package is the name of a directory that does not conform to the standard package hierarchy. Choose another package. This package may be damaged, or was not created/edited correctly. |
| WARNING | Category CATEGORY does not exist. Create it now? | The name you supplied for the category does not exist. Did you intend to create a new category? Did you miss type the category name? If you want to create a new category press "Ok". Otherwise, press "Cancel". |
| ERROR | The name CATEGORY is already in use by a non-category file. | The name you supplied for the category is the name of a non-directory file. Choose a different name. |
| ERROR | The file CATEGORY does not appear to be a valid category or is somehow corrupt... | The name you supplied for the category is the name of a directory that does not conform to the standard category hierarchy. Choose another category. This category may be damaged, or was not created/edited correctly. |
| ERROR | The name MODULE is already in use by another file. | The name you supplied for the module is the name of a file that already exists. Choose another name. |
The following are examples or skeletons for files that are required for building a package hierarchy. Many of the skeletons contain macros which must be expanded before the files can be used. The macros are:
NEW_MODULE = name of the new module
MODULE_AUTHOR = your name
MODULE_DATE = the date the module was created
PACKAGE_NAME = the name of the package that
the module/category belongs to
NEW_PACKAGE = the name of the new package
**** Module .cc file skeleton:
/*
* NEW_MODULE.cc:
*
* Written by:
* MODULE_AUTHOR
* MODULE_DATE
*
*/
#include
#include
#include
namespace PACKAGE_NAME {
namespace Modules {
using namespace PSECore::Dataflow;
class PACKAGE_NAMESHARE NEW_MODULE : public Module {
// [PORT DECLS HERE]
public:
NEW_MODULE(const clString& id);
virtual ~NEW_MODULE();
virtual void execute();
virtual void tcl_command(TCLArgs&, void*);
};
extern "C" PACKAGE_NAMESHARE Module*
make_NEW_MODULE(const clString& id) {
return new NEW_MODULE(id);
}
NEW_MODULE::NEW_MODULE(const clString& id)
: Module("NEW_MODULE", id, Source)
{
// [PORT CONSTRUCTORS HERE]
}
NEW_MODULE::~NEW_MODULE()
{
}
void NEW_MODULE::execute()
{
}
void NEW_MODULE::tcl_command(TCLArgs& args, void* userdata)
{
Module::tcl_command(args, userdata);
}
} // End namespace Modules
} // End namespace PACKAGE_NAME
**** skeleton sub.mk files for various trees:
** SCIRun/src/PACKAGE/sub.mk =
include $(OBJTOP_ABS)/scripts/largeso_prologue.mk
SRCDIR := NEW_PACKAGE
SUBDIRS := $(SRCDIR)/GUI $(SRCDIR)/Datatypes \
$(SRCDIR)/Modules
include $(OBJTOP_ABS)/scripts/recurse.mk
PSELIBS := PSECore SCICore
LIBS := $(TK_LIBRARY) $(GL_LIBS) -lm
include $(OBJTOP_ABS)/scripts/largeso_epilogue.mk
** SCIRun/src/PACKAGE/Modules/sub.mk =
SRCDIR := NEW_PACKAGE/Modules
SUBDIRS := \
#[INSERT NEW SUBDIRS HERE]
include $(OBJTOP_ABS)/scripts/recurse.mk
** SCIRun/src/PACKAGE/Modules/CATEGORY/sub.mk =
include $(OBJTOP_ABS)/scripts/smallso_prologue.mk
SRCDIR := PACKAGE_NAME/Modules/CATEGORY_NAME
SRCS += \
#[INSERT NEW MODULE HERE]
PSELIBS := PSECore/Datatypes PSECore/Dataflow \
SCICore/Persistent SCICore/Containers SCICore/Util \
SCICore/Exceptions SCICore/Thread SCICore/TclInterface \
SCICore/Geom SCICore/Datatypes SCICore/Geometry \
SCICore/TkExtensions
LIBS := $(TK_LIBRARY) $(GL_LIBS) -lm
include $(OBJTOP_ABS)/scripts/smallso_epilogue.mk
** SCIRun/src/PACKAGE/GUI/sub.mk =
SRCDIR := NEW_PACKAGE/GUI
ALLTARGETS := $(ALLTARGETS) $(SRCDIR)/tclIndex
$(SRCDIR)/tclIndex:
scripts/createTclIndex NEW_PACKAGE/GUI
CLEANPROGS := $(CLEANPROGS) $(SRCDIR)/tclIndex
** SCIRun/src/PACKAGE/Datatypes/sub.mk =
SRCDIR := NEW_PACKAGE/Datatypes
SUBDIRS := $(SRCDIR)/none
include $(OBJTOP_ABS)/scripts/recurse.mk
** SCIRun/src/PACKAGE/Datatypes/GROUP/sub.mk =
include $(OBJTOP_ABS)/scripts/smallso_prologue.mk
SRCDIR := NEW_PACKAGE/Datatypes/none
SRCS +=
PSELIBS :=
LIBS :=
include $(OBJTOP_ABS)/scripts/smallso_epilogue.mk
**** share.h skeleton file:
#undef NEW_PACKAGESHARE
#ifdef _WIN32
#if defined(BUILD_NEW_PACKAGE)
#define NEW_PACKAGESHARE __declspec(dllexport)
#else
#define NEW_PACKAGESHARE __declspec(dllimport)
#endif
#else
#define NEW_PACKAGESHARE
#endif
**** DllEntry.cc skeleton file:
#ifndef _WIN32
#error DllEntry.cc is for Windows platforms only
#endif
#include
#include
BOOL APIENTRY DllMain(HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
#ifdef DEBUG
char reason[100]="\0";
printf("Calling DllMain in NEW_PACKAGEd.dll : );
printf(" hModule = %d ***\n",hModule);
switch (ul_reason_for_call){
case DLL_PROCESS_ATTACH:sprintf(reason,"DLL_PROCESS_ATTACH"); break;
case DLL_THREAD_ATTACH:sprintf(reason,"DLL_THREAD_ATTACH"); break;
case DLL_THREAD_DETACH:sprintf(reason,"DLL_THREAD_DETACH"); break;
case DLL_PROCESS_DETACH:sprintf(reason,"DLL_PROCESS_DETACH"); break;
}
printf(" ul_reason_for_call = %s ***\n",reason);
#endif
return TRUE;
}
**** example .tcl file (from GenAxes.tcl inside BioPSE). The
structure required (as mentioned above) is inheritance from
Module, and the existence of a method name "ui". If "ui"
does not exist, then the module has no user interface:
itcl_class SCIRun_Visualization_GenAxes {
inherit Module
constructor {config} {
set name GenAxes
set_defaults
}
method set_defaults {} {
global $this-size
set $this-size 1
$this-c needexecute
}
method ui {} {
set w .ui[modname]
if {[winfo exists $w]} {
raise $w
return;
}
toplevel $w
frame $w.f
pack $w.f -padx 2 -pady 2
set n "$this-c needexecute"
global $this-size
scale $w.f.slide -label Size -from 0.01 -to 9.99 \
-showvalue true \
-orient horizontal -resolution 0.01 \
-digits 3 -variable $this-size \
-command "$this-c size_changed"
button $w.f.b -text "Execute" -command $n
pack $w.f.slide $w.f.b -side top
}
}
**** example components.xml file (from BioPSE package):
<?xml version='1.0' encoding='us-ascii' ?>
<!-- description of the PSECommon package -->
<package name="PSECommon">
<guiPath>GUI</guiPath>
<scirun-library category="FEM">
<soNames>
<soName>libPSECommon_Modules_FEM.so</soName>
<soName>libPSECommon.so</soName>
</soNames>
<dataflow-component name="BuildFEMatrix">
<meta>
<authors>
<author>Ruth Nicholson Klepfer</author>
</authors>
<version-date>October 1994</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_BuildFEMatrix</creationFunction>
</implementation>
</dataflow-component>
<dataflow-component name="FEMError">
<meta>
<authors>
<author>Steven G. Parker</author>
</authors>
<version-date>May 1996</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_FEMError</creationFunction>
</implementation>
</dataflow-component>
...
<!-- INSERT NEW FEM COMPONENT HERE -->
</scirun-library>
<scirun-library category="Readers">
<soNames>
<soName>libPSECommon_Modules_Readers.so</soName>
<soName>libPSECommon.so</soName>
</soNames>
<dataflow-component name="MatrixReader">
<meta>
<authors>
<author>Steven G. Parker</author>
</authors>
<version-date>July 1994</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_MatrixReader</creationFunction>
</implementation>
</dataflow-component>
<dataflow-component name="MeshReader">
<meta>
<authors>
<author>Steven G. Parker</author>
</authors>
<version-date>July 1994</version-date>
<version>1.0</version>
<description>No description available</description>
</meta>
<inputs></inputs>
<outputs></outputs>
<parameters></parameters>
<implementation>
<creationFunction>make_MeshReader</creationFunction>
</implementation>
</dataflow-component>
...
<!-- INSERT NEW Readers COMPONENT HERE -->
</scirun-library>
<!-- INSERT NEW CATEGORY HERE -->
</package>