8.3. EnSight Extension Mechanism

A number of features of EnSight are written as extensions in Python or command language. In EnSight, this system has been written to more fully integrate graphical user interface objects and command language extensions into EnSight. The extension framework itself is written in Python and resides in $CEI/ensight242/site_preferences/extensions. This directory contains two directories: core and user_defined. The core directory contains the Python implementation of all of the base classes described here. The user_defined directory contains the various EnSight extensions that are loaded on startup. At least two replacement Graphical User Interface extensions, ten Tools and a large number of Menus are included by Ansys in the distribution. The source code to these are an extremely valuable reference to the extension developer.

8.3.1. Startup Process

At startup, EnSight builds a default sys.path which includes $CEI/ensight242/site_preferences/extensions as well as EnSightDefaultDirectory/extensions (if it exists). EnSightDefaultsDirectory is %HOMEDRIVE%%HOMEPATH%\(username)\.ensight242 commonly located at C:\Users\username\.ensight242 on Vista and newer Windows Operating Systems, C:\Documents and Settings\yourusername\.ensight242 on older Windows, and ~/.ensight242 on Linux, and ~/Library/Application Support/ensight242 on the Mac. It will also add any directories specified by the CEI_PYTHONPATH environmental variable. This variable may contain multiple directory names, separated by a : under UNIX operating systems and a ; under Windows.

EnSight scans in sys.path, looking for entries that end in the name 'extension'. If any subdirectory of an extension directory contains an __init__.py file, the subdir is 'import'ed into the module ensight.ext (note: ensight.ext.__path__ is modified to include the extensions dirnames). If a user has common Python utilities or a startup script, it can be added as a module to the internal Python interpreter via this mechanism.

8.3.2. Formal EnSight Extensions

EnSight supports the concept of a formal extension. An extension to EnSight is a subclass of one of the base-classes of the extension framework. Common extensions are for user-defined menus or tools and to replace the EnSight graphical user interface itself. All extensions include a mechanism to extend EnSight command language to include custom batch scripting and action logging. EnSight looks for extensions by scanning all of the .DEFINE, .py and .enc files located in subdirectories rooted by a subdirectory of a sys.path extension directory (as described above) named user_defined for specially formatted comment blocks. There are two comment block forms for Legacy and Direct load extensions.

Legacy Extensions

Legacy extensions can include operations written in command language or Python. The comment block in each of these extensions has the general form:

#ENSIGHT_USER_DEFINED_BEGIN

#TEXT=name used for GUIs

#TOOLTIP=text displayed in tooltips

#ICON=name_of_an_iconfile.png

#NAME=simplename

#DESC=text description of the item

#TYPE=TOOL,MENU,TOOLDIR,MENUDIR

#MODE=All,Part,Annot,Plot,VPort,Frame

#PTYPE=All,None,Mixed,Model,ClipPlane,Contour,DiscreteParticle,Frame,IsoSurface,ParticleTrace,Profile,VectorArrow,ElevatedSurface,DevelopedSurface,ModelExtract,ModelCut,ModelBoundary,IsoVolume,BuiltUp,TensorGlyph,FxVortexCore,FxShock,FxSepAtt,MaterialInterface,Point,AxiSymmetric,ModelMerge,Mult

#ENSIGHT_USER_DEFINED_END

Most of these fields are optional. If they are included in a .py or .enc file, they inform EnSight that these are TOOL or MENU extensions (depending on the TYPE= value). EnSight will build an extension object (a subclass of the tool_extension or menu_extension classes) for the file and if the extension is executed, the contents of the file will be executed. When it is run, the directory containing the file will be added to sys.path temporarily.

MODE= and PTYPE= fields are used for filtering the display of the item depending on the current state of EnSight (what mode and what type of parts are selected).

A file named menu.define or tool.define can contain just this comment block, but uses the type TOOLDIR or MENUDIR. If this file exists, the other commented files are included as GUI children of that object. The loader will instantiate a class of the proper type: menu_extension or tool_extension and registers it with EnSight. The registration operation has two parts. First, the class is stored under the proper key in the dictionary ensight.core.extensions so that object can query and use the various extensions. Second, the register method is called on the base class (core_extension). This methods registers a method on the object as a command language extension. Therefore, an object with the name foo that is a child of an object with the name bar will be able to generate command language of the form ext: bar.foo string. It is also able to handle the playback of such strings by overriding the base class.

Direct Loaded (Python) Extensions

Direct load extensions can only be written in Python and have a number of advantages over Legacy extensions, including private namespaces in both Python and command language. Developers are encouraged to use this form of extension whenever possible as it avoid potential namespace collisions both in Python and EnSight. To use this mechanism, the developer writes their own subclass(es) of the appropriate extension base class(es). They then add a comment block to the top of the Python source code file that looks like this:

#ENSIGHT_USER_DEFINED_BEGIN

#FACTORY=function_name

#ENSIGHT_USER_DEFINED_END

