4.23. SALOME module workflow

Author:David Križaj

The purpose of this HOWTO is to give developers of SMITER some guides and examples for adding new modules to SMITER and creating SMITER dialogs. New SMITER modules can be added with the use of template made for that purpose. Instructions for using the template are listed below. SMITER dialogs are created in Python script with the use of PyQt5 module and connected to SMITER with CORBA engine.

4.23.1. Adding a new module to SMITER

All NEW_MODULE usage in this HOWTO is used to represent a new module that any SMITER developer may want to include in SMITER environment. At this point it makes sense to mention that the usage of upper and lower case characters in this HOWTO is also important. If you don’t stick to the use of lower case characters when said so, new module might not work or be visible in SMITER.

4.23.1.1. Adding a new module

To add a new module to SMITER, we can use the template named NMOD, located at:

~/smiter/examples/Dialog_examples

To add a new module into SMITER environment use commands shown below. Change NEW_MODULE to the name of the module that will be included.

cd ~/smiter/examples/Dialog_examples
find NMOD* | while read f
  do rename="sed -e s/NMOD/NEW_MODULE/g;s/nmod/new_module/g"
  nf=$(echo $f | $rename)
  if test -d $f
    then mkdir -p $nf
    else $rename $f > $nf
  fi
done
cd ~/smiter
cp -avr ~/smiter/examples/Dialog_examples/NEW_MODULE .
cp ~/smiter/examples/Dialog_examples/NEW_MODULE.pyconf ~/smiter/PROJECT/products/

Renaming can be done with multi-line command, or with one line command shown in a code block below. If you used multi-line code in a block above, there is no need to use code block below.

find ./examples/Dialog_examples/NMOD* | while read f; do rename="sed -e s/NMOD/NEW_MODULE/g;s/nmod/new_module/g"; nf=$(echo $f | $rename); if test -d $f;then mkdir -p $nf; else $rename $f > $nf; fi; done

Multi-line command renames files and folders and also changes content of files so all scripts are prepared to correctly compile. Then we copy NEW_MODULE.pyconf file to right place so it can be found when SMITER is compiling.

Then we have to edit only smiter *.pyconf files in:

~/smiter/PROJECT/applications/

A line with the name of the new module has to be added as shown in a code block below:

# SALOME MODULES :
SMITER : True
ELMER : True
RSECT : True
CONFIGURATION : True
NEW_MODULE : True #ADD THIS LINE

Makefile also has to be edited. In the code block below only the parts of the code which have to be edited are shown. Where NMOD is written, it has to be replaced with the name of the new module. Upper and lower case characters matter, so caution is needed.

SALOME_APPLICATION = $(subst imas-,,$(subst rsect-,,$(subst nmod-,,$(subst elmer-,,\
       $(subst smiter,SALOME,$(SMITER_APPLICATION))))))

ALL = INSTALL/SMITER INSTALL/SMITER_GUI INSTALL/SMITER_GUI/salome \
         INSTALL/SALOME/salome INSTALL/ELMER INSTALL/RSECT INSTALL/NMOD smiter_mesa \
         salome_mesa

NMOD_SOURCES := $(wildcard NMOD/src/*/* NMOD/doc/*/* NMOD/* NMOD/*/*)

INSTALL/NMOD: $(SALOME_MODULES) $(NMOD_SOURCES) $(SMITER_ASSETS)
        ./sat prepare ${SMITER_APPLICATION} --product NMOD
        ./sat compile ${SMITER_APPLICATION} --product NMOD --clean_all

When all scripts editing is done, we have to use the following commands to make the new module appear in SMITER. With those commands we generate new env_launch.sh script.

cd ~/smiter
rm -rf salomeTools
make ${PWD}/salomeTools/sat
make
./smiter

With those commands we update salomeTools and make sure that we have correctly added modules shown when we run SMITER. After using those commands new SMITER session should appear on the screen and the module icon should appear in the toolbar.

To set the custom icon you have to replace default NMOD icon with the icon that the new module will use. The icon is located at:

~/smiter/NEW_MODULE/resources/new_module_icon.png

4.23.1.2. Solving problems

After all commands listed above are correctly executed, NEW_MODULE has to appear in SMITER environment. If compiling gives you an error, README.md file might give some answers to solve compiling problems. After correct compilation of all packages and modules, the new module might not appear in SMITER. To check what the problem might be, instructions below should be followed.

  1. Check if the new module can be manually imported.

To test this, python console inside SMITER can be used. If console is not visible, you can activate it with Alt+Shift+P. To test if the new module is correctly added in the background, you should type:

import NEW_MODULE

If module is correctly added, no error should be given back.

  1. Check env_launch.sh script.
