Creation of Python bindings for a SAMSON Extension

Creation of Python bindings for a SAMSON Extension

In this tutorial, we will demonstrate how to create simple Python bindings for a SAMSON Extension. We will be using the PyBindTutorial sample from SAMSON-Developers-Tutorials as an example.

Warning: If you add Python bindings to your SAMSON Extension, users will need to install Python (see Python Scripting Installation tutorial) to be able to use your SAMSON Extension.

If you have any questions or experience any problems with creating Python bindings for your SAMSON Extension, please, check the SAMSON Forum.

Content

Prerequisites

Install the Python Scripting SAMSON Extension together with Python following the Python Scripting Installation tutorial.

Download or clone SAMSON-Developers-Tutorials samples. We will be using the PyBindTutorial Sample in this tutorial.

Used packages

Python bindings are done thanks to pybind11 – “a lightweight header-only library that exposes C++ types in Python”.

You can find an extraction of the necessary files in the PyBindTutorial/external folder or you can clone the pybind11 repository (we advice using pybind11 v.2.2.3+). Only the header files (pybind11/include/pybind11/...) are necessary from the pybind11 repository.

Building a SAMSON Extension with Python

Open the root CMakeLists.txt from the SAMSON-Developers-Tutorials and check if the PyBindTutorial is enabled:

Please, check the following tutorials to learn how to build a SAMSON Extension on your OS:

On Windows, a path to your Python installation should be present in the Path environment variable and you should be able to build the PyBindTutorial SAMSON Extension in the Microsoft Visual Studio or in another IDE.

On Linux and Mac, to build a SAMSON Extension with Python, you might need to provide a path to your Python installation in build environment variables of your project. The path to your Python executable should be the first in the PATH build environment variable of your project. See an image below (Linux, QtCreator):

Now, we need to provide paths to Python header files and link against the Python library. For that, open the CMakeLists.txt from the PyBindTutorial sample.

First, we need to check whether a Python interpreter is installed on your system. Note: Python bindings are available only in the release mode. We disable the creation of Python bindings in the debug mode since Python is not shipped with debug libraries.

Then, for each type of OS, we use Python to determine paths to Python header files (PYTHON_INCLUDE_DIR) and the Python library (PYTHON_LIBRARY).

In this CMake code, we add a definition of CREATE_PYTHON_BINDINGS which later is used in the C++ code to determine whether Python bindings should be created or not.

And we include the PYTHON_LIBRARY in the TARGET_LINK_LIBRARIES:

Creating Python bindings

In this tutorial we will show you how to expose some basic functionality of a SAMSON Extension. For more information on how to create Python bindings with pybind11, we refer you to the pybind11 documentation. In particular, we suggest you to check the following:

First, let us create an embedded Python module using pybind11. We use an embedded module since we have the Jupyter QtConsole embedded in the Python Scripting SAMSON Extension.

Please, open the PythonBindings.hpp file. An embedded module can be added using the PYBIND11_EMBEDDED_MODULE macro. Note that the definition must be placed at global scope. An embedded module can be imported in the Python Scripting SAMSON Extension like any other module. These modules are added to Python’s list of builtins, so they can also be imported in pure Python files loaded by the interpreter (the Jupyter QtConsole embedded into Python Scripting SAMSON Extension). See pybind11: Adding embedded modules for more information.

You can add an unlimited number of embedded modules. But we recommend having a single embedded module for a SAMSON Extension named based on the UUID of the SAMSON Extension so that there will be no clashes between modules from different SAMSON Extensions named the same. For example, if a SAMSON Extension A has an embedded Python module named “mymodule” and a SAMSON Extension B has an embedded Python module named “mymodule”, then there will be a clash and it will not be possible to use one of the modules. The UUID of a SAMSON Extension can be found in the CMakeLists.txt of your SAMSON Extension, check the line: SET(OUTPUT_UUID F2078F9E-F2CB-BA72-EE86-1E01A10B63D4). The UUID of the PyBindTutorial SAMSON Extension is F2078F9E-F2CB-BA72-EE86-1E01A10B63D4. The name of the module will be: “SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4”.

In the code above, we define an embedded module named SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4, set its docstring, and expose a function named “moduleName” which returns the module’s name.

You can call it in Python as follows:

Let us create Python bindings for some functionality of the PyBindTutorial App. Particularly, we would like to expose to Python a function from the SEPyBindTutorialApp class called addCustomStructuralModel which adds a custom structural model with custom structural groups into the SAMSON data graph. We create the Python bindings for it in a separate file SEPyBindTutorialAppPythonBindings.cpp. We have two options described below.

1. Expose a function which implements the functionality of the addCustomStructuralModel function without creating Python bindings for the SEPyBindTutorialApp class.

Here, we expose a function in which an instance of the SEPyBindTutorialApp is created and the addCustomStructuralModel function is called. In this case, you can call it in Python as follows:

2. Create Python bindings for a class, its constructor, and the addCustomStructuralModel function.

Here, we expose the SEPyBindTutorialApp class, its constructor, and the addCustomStructuralModel function itself. In this case, you can call it in Python as follows:

Now we can invoke the exposeSEPyBindTutorialApp function inside the PYBIND11_EMBEDDED_MODULE macro:

Let us now expose the CustomStructuralModel class which is based on the SBStructuralModel class from the SAMSON SDK. Note: The functionality already exposed by the Python Scripting SAMSON Extension is accessible through this Extension. For example, in this case you do not need to create any bindings for the functionality from the SBStructuralModel class, since it is already exposed in the Python Scripting SAMSON Extension.

We create Python bindings for the CustomStructuralModel class in the file CustomStructuralModelPythonBindings.cpp as follows (see pybind11: Object-oriented code and pybind11: Classes for more information):

As you can see, we can expose some functions in a more pythonic way – as attributes, read-only or with a read and write access. In the last case, we need to provide getter and setter functions.

Now we can invoke the exposeCustomStructuralModel function inside the PYBIND11_EMBEDDED_MODULE macro:

Special case: creating Python bindings for functions that return/receive SAMSON physical quantities and types

SAMSON physical quantities and types (i.e., SBQuantity class and SBPhysicalVector3, SBPhysicalVector6, SBPhysicalInterval, SBDTypePhysicalIAVector3, SBPhysicalMatrix33, SBPhysicalMatrix66, SBSpatialTransform classes) are not directly exposed to Python since they are template-based. Instead, special wrappers for these classes were created and exposed to be used in Python. These wrappers allow one to fully utilize the functionality of SAMSON physical quantities and types and significantly reduce the size of the Python Scripting Extension.

List of wrapped SAMSON physical quantities and types:

  • SBDQuantityWrapper – wraps SBDQuantity, parameterized by SBUnitSystem
  • SBDTypePhysicalVector3Wrapper – wraps SBDTypePhysicalVector3, parameterized by SBDQuantityWrapper
  • SBDTypePhysicalVector6Wrapper – wraps SBDTypePhysicalVector6, parameterized by SBDQuantityWrapper
  • SBDTypePhysicalIntervalWrapper – wraps SBDTypePhysicalInterval, parameterized by SBDQuantityWrapper
  • SBDTypePhysicalIAVector3Wrapper – wraps SBDTypePhysicalIAVector3, parameterized by SBDQuantityWrapper
  • SBDTypePhysicalMatrix33Wrapper – wraps SBDTypePhysicalMatrix33, parameterized by SBDQuantityWrapper
  • SBDTypePhysicalMatrix66Wrapper – wraps SBDTypePhysicalMatrix66, parameterized by SBDQuantityWrapper
  • SBDTypeSpatialTransformWrapper – wraps SBDTypeSpatialTransform

These wrapped SAMSON physical quantities and types are exposed by Python Scripting Extension together with commonly used short names (see the tutorial on Python Scripting Units), so you do not need to create bindings for them.

Header files for these wrapper classes are provided together with the PyBindTutorial sample from SAMSON-Developers-Tutorials. You can find them in the folder PyBindTutorial/external/SAMSON-SDK-wrappers/include. The wrapper classes are named the same as the classes that they wrap with an addition of a word Wrapper at the end. For simplicity, this path is included in the CMakeLists.txt of the PyBindTutorial sample:

You can copy these wrapper files to your SAMSON Extension project if you want to create bindings for functions that return/receive SAMSON physical quantities and types.

To create Python bindings for functions which return or receive SAMSON physical quantities and types, it is necessary to create wrappers for such functions. The provided wrapper classes are simple to use: they have template constructors for the creation of a wrapped object (e.g., a SBQuantityWrapper object from a SBQuantityWrapper object) and template getter functions that return a SAMSON physical quantity or type from a wrapped object (e.g., a SBQuantity object from a SBQuantityWrapper object).