In this case, the file will be imported and the function function_name() will be invoked with a single parent parameter. The function should create all of the instances of the extension class objects needed and return them as a Python list.


Note:  The function_name() should also do any parent adding for the objects it wants before returning them. The calling system will register the objects in the returned list and insert them into the ensight.core.extension dictionary. When this file is imported, the directory it is in will have been added to the sys.path variable.


8.3.3. Extension Base Classes

The extension mechanism is based on Python subclasses. All extensions subclass from a code base class. This class provides two important features. First, each extension is in a private namespace in Python, allowing each one to have its own global variables. Second, each extension is given part of the command language namespace complete with methods to generate custom command language and replay it. The simplest extension just extends command language with a new command (note: in command language, all extensions appear as: ext: namespace string). The base classes are core, guibase, gui, tool and menu.

core_extension class

Basic extension of the command language (type = "core"). Override the cmdExec() and cmdRecord() methods to generate your own command language extensions.

Data Members

_version : version number

_path : path to the file defining this extension

_name : name for purposes of Python/cmdlang namespaces (no spaces, odd chars, etc)

_text : Display name of the extension (spaces allowed)

_desc : ASCII description of the extension (spaces allowed)

_namespace : actual cmdlang namespace (for example, ext: namespace foo)

_children : list of children of this extension

Member Functions

addChild(child) : add a child

setDesc(text)

setSort(text) : set the menu sorting value for this extension

cmdExec(string) : called back when cmdlang sees ext: namespace foo String will be foo

cmdRecord(string) : call to record command language ext: _namespace string

guibase_extension class (subclass of core_extension)

This class adds common GUI elements such as itcons and tooltips (type = "guibase"). In general, user defined extensions should not subclass this class directly.

Member Functions

setInMenus(v) : Set the state of dynamic menu visiblity

getInMenus() : True if this extension should show up in any dynamic menus

setTooltip(text) : the text for the tooltip

setIcon("pathname") : filename for the icon, can be a resource reference (for example, ":/ensight/...")

getWidget(w,parent) : return the widget representation of the object. This method is overridden by subclasses

setWaitCursor(onoff) : display/clear the hourglass cursor for this extension

gui_extension class (subclass of guibase_extension)

A full user-defined GUI (type = "gui"). Subclass this class to completely replace the EnSight GUI with your own GUI.


Note:  The cmdExec method has been overridden to include activate_gui.


Member Functions

setSplash(pathname) : pathname to an SVG file to use as the splash screen

doActivate(onoff) : called to enable/disable this graphical user interface must be overridden, returns False on failure.

doErrorMessage(message,style) : called when EnSight throws an error or needs a Yes/No question, must be overridden.

doGuiEvent(message,targetlist) : called when there is a graphical user interface event, must be overridden.

Useful Module Functions

These functions can be used directly by applications that want to interact with the core graphical user interface and its events.

ensight.core.gui_extension.gui_activate(name)

gui=ensight.core.gui_extension.current_active_gui()

ensight.core.gui_extension.error_message(msg,what)

ensight.core.gui_extension.gui_event(msg,targets)

tool_extension class (subclass of guibase_extension)

Baseclass for an EnSight user-defined tool (type = "tool"). All tool extensions must subclass from this class.

Member functions

run() : This function is called when the tool is invoked. The function must be overridden.

menu_extension class (subclass of guibase_extension)

Baseclass for an EnSight popup menu (type = "menu"). All menu extensions must subclass from this class.

Data Members

_info : the dictionary of values resulting from the mousedown that triggered this menu

Member Functions

setSeparator(boolean) : set to true if this menu is a separator

setMode(mode) : a string containing the mode names for this menu to be displayed in. Concatenated from: Part,Annot,Plot,VPort,Frame,All

setPartType(type) : a string containing the part types for which this menu is to be displayed. Concatenated from: All,Model,ClipPlane,Contour,DiscreteParticle,Frame,IsoSurface,ParticleTrace,Profile,VectorArrow,ElevatedSurface,DevelopedSurface,ModelExtract,ModelCut,ModelBoundary,IsoVolume,BuiltUp,TensorGlyph,FxVortexCore,FxShock,FxSepAtt,MaterialInterface,Point,AxiSymmetric, ModelMerge,Mult

validFilter(self,emode,ptype) : return 1 if this menu should be shown. By default, it filters using the mode and parttype values as noted above, however for general filtering, one ma override this method.

run() : override this method do what this menu should do when selected.

the _info attribute is set to the context of the operation that started the menu operation. It is a dictionary. Some fields are pre-defined, depending on the EnSight RMB context.

The pre-defined _info fields are:

Common to All Modes

pick: the click target

pos: the (x,y,z) tuple of the click in data space

target: list of ensight object references that are the action target (list can be empty)

mousepos: (x,y) position tuple in the graphics window of the mouse down (normalized coords). -1,-1 if there is no position or one is not in the window.

pick: CVF_CURSOR_TOOL