If the new module name is not written in the shell script, you have to make sure that salomeTools are correctly made, because that shell file is generated by salomeTools. If the new module is not presented in that file, it’s recommended to run the code block for env_launch.sh generation again.

4.23.2. SMITER dialogs

Dialogs are part of program environment, which users interact with in aim to prepare cases for SMITER engines to solve.

4.23.2.1. SMITER dialogs file structure

For examples shown in this HOWTO, the module we will add is named NEW_MODULE and has already been added with the use of instructions above.

In every SMITER module, five base scripts have to be created.

  • NEW_MODULE_Gen.idl
  • NEW_MODULE_case.py
  • create_NEW_MODULE_case.py
  • NEW_MODULEGUI.py
  • NEW_MODULE_utils.py

All that scripts are already created, if instructions above were correctly followed. Connection between those five scripts is shown on a figure below.

../../_images/Dialogs_scheme.png

Fig. 4.110 SMITER script scheme for creating dialogs.

In the figure above we have scripts connected with black arrows. Black arrows are presenting the import function from one script to another and on the end with a blue arrow to the SMITER interface. We can follow the information flow from the upper right corner all the way to the interface.

First, we have NEW_MODULE_Gen.idl CORBA file that has all the methods, which Python scripts need to interact with Salome. NEW_MODULE_case.py script contains all methods written in NEW_MODULE_Gen.idl, except the first ones are written in python language and the second ones in CORBA language. Then those methods are imported to create_NEW_MODULE_case.py and used to connect our dialog with SMITER. Dialog class written in create_NEW_MODULE_case.py script is then imported into NEW_MODULEGUI.py script, where all SMITER object associated methods are written. All utility methods are written in NEW_MODULE_utils.py and all imported to create_NEW_MODULE_case.py and some also to NEW_MODULEGUI.py.

4.23.2.2. CORBA

Common Object Request Broker Architecture (CORBA) enables communication between software written in different languages and running on different computers. It is used to connect python scripts with Salome.

To connect Python methods with Salome, we have to create an .idl file. An example from NEW_MODULE is shown in a code block below.

#define __NEW_MODULE_GEN__
#ifndef __NEW_MODULE_GEN__

#include "SALOME_Component.idl"
#include "SALOMEDS.idl"
#include "SALOME_Exception.idl"

module NEW_MODULE_ORB
{
  interface NEW_MODULEcase
  {
    void setName(in string caseName);
    string getName();
  };

  interface NEW_MODULE_Gen : Engines::EngineComponent, SALOMEDS::Driver
  {
    string makeBanner(in string name)
      raises (SALOME::SALOME_Exception);

    void createObject(in SALOMEDS::Study theStudy,
                      in string name)
      raises (SALOME::SALOME_Exception);

    void raiseAnException()
      raises (SALOME::SALOME_Exception);

    void publishCase(in SALOMEDS::Study theStudy, in NEW_MODULEcase NMCase);

  };
};

#endif

First, __NEW_MODULE_GEN__ is defined and set as condition of an if statement. Then some .idl libraries are included. Those .idl files include all methods, which are needed to create CORBA files. Then we create the module and give it some interface functions. At the beginning, base interface is added. In the case above only setName() and getName() are used in first interface. With that two methods we can read SMITER objects with and we can add cases to object tree. Other methods can be added in aim to make new module work correctly. For useful examples and examples of good practice, other SMITERs modules scripts can be observed for some inspiration and new ideas. If we have more case types, which have different specifications, new interface code blocks can be added.

Second interface, named NEW_MODULE_Gen, is an interface, which is always presented in SMITER CORBA scripts. The only difference between other scripts is that we add new methods to that interface in aim to create other case types. Every case type has to have a void method written in that interface. For new basic case, code block line with publishCase can be used, but for others new methods have to be added. For examples ELMER scripts can be observed.

4.23.2.3. NEW_MODULE_case.py

In the CORBA script example above, we have two methods that were written in the first interface. To establish communication between our scripts and Salome, we have to write these methods in a Python script and define what those methods return. A code block below presents the NEW_MODULE_case.py file to connect NEW_MODULE scripts with Salome.

import NEW_MODULE_ORB__POA

class NEW_MODULEcase(NEW_MODULE_ORB__POA.NEW_MODULEcase):
  def __init__(self, caseName=''):
      self.caseName = caseName

  def setName(self, caseName):
      self.caseName = caseName

  def getName(self):
      return self.caseName

4.23.2.4. create_NEW_MODULE_case.py