In the PyBindTutorial sample, you will find a special class with functions that return or receive different SAMSON physical quantities and types: include/UnitsExample.hpp, source/UnitsExample.cpp. These are just simple getter and setter functions to describe a technique of creating Python bindings for such functions. The Python bindings for the UnitsExample class can be found in the source/UnitsExamplePythonBindings.cpp file. The Python bindings for the UnitsExample class are created as follows:

How to create Python bindings for functions that return a SAMSON physical quantity

The SBDQuantityWrapper class is a class template with a type of the SBUnitSystem (i.e., SBUnitSystemSI, SBUnitSystemAU, SBUnitSystemDalton, SBUnitSystemElectronvolt, SBUnitSystemKilocaloriePerMole) as the template argument. This argument should coincide with SBUnitSystem type of the SBQuantity object that is being wrapped:

Let’s see how to create Python bindings for some functions from the UnitsExample class that return SBQuantity objects.

In the source/UnitsExamplePythonBindings.cpp file you will find wrappers for these functions in which SBQuantity objects are transformed to SBQuantityWrapper objects. The getter functions can be wrapped as follows:

Now we can create Python bindings for these wrapper functions:

You can use these functions in Python Scripting as follows:

Note: Since there is no connection between a SBQuantityWrapper object and a SBQuantity object that it wraps (the same for SBPhysicalVector3Wrapper, etc), getter and setter functions that return SBQuantityWrapper, SBPhysicalVector3Wrapper, etc, should not be exposed to Python as attributes via def_property(), but as functions via def(). You may also expose getter functions as read-only attributes via def_property_readonly.

How to create Python bindings for functions that receive a SAMSON physical quantity

The header file for the SBDQuantityWrapper class provides a function template that returns a SBQuantity object from a SBDQuantityWrapper object:

where the template argument is a SBQuantity type (e.g., SBQuantity::length).

Let’s see some examples:

Let’s see how to create Python bindings for some functions from the UnitsExample class that receive SBQuantity objects.

In the source/UnitsExamplePythonBindings.cpp file you will find wrappers for these functions in which SBQuantityWrapper objects are transformed to SBQuantity objects. The setter functions can be wrapped as follows:

Now we can create Python bindings for these wrapper functions:

How to create Python bindings for functions that return a SAMSON physical type

Wrapper classes for SAMSON physical types (except for SBDTypeSpatialTransformWrapper) are template classes parametrized by a type of the SBQuantityWrapper (i.e., SBQuantityWrapper, SBQuantityWrapper, SBQuantityWrapper, SBQuantityWrapper, SBQuantityWrapper). This template argument should coincide with the SBQuantity unit type of the SAMSON physical type that is being wrapped. Let’s see some examples:

Let’s see how to create Python bindings for some functions from the UnitsExample class that return SAMSON physical types.

In the source/UnitsExamplePythonBindings.cpp file you will find wrappers for these functions in which SAMSON physical types are transformed to their wrapped versions objects. The getter functions can be wrapped as follows:

Now we can create Python bindings for these wrapper functions:

How to create Python bindings for functions that receive a SAMSON physical type

Header files for wrapper classes for SAMSON physical types each, except for the SBDTypeSpatialTransformWrapper class, provide a function template that returns a SAMSON physical type from its wrapped version:

where the template arguments are of SBQuantity type.

Let’s see some examples:

Let’s see how to create Python bindings for some functions from the UnitsExample class that receive SAMSON physical types.

In the source/UnitsExamplePythonBindings.cpp file you will find wrappers for these functions in which wrapped SAMSON physical types are transformed to SAMSON physical types. The setter functions can be wrapped as follows:

Now we can create Python bindings for these wrapper functions:

Running SAMSON with Python

Please, check the following tutorials to learn how to run SAMSON from an IDE on your OS:

On Windows, a path to your Python installation should be present in the Path environment variable and you should be able to run SAMSON from the Microsoft Visual Studio or another IDE.

On Linux and Mac, to run SAMSON with Python from an IDE, you might need to provide a Python library path of your Python installation in run environment variables of your project. The Python library path should be put after paths to Qt libraries in the LD_LIBRARY_PATH environment variable of your project. See an image below (Linux, QtCreator):

Comments are closed.