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
- Building a SAMSON Extension with Python
- Creating Python bindings
- Special case: creating Python bindings for functions that return/receive SAMSON physical quantities and types
- How to create Python bindings for functions that return a SAMSON physical quantity
- How to create Python bindings for functions that receive a SAMSON physical quantity
- How to create Python bindings for functions that return a SAMSON physical type
- How to create Python bindings for functions that receive a SAMSON physical type
- Running SAMSON with Python
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:
- Building a SAMSON Extension on Windows
- Building a SAMSON Extension on MacOS
- Building a SAMSON Extension on Linux
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:
- pybind11: First steps
- pybind11: Object-oriented code
- pybind11: Classes
- pybind11: Functions
- pybind11: Return value policies
- pybind11: FAQ
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
/* 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:
- Building a SAMSON Extension on Windows
- Building a SAMSON Extension on MacOS
- Building a SAMSON Extension on Linux
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):