To actually create dialogs and affect their appearance, we create python script and with use of PyQt5 methods create windows, as we would like them to appear on the screen. For creating dialogs we can use already prepared methods, which are in NEW_MODULE_base_dialog.py script. If methods, written in that script, do not meet our expectations, or some methods are missing, we can write new methods in that file, or we can write dialog scripts with use of QDialog class. QDialog class is part of PyQt5 module and has all different methods that are available. If one decides to use QDialog class, the dialog code can become messy and unclear.

4.23.2.5. NEW_MODULEGUI.py

NEW_MODULEGUI.py is a script, which shows our module in SMITER. In that script methods for menus and toolbars are written. For that purpose GUIcontext class is used. In a code block below an example for NEW_MODULE is presented.

class GUIcontext:
    # menus/toolbars/actions IDs
    NEW_MODULE_MENU_ID  = 90
    HELLO_ID            = 731
    CREATE_OBJECT_ID    = 732
    OPTIONS_ID          = 733
    PASSWORD_ID         = 734
    NEW_MODULE_TB_ID    = 90
    DELETE_ALL_ID       = 741
    SHOW_ME_ID          = 742
    DELETE_ME_ID        = 743
    RENAME_ME_ID        = 744
    CASE_ID             = 745
    # default object name
    DEFAULT_NAME     = "Object"
    # default password
    DEFAULT_PASSWD   = "Passwd"

    def __init__( self ):
        # create top-level menu
        mid = sgPyQt.createMenu("NEW_MODULE", -1,
                                GUIcontext.NEW_MODULE_MENU_ID,
                                sgPyQt.defaultMenuGroup())
        # create toolbar
        tid = sgPyQt.createTool("NEW_MODULE")
        # create actions and fill menu and toolbar with actions
        a = sgPyQt.createAction(GUIcontext.HELLO_ID, "Hello", "Hello",
                                "Show hello dialog box", "NEW_MODULE_icon.png")
        sgPyQt.createMenu(a, mid)
        sgPyQt.createTool(a, tid)

        b = sgPyQt.createAction(GUIcontext.CASE_ID, "Case", "Case",
                                "Prepare case", "NEW_MODULE_case_icon.png")
        sgPyQt.createMenu(b, mid)
        sgPyQt.createTool(b, tid)

        a = sgPyQt.createSeparator()
        sgPyQt.createMenu(a, mid)

        a = sgPyQt.createAction(GUIcontext.CREATE_OBJECT_ID, "Create object",
                                "Create object", "Create object")
        sgPyQt.createMenu(a, mid)
        a = sgPyQt.createSeparator()
        sgPyQt.createMenu(a, mid)

        a = sgPyQt.createSeparator()
        a = sgPyQt.createAction(GUIcontext.PASSWORD_ID, "Display password",
                                "Display password", "Display password")
        sgPyQt.createMenu(a, mid)

        # the following action are used in context popup
        a = sgPyQt.createAction(GUIcontext.DELETE_ALL_ID, "Delete all",
                                "Delete all", "Delete all objects")
        a = sgPyQt.createAction(GUIcontext.SHOW_ME_ID, "Show",
                                "Show", "Show object name")
        a = sgPyQt.createAction(GUIcontext.DELETE_ME_ID, "Delete", "Delete",
                                "Remove object")
        a = sgPyQt.createAction(GUIcontext.RENAME_ME_ID, "Rename",
                                "Rename", "Rename object")
        pass
    pass

At the beginning of GUIcontext class we define IDs of elements that we use to create dialogs. Every element has to have an unique ID number. With that number SMITER identifies its elements. With method __init__ all static interface is created. The code is very clear and straightforward because of sgPyQt class. It is imported at the beginning of the document and defined with sgPyQt = SalomePyQt().

After the GUIcontext class, we first define global variables.

################################################
# Global variables
################################################

# object counter
__objectid__ = 0

################################################

# Get SALOME PyQt interface
sgPyQt = SalomePyQt()

# Get SALOME Swig interface
sg = libSALOME_Swig.SALOMEGUI_Swig()

################################################

After global variables are defined, we define internal methods. Their purpose is to detect if objects have children or to give objects an unique ID number. If an object is a child of a parent object, new number is added to its ID. For example, if we have NEW_MODULE in SMITER tree, and that NEW_MODULE has two cases, ID numbers would look like:

  • NEW_MODULE (ID 0.1.1)
    • CASE_1 (ID 0.1.1.1)
    • CASE_2 (ID 0.1.1.2)

This part of the code is presented in a code block below and also has additional comments.

################################################
# Internal methods
################################################

###
# returns True if object has children
###
def _hasChildren(sobj):
    if sobj:
        iter = salome.myStudy.NewChildIterator(sobj)
        while iter.More():
            name = iter.Value().GetName()
            if name:
                return True
            iter.Next()
            pass
        pass
    return False
