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.
- 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_MODULEIf module is correctly added, no error should be given back.
- 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 forenv_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.
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.