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:

ADD_SUBDIRECTORY( PyBindTutorial )

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.

IF( NOT DEBUG ) # Disable the creation of Python bindings in the Debug mode since Python is not supported in the Debug mode
 
	FIND_PACKAGE( PythonInterp ${PYTHON_VERSION} REQUIRED )
 
	IF ( PYTHONINTERP_FOUND )
 
	    SET(CREATE_PYTHON_BINDINGS TRUE)
 
	ENDIF ( PYTHONINTERP_FOUND )
 
ENDIF()

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).

IF ( CREATE_PYTHON_BINDINGS )
 
	# Determine the include directory: set PYTHON_INCLUDE_DIR variable
	# set PYTHON_INCLUDE_DIR variable using python: distutils.sysconfig.get_python_inc()  or  distutils.sysconfig.get_config_var(\"INCLUDEPY\")
	IF( UNIX )
		EXECUTE_PROCESS(COMMAND
			python3 -c "import sys; print(sys.prefix)"
			OUTPUT_VARIABLE  PYTHON_INSTALL_PREFIX  OUTPUT_STRIP_TRAILING_WHITESPACE)
		EXECUTE_PROCESS(COMMAND
			which python3
			OUTPUT_VARIABLE  PYTHON_EXECUTABLE  OUTPUT_STRIP_TRAILING_WHITESPACE)
		EXECUTE_PROCESS(COMMAND
			python3 -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())"
			OUTPUT_VARIABLE  PYTHON_INCLUDE_DIR  OUTPUT_STRIP_TRAILING_WHITESPACE)
	ELSE( UNIX )
		# Windows: python3 is named python in Anaconda
		EXECUTE_PROCESS(COMMAND
			python -c "import sys; print(sys.prefix)"
			OUTPUT_VARIABLE  PYTHON_INSTALL_PREFIX  OUTPUT_STRIP_TRAILING_WHITESPACE)
		EXECUTE_PROCESS(COMMAND
			where python
			OUTPUT_VARIABLE  PYTHON_EXECUTABLE  OUTPUT_STRIP_TRAILING_WHITESPACE)
		EXECUTE_PROCESS(COMMAND
			python -c "from distutils.sysconfig import get_python_inc; print(get_python_inc())"
			OUTPUT_VARIABLE  PYTHON_INCLUDE_DIR  OUTPUT_STRIP_TRAILING_WHITESPACE)
	ENDIF( UNIX )
 
	# Determine the Python library path: set PYTHON_LIBRARY variable
	IF( UNIX )
		IF( APPLE )
			# Anaconda installation
			#EXECUTE_PROCESS(COMMAND python3 -c "import sys; from distutils import sysconfig; sys.stdout.write(\"/\".join(map(sysconfig.get_config_var, (\"LIBDIR\", \"LDLIBRARY\"))))"
			#    OUTPUT_VARIABLE PYTHON_LIBRARY        OUTPUT_STRIP_TRAILING_WHITESPACE)
 
			# Homebrew installation
			EXECUTE_PROCESS(COMMAND
				python3 -c "import sys; from distutils import sysconfig; sys.stdout.write(\"/\".join( [sysconfig.get_config_var(\"LIBDIR\"), \"libpython3.6m.dylib\"] ) )"
				OUTPUT_VARIABLE  PYTHON_LIBRARY  OUTPUT_STRIP_TRAILING_WHITESPACE)
		ELSE( APPLE )
			EXECUTE_PROCESS(COMMAND
				python3 -c "import sys; from distutils import sysconfig; sys.stdout.write(\"/\".join(map(sysconfig.get_config_var, (\"LIBDIR\", \"INSTSONAME\"))))"
				OUTPUT_VARIABLE  PYTHON_LIBRARY  OUTPUT_STRIP_TRAILING_WHITESPACE)
		ENDIF( APPLE )
	ELSE( UNIX )
		# Windows: python3 is named python in Anaconda
		EXECUTE_PROCESS(COMMAND
			python -c "import sys; from distutils import sysconfig; print(sysconfig.get_config_var(\"VERSION\"))"
			OUTPUT_VARIABLE  PYTHON_CONFIG_VERSION  OUTPUT_STRIP_TRAILING_WHITESPACE)
		SET (PYTHON_LIBRARY "${PYTHON_INSTALL_PREFIX}\\libs\\python${PYTHON_CONFIG_VERSION}.lib")
	ENDIF( UNIX )
 
	MESSAGE("Python install prefix: " ${PYTHON_INSTALL_PREFIX} )
	MESSAGE("Python executable:     " ${PYTHON_EXECUTABLE} )
	MESSAGE("Python include dir:    " ${PYTHON_INCLUDE_DIR} )
	MESSAGE("Python lib:            " ${PYTHON_LIBRARY} )
 
	# Set path to pybind11 headers
	SET(PYBIND11_INCLUDE_DIRS "./external/pybind11/include/")
 
	# include paths to python and pybind11 headers
	INCLUDE_DIRECTORIES(${PYTHON_INCLUDE_DIR} ${PYBIND11_INCLUDE_DIRS})
 
	# This define is used in the code to determine whether Python bindings should be created or not
	add_definitions(-DCREATE_PYTHON_BINDINGS)
 