###
# increment object counter in the map
###
def _incObjToMap(m, id):
    if id not in m:
        m[id] = 0
    m[id] += 1
    pass
###
# analyse selection
###
def _getSelection():
    selcount = sg.SelectedCount()
    seltypes = {}
    for i in range(selcount):
        _incObjToMap(seltypes, getObjectID(sg.getSelected(i)))
        pass
    return selcount, seltypes
################################################

After that we define callback functions. These methods are capable of performing initialization actions, returning the map of popup windows, etc.

NEW_MODULEGUI.py is also used to import all dialogs, which we created, and to make them appear when we call them.

4.23.2.6. NEW_MODULE_utils.py

The NEW_MODULE_utils.py has methods, which we use in CORBA and python scripts. It has methods that we use to get ID numbers, and also important functions like getStudy(), getEngine() etc.

4.23.3. Creating SMITER dialogs

4.23.3.1. NEW_MODULE_base_dialog.py

To make creating dialogs easier, we can use the NEW_DIALOG_base_dialog.py script. That script is automatically created, if we create new module with instructions listed above. In that script many methods are written in Dialog class and can make writing SMITER dialogs very simple and clear. The main disadvantage of this approach is that not all methods are written, and thus we can not use the whole potential of PyQt5 abilities. With that in mind, developers of SMITER dialogs can add some methods that are not written yet and expand the library of existing methods.

For base example of this script, SMITER base dialog script can be used. That script is the base script for all others module scripts and is located at:

cd SMITER/src/Dialogs/base_dialog.py

Dialog class is a base widget for creating dialogs. We create this base widget because it makes creating dialogs easier, since we use already prepared types of widgets.

If the widget is not already prepared, it can be easily added. Methods, which are already in the script, can be used for examples of good practice.

4.23.3.2. Creating dialogs

If the new module is created with instructions above, three different dialogs are already written. First one is the base HELLO dialog and other two dialogs are created for examples of different dialog creations. First one is written with the use of Dialog class from NEW_MODULE_base_dialog.py, and second is written with the use of QDialog class. Creating new dialogs in SMITER requires some basic Python language understanding.

4.23.3.3. Adding a new dialog

In case a new dialog needs to be added into SMITER module, there are some things that have to be done beside creating new dialog class.

To make the dialog structure clear and understandable, we write new dialog class in a new python script with the name that suits its purpose. When we create the new script, we have to add the name of that script to CMakeLists.txt file, which is located in scripts folder. Every folder has its own CMakeLists.txt script and all files in that scripts folder have to be written in it. With that we include that script in the make scheme. An example of CMakeLists.txt file is shown in a code block below:

# --- scripts ---

# scripts / static
SET(_bin_SCRIPTS
  nmod_base_dialog.py
  create_nmod_case.py
  create_dialog_nmod_case.py
  )

  # SET(SUBDIRS_COMMON
  #   edf
  #   edf-extra
  #   forms
  #   images
  # )
  #
  # SET(SUBDIRS
  #   ${SUBDIRS_COMMON}
  # )


  # --- rules ---
  SALOME_INSTALL_SCRIPTS("${_bin_SCRIPTS}" ${SALOME_INSTALL_SCRIPT_PYTHON})
  # FOREACH(dir ${SUBDIRS})
  #  ADD_SUBDIRECTORY(${dir})
  # ENDFOREACH(dir ${SUBDIRS})

We also have to make our new dialog appear in SMITER. For that we use the NEW_MODULEGUI.py script. First, we add new ID variable to GUIcontext class. Then we have to add our new module dialog to the menu and toolbar. We can do that with adding correct part of the code to the __init__ method of GUIcontext class. Adding it is very intuitive, because at that part of the code some examples are already written.

After that we have to write a new method that will connect our dialog with SMITER. For that purpose we can use already written examples.

At the end of NEW_MODULEGUI.py script, a dictionary that connects methods and ID numbers is written. Our new dialog has to be added in that dictionary. We can take a look at already written examples and add a line that looks like: GUIcontext.CASE_ID: NewCASE, where CASE_ID stands for the number written at the beginning of the script and NewCASE presents the name of the method, which we used to present our imported dialog.

From already written dialog examples we can see, that usage of prepared methods is easier way to write dialog. In SMITER and ELMER module base dialog script does not have the same type of methods. Because of the need to create dialog tabs, methods had to be edited in a way that enables prescription of parent widget. If parent widget is not defined, global parent is used. Methods had to be edited because tab widget is independent widget that has own layout and as consequence, methods had to be changed.