pick: CVF_LINE_TOOL

'item': The part of the line clicked: CVF_EVENT_LOCAT_MID, CVF_EVENT_LOCAT_END2, CVF_EVENT_LOCAT_END1, CVF_EVENT_LOCAT_XAXIS, CVF_EVENT_LOCAT_YAXIS, CVF_EVENT_LOCAT_ZAXIS

pick: CVF_PLANE_TOOL

'item': The part of the line clicked: CVF_EVENT_LOCAT_MID, CVF_EVENT_LOCAT_UPPER_RIGHT, CVF_EVENT_LOCAT_LOWER_LEFT, CVF_EVENT_LOCAT_LOWER_RIGHT, CVF_EVENT_LOCAT_UPPER_LEFT, CVF_EVENT_LOCAT_XAXIS, CVF_EVENT_LOCAT_YAXIS, CVF_EVENT_LOCAT_ZAXIS

pick: CVF_CYLINDER_TOOL

'item': The part of the line clicked: CVF_EVENT_LOCAT_MID, CVF_EVENT_LOCAT_END2, CVF_EVENT_LOCAT_END1, CVF_EVENT_LOCAT_XAXIS, CVF_EVENT_LOCAT_YAXIS, CVF_EVENT_LOCAT_ZAXIS, CVF_EVENT_LOCAT_RADIUS

pick: CVF_CONE_TOOL

'item': The part of the line clicked: CVF_EVENT_LOCAT_MID, CVF_EVENT_LOCAT_END2, CVF_EVENT_LOCAT_END1, CVF_EVENT_LOCAT_XAXIS, CVF_EVENT_LOCAT_YAXIS, CVF_EVENT_LOCAT_ZAXIS, CVF_EVENT_LOCAT_RADIUS

pick: CVF_SPHERE_TOOL

'item': The part of the line clicked: CVF_EVENT_LOCAT_MID, CVF_EVENT_LOCAT_END2, CVF_EVENT_LOCAT_END1, CVF_EVENT_LOCAT_XAXIS, CVF_EVENT_LOCAT_YAXIS, CVF_EVENT_LOCAT_ZAXIS

pick: CVF_REVO_TOOL

'item': The part of the line clicked: CVF_EVENT_LOCAT_MID, CVF_EVENT_LOCAT_END2, CVF_EVENT_LOCAT_END1, CVF_EVENT_LOCAT_XAXIS, CVF_EVENT_LOCAT_YAXIS, CVF_EVENT_LOCAT_ZAXIS

pick: CVF_POLYLINE

'item': The spline number clicked on

'point': The index into the spline knot points

pick: CVF_LEGEND

'item': The palette id

'component': The palette id component (for vectors)

pick: CVF_ANNOT

'item': The annotation id (the target object has the type implicitly)

pick: CVF_PART

'item': The part id

pick: CVF_VIEWPORT

'item': The viewport id

pick: CVF_PLOTTER

'curve': The curve number clicked on

'value': The (f,x) value at the clicked point

'_prepick_': This key is optional. If set to the name of a user-defined menu, the doPopup() function will not display any menus. Instead, it will call the 'run()' method on the first menu with that name. This mechanism makes it easy to execute a menu in a given spatial context.

Useful Module Functions

core.userdefinedmenus.doPopup(infodict) : popup the current RMB menu at the mouse. You must pass a proper info dictionary (see: _info above)

With this function, the caller needs to create the _info dictionary. One special key has been defined: 'mode'. If this key is present, it will override the current EnSight mode value for validFilter() filtering. An example using this function would be:

dict = {'pick': ensight.CVF_PART, 'item': partid, 'target': partobj,

'pos': (0,0,0), 'mousepos': (-1,-1) }

ensight.core.userdefinedmenus.doPopup(dict)

Internationalization and EnSight Extensions

The EnSight core includes an instance of the CEItranslate object. It can be accessed as: ensight.core.tr. When the ensight.core module loads and scans the directories for extensions, it also scans for any *.qm files and automatically registers them with ensight.core.tr. Thus, if you have internationalized an extension GUI (or any other Qt/PyQt GUI), simply place the translation files in the extensions directory and they will be loaded. When the QApplication instance is created, ensight.core.tr.changelang() is called. If the user has set CEI_LANG (or the host system has a language set), the translation files for that language are automatically loaded. CEI_LANG should be set to the two character ISO 639 code for the target language. See http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes for more information.

In Python code, you can trap and handle dynamic language changes by including the following method to your top level widget:

# i18n bits: handle dynamic language changes
    def changeEvent(self, event):
        if (event.type() == QtCore.QEvent.LanguageChange):
            self.retranslateUi(self)
        QtGui.QMainWindow.changeEvent(self, event)

Note:  You may need to change QMainWindow to the appropriate classname. The 'self.retranslateUi()' call should be replaced with whatever function regenerates the graphical user interface text contents on your system (it may be more than one call to auto-generated retranslateUi() methods.