From d78593a346ca1c8c665dcecd85768853b615971e Mon Sep 17 00:00:00 2001 From: Carlos Cardenas <44415073+carloscp3009@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:25:09 +0200 Subject: [PATCH] Add Python bindings for human state-related data structures (#379) Co-authored-by: Silvio Traversaro --- CMakeLists.txt | 5 ++ bindings/CMakeLists.txt | 69 +++++++++++++++++++ bindings/python/CMakeLists.txt | 34 +++++++++ bindings/python/hde.cpp | 18 +++++ bindings/python/msgs/CMakeLists.txt | 11 +++ .../include/hde/bindings/msgs/BufferedPort.h | 44 ++++++++++++ .../include/hde/bindings/msgs/HumanState.h | 19 +++++ .../msgs/include/hde/bindings/msgs/Module.h | 19 +++++ bindings/python/msgs/src/HumanState.cpp | 38 ++++++++++ bindings/python/msgs/src/Module.cpp | 20 ++++++ cmake/AddHDEPythonModule.cmake | 46 +++++++++++++ 11 files changed, 323 insertions(+) create mode 100644 bindings/CMakeLists.txt create mode 100644 bindings/python/CMakeLists.txt create mode 100644 bindings/python/hde.cpp create mode 100644 bindings/python/msgs/CMakeLists.txt create mode 100644 bindings/python/msgs/include/hde/bindings/msgs/BufferedPort.h create mode 100644 bindings/python/msgs/include/hde/bindings/msgs/HumanState.h create mode 100644 bindings/python/msgs/include/hde/bindings/msgs/Module.h create mode 100644 bindings/python/msgs/src/HumanState.cpp create mode 100644 bindings/python/msgs/src/Module.cpp create mode 100644 cmake/AddHDEPythonModule.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index f24ed6454..5f341cca8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ find_package(YCM REQUIRED) # Import cmake utilities list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) include(AddWarningsConfigurationToTarget) +include(AddHDEPythonModule) # To build shared libraries in Windows, we set CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS to TRUE # See https://cmake.org/cmake/help/v3.4/variable/CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS.html @@ -51,6 +52,9 @@ endif() set(CMAKE_POSITION_INDEPENDENT_CODE ON) +# Flag to enable python bindings +option(HDE_COMPILE_PYTHON_BINDINGS "Flag that enables building the bindings" OFF) + # Plugins are force to be Shared/Dynamic Library set(YARP_FORCE_DYNAMIC_PLUGINS ON) @@ -86,6 +90,7 @@ add_subdirectory(modules) add_subdirectory(servers) add_subdirectory(clients) add_subdirectory(publishers) +add_subdirectory(bindings) add_subdirectory(HumanDynamicsEstimationLibrary) include(InstallBasicPackageFiles) diff --git a/bindings/CMakeLists.txt b/bindings/CMakeLists.txt new file mode 100644 index 000000000..acfbcd928 --- /dev/null +++ b/bindings/CMakeLists.txt @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +# SPDX-License-Identifier: BSD-3-Clause + +if(HDE_COMPILE_PYTHON_BINDINGS) + + find_package(Python3 3.6 COMPONENTS Interpreter Development REQUIRED) + + find_package(pybind11 2.4.3 CONFIG REQUIRED) + + set(NEW_LINE "\n") + + option(HDE_DETECT_ACTIVE_PYTHON_SITEPACKAGES + "Do you want HDE to detect and use the active site-package directory? (it could be a system dir)" + FALSE) + + # Install the resulting Python package for the active interpreter + if(NOT DEFINED HDE_PYTHON_INSTALL_DIR) + if(HDE_DETECT_ACTIVE_PYTHON_SITEPACKAGES) + set(HDE_PYTHON_INSTALL_DIR ${Python3_SITELIB}) + else() + execute_process(COMMAND ${Python3_EXECUTABLE} -c "from distutils import sysconfig; print(sysconfig.get_python_lib(1,0,prefix=''))" + OUTPUT_VARIABLE _PYTHON_INSTDIR) + string(STRIP ${_PYTHON_INSTDIR} _PYTHON_INSTDIR_CLEAN) + set(HDE_PYTHON_INSTALL_DIR ${_PYTHON_INSTDIR_CLEAN}) + endif() + endif() + set(PYTHON_INSTDIR ${HDE_PYTHON_INSTALL_DIR}/hde) + + # Folder of the Python package within the build tree. + # It is used for the Python tests. + set(HDE_PYTHON_PACKAGE "${CMAKE_BINARY_DIR}/hde") + + # Add the bindings directory + add_subdirectory(python) + + # Create the __init__.py file + file(GENERATE + OUTPUT "${HDE_PYTHON_PACKAGE}/__init__.py" + CONTENT "from .bindings import *${NEW_LINE}") + + # Install the __init__.py file + install(FILES "${HDE_PYTHON_PACKAGE}/__init__.py" + DESTINATION ${PYTHON_INSTDIR}) + + # Install pip metadata files to ensure that HDE installed via CMake is listed by pip list + # See https://packaging.python.org/specifications/recording-installed-packages/ + # and https://packaging.python.org/en/latest/specifications/core-metadata/#core-metadata + option(HDE_PYTHON_PIP_METADATA_INSTALL "Use CMake to install Python pip metadata. Set to off if some other tool already installs it." ON) + mark_as_advanced(HDE_PYTHON_PIP_METADATA_INSTALL) + set(HDE_PYTHON_PIP_METADATA_INSTALLER "cmake" CACHE STRING "Specify the string to identify the pip Installer. Default: cmake, change this if you are using another tool.") + mark_as_advanced(HDE_PYTHON_PIP_METADATA_INSTALLER) + if(HDE_PYTHON_PIP_METADATA_INSTALL) + get_filename_component(PYTHON_METADATA_PARENT_DIR ${PYTHON_INSTDIR} DIRECTORY) + if(WIN32) + set(NEW_LINE "\n\r") + else() + set(NEW_LINE "\n") + endif() + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/METADATA "") + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/METADATA "Metadata-Version: 2.1${NEW_LINE}") + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/METADATA "Name: hde${NEW_LINE}") + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/METADATA "Version: ${hde_VERSION}${NEW_LINE}") + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/INSTALLER "${HDE_PYTHON_PIP_METADATA_INSTALLER}${NEW_LINE}") + install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/METADATA" "${CMAKE_CURRENT_BINARY_DIR}/INSTALLER" + DESTINATION ${PYTHON_METADATA_PARENT_DIR}/hde-${hde_VERSION}.dist-info) + endif() + +endif() \ No newline at end of file diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt new file mode 100644 index 000000000..bc92d2421 --- /dev/null +++ b/bindings/python/CMakeLists.txt @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(msgs) + +get_property(pybind_headers GLOBAL PROPERTY pybind_headers) +get_property(pybind_sources GLOBAL PROPERTY pybind_sources) +get_property(pybind_include_dirs GLOBAL PROPERTY pybind_include_dirs) +get_property(pybind_link_libraries GLOBAL PROPERTY pybind_link_libraries) + +pybind11_add_module(pybind11_hde MODULE + hde.cpp + ${pybind_sources} + ${pybind_headers} + ) + +target_include_directories(pybind11_hde PUBLIC "$") + +target_link_libraries(pybind11_hde PRIVATE + ${pybind_link_libraries}) + +# # The generated Python dynamic module must have the same name as the pybind11 +# # module, i.e. `bindings`. +set_target_properties(pybind11_hde PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "${HDE_PYTHON_PACKAGE}" + OUTPUT_NAME "bindings") + +# Output package is: +# +# hde +# |-- __init__.py (generated from main bindings CMake file) +# `-- bindings. + +install(TARGETS pybind11_hde DESTINATION ${PYTHON_INSTDIR}) \ No newline at end of file diff --git a/bindings/python/hde.cpp b/bindings/python/hde.cpp new file mode 100644 index 000000000..79848923b --- /dev/null +++ b/bindings/python/hde.cpp @@ -0,0 +1,18 @@ +// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +// SPDX-License-Identifier: BSD-3-Clause + + +#include + +#include + +// Create the Python module +PYBIND11_MODULE(bindings, m) +{ + namespace py = ::pybind11; + + m.doc() = "Human dynamics estimation bindings"; + + py::module msgModule = m.def_submodule("msg"); + hde::bindings::msgs::CreateModule(msgModule); +} \ No newline at end of file diff --git a/bindings/python/msgs/CMakeLists.txt b/bindings/python/msgs/CMakeLists.txt new file mode 100644 index 000000000..aa723e9f4 --- /dev/null +++ b/bindings/python/msgs/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +# SPDX-License-Identifier: BSD-3-Clause + + +set(H_PREFIX include/hde/bindings/msgs) + +add_hde_python_module( + NAME MsgsBindings + SOURCES src/HumanState.cpp src/Module.cpp + HEADERS ${H_PREFIX}/HumanState.h ${H_PREFIX}/BufferedPort.h ${H_PREFIX}/Module.h + LINK_LIBRARIES HumanDynamicsEstimation::HumanStateMsg YARP::YARP_os) \ No newline at end of file diff --git a/bindings/python/msgs/include/hde/bindings/msgs/BufferedPort.h b/bindings/python/msgs/include/hde/bindings/msgs/BufferedPort.h new file mode 100644 index 000000000..ec6d398d0 --- /dev/null +++ b/bindings/python/msgs/include/hde/bindings/msgs/BufferedPort.h @@ -0,0 +1,44 @@ +// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef HDE_BINDINGS_MSGS_BUFFERED_PORT_H +#define HDE_BINDINGS_MSGS_BUFFERED_PORT_H + +#include + +#include +#include + +#include + +namespace hde { + namespace bindings { + namespace msgs { + + template + void CreateBufferedPort(pybind11::module& module, const std::string& name) + { + namespace py = ::pybind11; + py::class_<::yarp::os::BufferedPort>(module, name.c_str()) + .def(py::init()) + .def("open", + py::overload_cast(&::yarp::os::BufferedPort::open), + py::arg("name")) + .def("close", &::yarp::os::BufferedPort::close) + .def("isClosed", &::yarp::os::BufferedPort::isClosed) + .def("prepare", + &::yarp::os::BufferedPort::prepare, + py::return_value_policy::reference_internal) + .def("write", + &::yarp::os::BufferedPort::write, + py::arg("forceStrict") = false) + .def("read", + &::yarp::os::BufferedPort::read, + py::arg("shouldWait") = true, + py::return_value_policy::reference_internal); + } + } // namespace msgs + } // namespace bindings +} // namespace hde + +#endif // HDE_BINDINGS_MSGS_BUFFERED_PORT_H \ No newline at end of file diff --git a/bindings/python/msgs/include/hde/bindings/msgs/HumanState.h b/bindings/python/msgs/include/hde/bindings/msgs/HumanState.h new file mode 100644 index 000000000..a6ed129ef --- /dev/null +++ b/bindings/python/msgs/include/hde/bindings/msgs/HumanState.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef HDE_BINDINGS_MSGS_HUMAN_STATE_H +#define HDE_BINDINGS_MSGS_HUMAN_STATE_H + +#include + +namespace hde { + namespace bindings { + namespace msgs { + + void CreateHumanState(pybind11::module& module); + + } // namespace msgs + } // namespace bindings +} // namespace hde + +#endif // HDE_BINDINGS_MSGS_HUMAN_STATE_H \ No newline at end of file diff --git a/bindings/python/msgs/include/hde/bindings/msgs/Module.h b/bindings/python/msgs/include/hde/bindings/msgs/Module.h new file mode 100644 index 000000000..6cedd1b6f --- /dev/null +++ b/bindings/python/msgs/include/hde/bindings/msgs/Module.h @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +// SPDX-License-Identifier: BSD-3-Clause + +#ifndef HDE_BINDINGS_MSGS_MODULE_H +#define HDE_BINDINGS_MSGS_MODULE_H + +#include + +namespace hde { + namespace bindings { + namespace msgs { + + void CreateModule(pybind11::module& module); + + } // namespace msgs + } // namespace bindings +} // namespace hde + +#endif // HDE_BINDINGS_MSGS_MODULE_H \ No newline at end of file diff --git a/bindings/python/msgs/src/HumanState.cpp b/bindings/python/msgs/src/HumanState.cpp new file mode 100644 index 000000000..95aa7477d --- /dev/null +++ b/bindings/python/msgs/src/HumanState.cpp @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +// SPDX-License-Identifier: BSD-3-Clause + +#include +#include + +#include +#include + +#include +#include + +namespace hde { + namespace bindings { + namespace msgs { + + void CreateHumanState(pybind11::module& module) + { + namespace py = ::pybind11; + using namespace ::hde::msgs; + + py::class_(module, "HumanState") + .def(py::init()) + .def_readwrite("jointNames", &HumanState::jointNames) + .def_readwrite("positions", &HumanState::positions) + .def_readwrite("velocities", &HumanState::velocities) + .def_readwrite("baseName", &HumanState::baseName) + .def_readwrite("baseOriginWRTGlobal", &HumanState::baseOriginWRTGlobal) + .def_readwrite("baseOrientationWRTGlobal", &HumanState::baseOrientationWRTGlobal) + .def_readwrite("baseVelocityWRTGlobal", &HumanState::baseVelocityWRTGlobal) + .def_readwrite("CoMPositionWRTGlobal", &HumanState::CoMPositionWRTGlobal) + .def_readwrite("CoMVelocityWRTGlobal", &HumanState::CoMVelocityWRTGlobal); + + CreateBufferedPort(module, "BufferedPortHumanState"); + } + } // namespace msgs + } // namespace bindings +} // namespace hde \ No newline at end of file diff --git a/bindings/python/msgs/src/Module.cpp b/bindings/python/msgs/src/Module.cpp new file mode 100644 index 000000000..c724c9e17 --- /dev/null +++ b/bindings/python/msgs/src/Module.cpp @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +// SPDX-License-Identifier: BSD-3-Clause + +#include + +#include + +namespace hde { + namespace bindings { + namespace msgs { + + void CreateModule(pybind11::module& module) + { + module.doc() = "YarpUtilities module."; + + CreateHumanState(module); + } + } // namespace msgs + } // namespace bindings +} // namespace hde \ No newline at end of file diff --git a/cmake/AddHDEPythonModule.cmake b/cmake/AddHDEPythonModule.cmake new file mode 100644 index 000000000..6fdb4d082 --- /dev/null +++ b/cmake/AddHDEPythonModule.cmake @@ -0,0 +1,46 @@ +# SPDX-FileCopyrightText: Fondazione Istituto Italiano di Tecnologia (IIT) +# SPDX-License-Identifier: BSD-3-Clause + +function(add_hde_python_module) + + set(options ) + set(oneValueArgs NAME) + set(multiValueArgs + SOURCES + HEADERS + LINK_LIBRARIES + TESTS + TESTS_RUNTIME_CONDITIONS) + + set(prefix "hde") + + cmake_parse_arguments(${prefix} + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN}) + + set(name ${${prefix}_NAME}) + set(is_interface ${${prefix}_IS_INTERFACE}) + set(sources ${${prefix}_SOURCES}) + set(headers ${${prefix}_HEADERS}) + set(link_libraries ${${prefix}_LINK_LIBRARIES}) + set(subdirectories ${${prefix}_SUBDIRECTORIES}) + set(tests ${${prefix}_TESTS}) + set(tests_runtime_conditions ${${prefix}_TESTS_RUNTIME_CONDITIONS}) + + foreach(file ${headers}) + set_property(GLOBAL APPEND PROPERTY pybind_headers ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + endforeach() + + foreach(file ${sources}) + set_property(GLOBAL APPEND PROPERTY pybind_sources ${CMAKE_CURRENT_SOURCE_DIR}/${file}) + endforeach() + + set_property(GLOBAL APPEND PROPERTY pybind_include_dirs ${CMAKE_CURRENT_SOURCE_DIR}/include) + + set_property(GLOBAL APPEND PROPERTY pybind_link_libraries ${link_libraries}) + + message(STATUS "Added files for bindings named ${name} in ${PROJECT_NAME}.") + +endfunction()