ELSE ( CREATE_PYTHON_BINDINGS )
 
	SET (PYTHON_LIBRARY "")
 
ENDIF ( CREATE_PYTHON_BINDINGS )

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:

TARGET_LINK_LIBRARIES (	${OUTPUT_NAME}
	                ${QT_LIBRARIES}
			${PYTHON_LIBRARY}
			${OPENGL_LIBRARY}
			${SAMSON_SDK_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”.

#pragma once
 
#ifdef CREATE_PYTHON_BINDINGS
 
#include "pybind11/pybind11.h"
#include "pybind11/embed.h"
 
namespace py = pybind11;
 
// Must be defined in the global scope.
// The first parameter is the name of the module (without quotes)
// The second parameter is the variable that will be used as the interface to add functions and classes to the module.
PYBIND11_EMBEDDED_MODULE(SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4, m) {
 
	m.doc() = "SEPyBindTutorial from SAMSON Developers tutorial";	// module docstring
 
	m.def("moduleName", [](){ return "pybindtutorial"; });		// the module name - preferably the SAMSON Extension name
 
}
 
#endif //CREATE_PYTHON_BINDINGS

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:

import SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4 as pybindtutorial
 
pybindtutorial.moduleName()

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.

void exposeSEPyBindTutorialApp(py::module& m) {
 
	m.def("addCustomStructuralModel", [](){
			SEPyBindTutorialApp* app = new SEPyBindTutorialApp();
			app->addCustomStructuralModel();
		},
		"Add a custom structural model with a custom group");
 
}

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:

import SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4 as pybindtutorial
 
pybindtutorial.addCustomStructuralModel()

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

void exposeSEPyBindTutorialApp(py::module& m) {
 
	py::class_<SEPyBindTutorialApp> c(m, "SEPyBindTutorialApp", "The SEPyBindTutorialApp class");
 
	// constructor
	c.def(py::init<>(), "Constructs a custom structural group");
 
	// functions
	c.def("addCustomStructuralModel", &SEPyBindTutorialApp::addCustomStructuralModel, "Add a custom structural model with a custom group");
 
}

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

import SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4 as pybindtutorial
 
tutorialApp = pybindtutorial.SEPyBindTutorialApp() # create a class instance
tutorialApp.addCustomStructuralModel()             # call a function on this instance

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

PYBIND11_EMBEDDED_MODULE(SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4, m) {
 
	// ...
 
	// call functions that expose functionality of the SAMSON Extension
	exposeSEPyBindTutorialApp(m);
 
}

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):

void exposeCustomStructuralModel(py::module& m) {
 
	py::class_<
			CustomStructuralModel,						/* the class */
			std::unique_ptr<CustomStructuralModel, py::nodelete>,		/* the class type */
			SBStructuralModel						/* the base class */
			>
			c(m,								/* pybind11::module */
			  "CustomStructuralModel",					/* the class name in python*/
			  R"(This class describes a custom structural model from SEPyBindTutorial Extension)" /* the docstring */
			  );
 
	// constructors
 
	c.def(py::init<>(), "Constructs a custom structural model");
 
	// attributes
 
	// read-only attributes
	c.def_property_readonly("hasCustomComment", &CustomStructuralModel::hasCustomComment, "Returns true when the custom model's custom comment is set");
	// read-and-write attributes
	c.def_property("customComment", &CustomStructuralModel::getCustomComment, &CustomStructuralModel::setCustomComment, "A custom comment");
 
	// functions
 
	c.def("clearCustomComment", &CustomStructuralModel::clearCustomComment, "Clears the custom comment");
 
}

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:

PYBIND11_EMBEDDED_MODULE(SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4, m) {
 
	// ...
 
	// call functions that expose the functionality of the SAMSON Extension
	// ...
	exposeCustomStructuralModel(m);
 
}

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:

	# Set path to headers for wrappers for SAMSON physical quantities and types
	SET(SAMSONSDKWRAPPERS_INCLUDE_DIRS "./external/SAMSON-SDK-wrappers/include/")
	INCLUDE_DIRECTORIES(${SAMSONSDKWRAPPERS_INCLUDE_DIRS})

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:

/* File: source/UnitsExamplePythonBindings.cpp */
 
/* ... */
 
void exposeUnitsExample(py::module& m) {
 
	// The py::class_ creates bindings for a C++ class
	py::class_<UnitsExample> c(m, "UnitsExample", R"(An example of a class which has functions that return/receive SAMSON Units and Types)");
 
	// constructors
	c.def(py::init<>(), "Constructor");
 
	/* ... */
 
}

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:

/* SI units */
SBQuantity::dimensionless dimensionless;
// SBQuantityWrapperSI is a shortname for SBDQuantityWrapper<SBUnitSystemSI>
SBDQuantityWrapper<SBUnitSystemSI> dimensionlessWrapped = SBQuantityWrapperSI(dimensionless);
SBQuantity::length length;
SBDQuantityWrapper<SBUnitSystemSI> lengthWrapped = SBQuantityWrapperSI(length);
 
/* AU units */
SBQuantity::auMass auMass;
// SBQuantityWrapperAU is a shortname for SBDQuantityWrapper<SBUnitSystemAU>
SBDQuantityWrapper<SBUnitSystemAU> auMassWrapped = SBQuantityWrapperAU(auMass);
 
/* Dalton units */
SBQuantity::dalton dalton;
// SBQuantityWrapperDalton is a shortname for SBDQuantityWrapper<SBUnitSystemDalton>
SBDQuantityWrapper<SBUnitSystemDalton> daltonWrapped = SBQuantityWrapperDalton(dalton);
 
/* electronvolt units */
SBQuantity::electronvolt energyEv;
// SBQuantityWrapperElectronvolt is a shortname for SBDQuantityWrapper<SBUnitSystemElectronvolt>
SBDQuantityWrapper<SBUnitSystemElectronvolt> energyEvWrapped = SBQuantityWrapperElectronvolt(energyEv);
 
/* kilocaloriePerMole units */
SBQuantity::kilocaloriePerMole kcalPerMol;
// SBQuantityWrapperKilocaloriePerMole is a shortname for SBDQuantityWrapper<SBUnitSystemKilocaloriePerMole>
SBDQuantityWrapper<SBUnitSystemKilocaloriePerMole> energyEvWrapped = SBQuantityWrapperKilocaloriePerMole(kcalPerMol);

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

/* File: include/UnitsExample.hpp */
/* Class: UnitsExample */
 
/* ... */
 
class UnitsExample {
 
public:
 
	/* ... */
 
	SBQuantity::dimensionless const&	getDimensionless();
	SBQuantity::length const&		getLength();
	SBQuantity::auMass const&		getAuMass();
 
	/* ... */
 
};

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:

/* File: source/UnitsExamplePythonBindings.cpp */
 
// include headers with the wrapper for SAMSON physical quantities for use in Python
#include "SBDQuantityWrapper.hpp"
 
SBDQuantityWrapper<SBUnitSystemSI> getDimensionlessWrapper(UnitsExample& obj) {
 
	// create a SBDQuantityWrapper<SBUnitSystemSI> object from SBQuantity and return it
	return SBDQuantityWrapper<SBUnitSystemSI>(obj.getDimensionless());
 
}
 
SBQuantityWrapperSI getLengthWrapper(UnitsExample& obj) {
 
	// create a SBDQuantityWrapper<SBUnitSystemSI> object from SBQuantity and return it
	return SBQuantityWrapperSI(obj.getLength());
 
}
 
SBDQuantityWrapper<SBUnitSystemAU> getAuMassWrapper(UnitsExample& obj) {
 
	// create a SBDQuantityWrapper<SBUnitSystemAU> object from SBQuantity and return it
	return SBDQuantityWrapper<SBUnitSystemAU>(obj.getAuMass());
 
}

Now we can create Python bindings for these wrapper functions:

/* File: source/UnitsExamplePythonBindings.cpp */
 
/* ... */
 
void exposeUnitsExample(py::module& m) {
 
	// The py::class_ creates bindings for a C++ class
	py::class_<UnitsExample> c(m, "UnitsExample", R"(An example of a class which has functions that return/receive SAMSON Units and Types)");
 
	// constructors
	c.def(py::init<>(), "Constructor");
 
	/* ... */
 
	c.def("getDimensionless", &getDimensionlessWrapper);
	c.def("getLength", &getLengthWrapper);
	c.def("getAuMass", &getAuMassWrapper);
 
	/* ... */
 
}

You can use these functions in Python Scripting as follows:

import SE_F2078F9E_F2CB_BA72_EE86_1E01A10B63D4 as pybindtutorial
ex = pybindtutorial.UnitsExample()
ex.getDimensionless()
ex.getAuMass()

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:

getSBQuantity<Quantity>(...)

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

Let’s see some examples:

/* SI units */
SBDQuantityWrapper<SBUnitSystemSI> dimensionlessWrapped;
SBQuantity::dimensionless dimensionless = getSBQuantity<SBQuantity::dimensionless>(dimensionlessWrapped);
SBQuantityWrapperSI lengthWrapped;
SBQuantity::length length = getSBQuantity<SBQuantity::length>(lengthWrapped);
 
/* AU units */
SBDQuantityWrapper<SBUnitSystemAU> auMassWrapped;
SBQuantity::auMass auMass = getSBQuantity<SBQuantity::auMass>(auMassWrapped);
 
/* Dalton units */
SBDQuantityWrapper<SBUnitSystemDalton> daltonWrapped;
SBQuantity::dalton dalton = getSBQuantity<SBQuantity::dalton>(daltonWrapped);
 
/* electronvolt units */
SBDQuantityWrapper<SBUnitSystemElectronvolt> energyEvWrapped;
SBQuantity::electronvolt energyEv = getSBQuantity<SBQuantity::electronvolt>(energyEvWrapped);
 
/* kilocaloriePerMole units */
SBDQuantityWrapper<SBUnitSystemKilocaloriePerMole> energyEvWrapped;
SBQuantity::kilocaloriePerMole kcalPerMol = getSBQuantity<SBQuantity::kilocaloriePerMole>(energyEvWrapped);

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

/* File: include/UnitsExample.hpp */
/* Class: UnitsExample */
 
/* ... */
 
class UnitsExample {
 
public:
 
	/* ... */
 
	void					setDimensionless(const SBQuantity::dimensionless& v);
	void					setLength(const SBQuantity::length& v);
	void					setAuMass(const SBQuantity::auMass& v);
 
	/* ... */
 
};

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:

/* File: source/UnitsExamplePythonBindings.cpp */
 
void setDimensionlessWrapper(UnitsExample& obj, SBDQuantityWrapper<SBUnitSystemSI>& u) {
 
	// create a SBQuantity object from SBDQuantityWrapper and pass it into a specific function
	obj.setDimensionless( getSBQuantity<SBQuantity::dimensionless>(u) );
 
}
 
void setLengthWrapper(UnitsExample& obj, SBQuantityWrapperSI& u) {
 
	// create a SBQuantity object from SBDQuantityWrapper and pass it into a specific function
	obj.setLength( getSBQuantity<SBQuantity::length>(u) );
 
}
 
void setAuMassWrapper(UnitsExample& obj, SBDQuantityWrapper<SBUnitSystemAU>& u) {
 
	// create a SBQuantity object from SBDQuantityWrapper and pass it into a specific function
	obj.setAuMass( getSBQuantity<SBQuantity::auMass>(u) );
 
}

Now we can create Python bindings for these wrapper functions:

/* File: source/UnitsExamplePythonBindings.cpp */
 
/* ... */
 
void exposeUnitsExample(py::module& m) {
 
	// The py::class_ creates bindings for a C++ class
	py::class_<UnitsExample> c(m, "UnitsExample", R"(An example of a class which has functions that return/receive SAMSON Units and Types)");
 
	// constructors
	c.def(py::init<>(), "Constructor");
 
	/* ... */
 
	c.def("setDimensionless", &setDimensionlessWrapper);
	c.def("setLength", &setLengthWrapper);
	c.def("setAuMass", &setAuMassWrapper);
 
	/* ... */
 
}

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:

/* Example objects for SBVector3 */
SBVector3 dimensionlessVec3;
// SBPhysicalVector3WrapperSI is a shortname for SBDTypePhysicalVector3Wrapper<SBDQuantityWrapperSI>
SBTypePhysicalVector3Wrapper<SBQuantityWrapper<SBUnitSystemSI>> dimensionlessVec3Wrapped = SBPhysicalVector3WrapperSI(dimensionlessVec3);
SBPosition3 position3;
SBTypePhysicalVector3Wrapper<SBQuantityWrapperSI> position3Wrapped = SBPhysicalVector3WrapperSI(position3);
 
/* Example objects for SBVector6 */
SBVelocity6 velocity6;
// SBPhysicalVector6WrapperSI is a shortname for SBTypePhysicalVector6Wrapper<SBDQuantityWrapperSI>
SBTypePhysicalVector6Wrapper<SBDQuantityWrapperSI> velocity6Wrapped = SBPhysicalVector6WrapperSI(velocity6);
 
/* Example objects for SBPhysicalMatrix33 */
SBInverseMass33 inverseMass33;
// SBPhysicalMatrix33WrapperSI is a shortname for SBTypePhysicalMatrix33Wrapper<SBDQuantityWrapperSI>
SBTypePhysicalMatrix33Wrapper<SBDQuantityWrapperSI> inverseMass33Wrapped = SBPhysicalMatrix33WrapperSI(inverseMass33);
 
/* Example objects for SBPhysicalMatrix66 */
SBInertia66 inertia66;
// SBPhysicalMatrix66WrapperSI is a shortname for SBTypePhysicalMatrix66Wrapper<SBDQuantityWrapperSI>
SBTypePhysicalMatrix66Wrapper<SBDQuantityWrapperSI> inertia66Wrapped = SBPhysicalMatrix66WrapperSI(inertia66);
 
/* Example objects for SBSpatialTransform */
SBSpatialTransform spatialTransform;
SBSpatialTransformWrapper spatialTransformWrapped = SBSpatialTransformWrapper(spatialTransform);

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

/* File: include/UnitsExample.hpp */
/* Class: UnitsExample */
 
/* ... */
 
class UnitsExample {
 
public:
 
	/* ... */
 
	SBVector3 const&			getVector3();
	SBPosition3 const&			getPosition3();
	SBVelocity6 const&			getVelocity6();
	SBInverseMass33 const&			getInverseMass33();
	SBInertia66 const&			getInertia66();
	SBSpatialTransform const&		getSpatialTransform();
 
	/* ... */
 
};

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:

/* File: source/UnitsExamplePythonBindings.cpp */
 
// include headers with wrappers for SAMSON physical types (SBDType*) for use in Python
#include "SBDTypePhysicalVector3Wrapper.hpp"
#include "SBDTypePhysicalVector6Wrapper.hpp"
#include "SBDTypePhysicalIntervalWrapper.hpp"
#include "SBDTypePhysicalIAVector3Wrapper.hpp"
#include "SBDTypePhysicalMatrix33Wrapper.hpp"
#include "SBDTypePhysicalMatrix66Wrapper.hpp"
#include "SBDTypeSpatialTransformWrapper.hpp"
 
SBPhysicalVector3WrapperSI getVector3Wrapper(UnitsExample& obj) {
 
	// create a SBDTypePhysicalVector3Wrapper<SBUnitSystemSI> object from SBDTypePhysicalVector3 and return it
	return SBPhysicalVector3WrapperSI(obj.getVector3());
 
}
 
SBPhysicalVector3WrapperSI getPosition3Wrapper(UnitsExample& obj) {
 
	// create a SBDTypePhysicalVector3Wrapper<SBUnitSystemSI> object from SBDTypePhysicalVector3 and return it
	return SBPhysicalVector3WrapperSI(obj.getPosition3());
 
}
 
SBPhysicalVector6WrapperSI getVelocity6Wrapper(UnitsExample& obj) {
 
	// create a SBPhysicalVector6WrapperSI object from SBDTypePhysicalVector6 and return it
	return SBPhysicalVector6WrapperSI(obj.getVelocity6());
 
}
 
SBPhysicalMatrix33WrapperSI getInverseMass33Wrapper(UnitsExample& obj) {
 
	return SBPhysicalMatrix33WrapperSI(obj.getInverseMass33());
 
}
 
SBPhysicalMatrix66WrapperSI getInertia66Wrapper(UnitsExample& obj) {
 
	return SBPhysicalMatrix66WrapperSI(obj.getInertia66());
 
}
 
SBSpatialTransformWrapper getSpatialTransformWrapper(UnitsExample& obj) {
 
	return SBSpatialTransformWrapper(obj.getSpatialTransform());
 
}

Now we can create Python bindings for these wrapper functions:

/* File: source/UnitsExamplePythonBindings.cpp */
 
/* ... */
 
void exposeUnitsExample(py::module& m) {
 
	// The py::class_ creates bindings for a C++ class
	py::class_<UnitsExample> c(m, "UnitsExample", R"(An example of a class which has functions that return/receive SAMSON Units and Types)");
 
	// constructors
	c.def(py::init<>(), "Constructor");
 
	/* ... */
 
	c.def("getVector3", &getVector3Wrapper);
	c.def("getPosition3", &getPosition3Wrapper);
	c.def("getVelocity6", &getVelocity6Wrapper);
	c.def("getInverseMass33", &getInverseMass33Wrapper);
	c.def("getInertia66", &getInertia66Wrapper);
	c.def("getSpatialTransform", &getSpatialTransformWrapper);
 
	/* ... */
 
}

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:

  • getSBPhysicalVector3<Quantity>(...)
  • getSBPhysicalVector6<QuantityA, QuantityL>(...)
  • getSBPhysicalInterval<Quantity>(...)
  • getSBPhysicalIAVector3<Quantity>(...)
  • getSBPhysicalMatrix33<Quantity>(...)
  • getSBPhysicalMatrix66<Quantity00, Quantity01, Quantity10, Quantity11>(...)

where the template arguments are of SBQuantity type.

Let’s see some examples:

/* Example objects for SBVector3 */
SBTypePhysicalVector3Wrapper<SBQuantityWrapperSI> dimensionlessVec3Wrapped;
SBVector3 dimensionlessVec3 = getSBPhysicalVector3<SBQuantity::dimensionless>(dimensionlessVec3Wrapped);
SBPhysicalVector3WrapperSI position3Wrapped;
SBPosition3 position3 = getSBPhysicalVector3<SBQuantity::position>(position3Wrapped);
 
/* Example objects for SBVector6 */
SBPhysicalVector6WrapperSI velocity6Wrapped;
SBVelocity6 velocity6 = getSBPhysicalVector6<SBQuantity::inverseTime, SBQuantity::velocity>(velocity6Wrapped);
 
/* Example objects for SBPhysicalMatrix33 */
SBPhysicalMatrix33WrapperSI inverseMass33Wrapped;
SBInverseMass33 inverseMass33 = getSBPhysicalMatrix33<SBQuantity::inverseMass>(inverseMass33Wrapped);
 
/* Example objects for SBPhysicalMatrix66 */
SBPhysicalMatrix66WrapperSI inertia66Wrapped;
SBInertia66 inertia66 = getSBPhysicalMatrix66<SBQuantity::momentOfInertia, SBQuantity::lengthMass, SBQuantity::lengthMass, SBQuantity::mass>(inertia66Wrapped);
 
/* Example objects for SBSpatialTransform */
SBSpatialTransformWrapper spatialTransformWrapped;
SBSpatialTransform spatialTransform = spatialTransformWrapped.toSBSpatialTransform();

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

/* File: include/UnitsExample.hpp */
/* Class: UnitsExample */
 
/* ... */
 
class UnitsExample {
 
public:
 
	/* ... */
 
	void					setVector3(const SBVector3& v);
	void					setPosition3(const SBPosition3& v);
	void					setVelocity6(const SBVelocity6& v);
	void					setInverseMass33(const SBInverseMass33& v);
	void					setInertia66(const SBInertia66& v);
	void					setSpatialTransform(const SBSpatialTransform& v);
 
	/* ... */
 
};

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:

/* File: source/UnitsExamplePythonBindings.cpp */
 
void setVector3Wrapper(UnitsExample& obj, SBPhysicalVector3WrapperSI& u) {
 
	// create a SBDTypePhysicalVector3 object from SBDTypePhysicalVector3Wrapper and pass it into a specific function
	obj.setVector3( getSBPhysicalVector3<SBQuantity::dimensionless>(u) );
 
}
 
void setPosition3Wrapper(UnitsExample& obj, SBPhysicalVector3WrapperSI& u) {
 
	// create a SBDTypePhysicalVector3 object from SBDTypePhysicalVector3Wrapper and pass it into a specific function
	obj.setPosition3( getSBPhysicalVector3<SBQuantity::position>(u) );
 
}
void setVelocity6Wrapper(UnitsExample& obj, SBPhysicalVector6WrapperSI& u) {
 
	// create a SBDTypePhysicalVector6 object from SBPhysicalVector6WrapperSI and pass it into a specific function
	obj.setVelocity6( getSBPhysicalVector6<SBQuantity::inverseTime, SBQuantity::velocity>(u) );
 
}
 
void setInverseMass33Wrapper(UnitsExample& obj, SBPhysicalMatrix33WrapperSI& u) {
 
	obj.setInverseMass33( getSBPhysicalMatrix33<SBQuantity::inverseMass>(u) );
 
}
 
void setInertia66Wrapper(UnitsExample& obj, SBPhysicalMatrix66WrapperSI& u) {
 
	obj.setInertia66( getSBPhysicalMatrix66<SBQuantity::momentOfInertia, SBQuantity::lengthMass, SBQuantity::lengthMass, SBQuantity::mass>(u) );
 
}
 
void setSpatialTransformWrapper(UnitsExample& obj, SBSpatialTransformWrapper& u) {
 
	obj.setSpatialTransform( u.toSBSpatialTransform() );
 
}

Now we can create Python bindings for these wrapper functions:

/* File: source/UnitsExamplePythonBindings.cpp */
 
/* ... */
 
void exposeUnitsExample(py::module& m) {
 
	// The py::class_ creates bindings for a C++ class
	py::class_<UnitsExample> c(m, "UnitsExample", R"(An example of a class which has functions that return/receive SAMSON Units and Types)");
 
	// constructors
	c.def(py::init<>(), "Constructor");
 
	/* ... */
 
	c.def("setVector3", &setVector3Wrapper);
	c.def("setPosition3", &setPosition3Wrapper);
	c.def("setVelocity6", &setVelocity6Wrapper);
	c.def("setInverseMass33", &setInverseMass33Wrapper);
	c.def("setInertia66", &setInertia66Wrapper);
	c.def("setSpatialTransform", &setSpatialTransformWrapper);
 
	/* ... */
 
}

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.