Unverified Commit 364f5734 authored by Justin Carpentier's avatar Justin Carpentier Committed by GitHub
Browse files

Merge pull request #1519 from jmirabel/pybind11

[python] Add pybind11 header.
parents 22959bfb ce31b402
Pipeline #16554 passed with stage
in 34 minutes and 12 seconds
......@@ -260,4 +260,18 @@ IF(BUILD_PYTHON_INTERFACE)
FILES "${CMAKE_CURRENT_BINARY_DIR}/pinocchiopy.pc"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig
PERMISSIONS OWNER_READ GROUP_READ WORLD_READ OWNER_WRITE)
IF(DOXYGEN_FOUND AND DOXYGEN_VERSION VERSION_GREATER 1.8.17)
SET(DOXYGEN_GENERATE_HTML YES)
SET(DOXYGEN_GENERATE_LATEX NO)
SET(DOXYGEN_PROJECT_NAME "Pinocchio PyBind11 helpers.")
message("DOXYGEN_GENERATE_HTML: ${DOXYGEN_GENERATE_HTML}")
cmake_policy(PUSH)
cmake_policy(SET CMP0054 NEW)
doxygen_add_docs(doc_pybind11
pybind11.hpp pybind11-all.hpp
USE_STAMP_FILE
COMMENT "Generating documentation of the PyBind11 helpers.")
cmake_policy(POP)
ENDIF()
ENDIF(BUILD_PYTHON_INTERFACE)
// No header guard on purpose because the file can be included several times
// with different value for preprocessor variables SCALAR, OPTIONS and
// JOINT_MODEL_COLLECTION
/// \page default_type_caster Predefined casters
///
/// At the time of writting, this exposes
/// \li ModelTpl
/// \li DataTpl
/// \li SE3Tpl
/// \li MotionTpl
/// \li GeometryModel
/// \li (not yet) GeometryData
#if !defined SCALAR or !defined OPTIONS or !defined JOINT_MODEL_COLLECTION
#error \
"You must define SCALAR, OPTIONS and JOINT_MODEL_COLLECTION before including this file."
#endif
#include <pinocchio/bindings/python/pybind11.hpp>
#include <pinocchio/multibody/data.hpp>
#include <pinocchio/multibody/geometry.hpp>
#include <pinocchio/multibody/model.hpp>
// Required to be able to pass argument with commas to macros
#define _SINGLE_ARG(...) __VA_ARGS__
#define _PINOCCHIO_PYBIND11_EXPOSE(type, name) \
PINOCCHIO_PYBIND11_ADD_ALL_CONVERT_TYPE(_SINGLE_ARG(type)) \
PINOCCHIO_PYBIND11_TYPE_CASTER(_SINGLE_ARG(type), name)
_PINOCCHIO_PYBIND11_EXPOSE(_SINGLE_ARG(::pinocchio::SE3Tpl<SCALAR,OPTIONS>),
_("pinocchio.pinocchio_pywrap.SE3"))
_PINOCCHIO_PYBIND11_EXPOSE(_SINGLE_ARG(::pinocchio::MotionTpl<SCALAR, OPTIONS>),
_("pinocchio.pinocchio_pywrap.Motion"))
_PINOCCHIO_PYBIND11_EXPOSE(
_SINGLE_ARG(::pinocchio::ModelTpl<SCALAR, OPTIONS, JOINT_MODEL_COLLECTION>),
_("pinocchio.pinocchio_pywrap.Model"))
_PINOCCHIO_PYBIND11_EXPOSE(
_SINGLE_ARG(::pinocchio::DataTpl<SCALAR, OPTIONS, JOINT_MODEL_COLLECTION>),
_("pinocchio.pinocchio_pywrap.Model"))
_PINOCCHIO_PYBIND11_EXPOSE(::pinocchio::GeometryModel,
_("pinocchio.pinocchio_pywrap.GeometryModel"))
// \todo this triggers a warning because GeometryData has
// a copy constructor and no operator=
// _PINOCCHIO_PYBIND11_EXPOSE(::pinocchio::GeometryData,
// _("pinocchio.pinocchio_pywrap.GeometryData"))
#undef _PINOCCHIO_PYBIND11_EXPOSE
#undef _SINGLE_ARG
#ifndef __pinocchio_python_pybind11_hpp__
#define __pinocchio_python_pybind11_hpp__
/// \mainpage Pinocchio PyBind11 helpers
///
/// This package provides utilities to ease the use of Pinocchio objects when
/// using PyBind11.
/// There are two methods:
/// 1. The developer-friendly but likely less user-friendly method: \ref
/// PINOCCHIO_PYBIND11_TYPE_CASTER
/// 2. The user-friendly but less developer-friendly method: \ref
/// pinocchio::python::make_pybind11_function
///
/// Both methods can be mixed. For both cases, you may
/// \code
/// // with necessary #define. See below
/// #include <pinocchio/bindings/python/pybind11-all.hpp>
/// \endcode
/// to get some \subpage default_type_caster.
///
/// \section example Example
/// \code
/// #define SCALAR double
/// #define OPTIONS 0
/// #define JOINT_MODEL_COLLECTION ::pinocchio::JointCollectionDefaultTpl
/// #include <pinocchio/bindings/python/pybind11-all.hpp>
///
/// ...
/// // method 1
/// m.def("function", my_function);
/// // method 2
/// m.def("function", pinocchio::python::make_pybind11_function(my_function));
/// \endcode
///
#include <iostream>
#include <pinocchio/fwd.hpp>
// This lines forces clang-format to keep the include split here
#include <pybind11/pybind11.h>
#include <boost/python.hpp>
namespace pinocchio {
namespace python {
namespace bp = boost::python;
namespace py = pybind11;
template <typename T>
inline py::object to(T& t) {
// Create PyObject using boost Python
bp::object obj = bp::api::object(t);
PyObject* pyobj = obj.ptr();
return pybind11::reinterpret_borrow<py::object>(pyobj);
}
template <typename T>
inline py::object to(T* t) {
// Create PyObject using boost Python
typename bp::manage_new_object::apply<T*>::type converter;
PyObject* pyobj = converter(t);
// Create the Pybind11 object
return py::reinterpret_borrow<py::object>(pyobj);
}
template <typename ReturnType>
inline ReturnType& from(py::handle model) {
return bp::extract<ReturnType&>(model.ptr());
}
template <typename T>
struct convert_type {
typedef T type;
static inline T _to(T t) { return t; }
static inline type _from(type t) { return t; }
};
template <>
struct convert_type<void> {
// typedef void type;
// static inline void _to() {}
};
template <typename T>
struct convert_boost_python_object {
typedef py::object type;
static inline type _to(T t) {
return to<typename std::remove_pointer<typename std::remove_reference<
typename std::remove_cv<T>::type>::type>::type>(t);
}
static inline T _from(type t) {
return from<
typename std::remove_cv<typename std::remove_reference<T>::type>::type>(
t);
}
};
/// \brief Defines a conversion used by \ref make_pybind11_function
#define PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(CLASS) \
namespace pinocchio { \
namespace python { \
template <> \
struct convert_type<CLASS> : convert_boost_python_object<CLASS> {}; \
} \
}
/// \brief Defines a set of conversion used by \ref make_pybind11_function
#define _SINGLE_ARG(...) __VA_ARGS__
#define PINOCCHIO_PYBIND11_ADD_ALL_CONVERT_TYPE(CLASS) \
PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(_SINGLE_ARG(CLASS)) \
PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(_SINGLE_ARG(CLASS const)) \
PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(_SINGLE_ARG(CLASS&)) \
PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(_SINGLE_ARG(CLASS const&)) \
PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(_SINGLE_ARG(CLASS*)) \
PINOCCHIO_PYBIND11_ADD_CONVERT_TYPE(_SINGLE_ARG(CLASS const*))
namespace internal {
template <typename R, typename... Args>
auto call(R (*f)(Args...), typename convert_type<Args>::type... args) {
return convert_type<R>::_to(f(convert_type<Args>::_from(args)...));
}
template <typename... Args>
void call(void (*f)(Args...), typename convert_type<Args>::type... args) {
f(convert_type<Args>::_from(args)...);
}
template <typename T>
struct function_wrapper;
template <typename R, typename... Args>
struct function_wrapper<R (*)(Args...)> {
static const size_t nargs = sizeof...(Args);
typedef R result_type;
template <size_t i>
struct arg {
typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
};
typedef R (*func_type)(Args...);
func_type f;
// typename convert_type<result_type>::type
auto operator()(typename convert_type<Args>::type... args) {
return call(f, args...);
}
};
} // namespace internal
/// \brief Creates a function wrapper.
///
/// Using function wrapper has the advantage of being copy-less when possible
/// but the disadvantage of requiring to wrap the exposed function.
///
/// The wrapper does:
/// - converts the argument if a conversion has been previously declared,
/// - call the wrapped function
/// - converts the result if a conversion has been previously declared.
template <typename R, typename... Args>
internal::function_wrapper<R (*)(Args...)> make_pybind11_function(
R (*func)(Args...)) {
internal::function_wrapper<R (*)(Args...)> wrapper;
wrapper.f = func;
return wrapper;
}
template <typename T>
py::object default_arg(T t) {
py::object obj = to<T>(t);
//obj.inc_ref();
return obj;
}
/// \brief Add a PyBind11 type caster.
///
/// Using type caster has the advantage of not requiring to wrap the exposed
/// functions but the disadvantage of systematically requiring a copy.
///
/// See \ref https://pybind11.readthedocs.io/en/stable/advanced/cast/custom.html
/// "PyBind11 documentation"
#define PINOCCHIO_PYBIND11_TYPE_CASTER(native_type, boost_python_name) \
namespace pybind11 { \
namespace detail { \
template <> \
struct type_caster<native_type> { \
PYBIND11_TYPE_CASTER(_SINGLE_ARG(native_type), boost_python_name); \
\
/* Python -> C++ */ \
bool load(pybind11::handle src, bool) { \
PyObject* source = src.ptr(); \
value = boost::python::extract<native_type>(source); \
return !PyErr_Occurred(); \
} \
/* C++ -> Python */ \
static pybind11::handle cast(native_type src, \
pybind11::return_value_policy /*policy*/, \
pybind11::handle /*parent*/) { \
typename boost::python::manage_new_object::apply<native_type*>::type \
converter; \
return boost::python::api::object(src).ptr(); \
} \
}; \
} /* namespace detail */ \
} /* namespace pybind11 */
} // namespace python
} // namespace pinocchio
#undef _SINGLE_ARG
#endif // #ifndef __pinocchio_python_pybind11_hpp__
......@@ -78,3 +78,5 @@ FOREACH(TEST ${${PROJECT_NAME}_PYTHON_TESTS})
ENDFOREACH(TEST ${${PROJECT_NAME}_PYTHON_TESTS})
MAKE_DIRECTORY("${CMAKE_CURRENT_BINARY_DIR}/serialization-data")
ADD_SUBDIRECTORY(pybind11)
if(CMAKE_VERSION VERSION_GREATER 3.11)
include(FetchContent)
FetchContent_Declare(pybind11
GIT_REPOSITORY https://github.com/pybind/pybind11
GIT_TAG v2.8.0)
FetchContent_GetProperties(pybind11)
if(NOT pybind11_POPULATED)
FetchContent_Populate(pybind11)
add_subdirectory(${pybind11_SOURCE_DIR} ${pybind11_BINARY_DIR})
pybind11_add_module(cpp2pybind11 cpp2pybind11.cpp)
target_link_libraries(cpp2pybind11 PRIVATE pinocchio_pywrap)
if(CMAKE_CXX_STANDARD LESS 14)
message(STATUS "CXX_STANDARD for cpp2pybind11 set changed from ${CMAKE_CXX_STANDARD} to 14")
set_target_properties(cpp2pybind11 PROPERTIES CXX_STANDARD 14)
endif()
if(WIN32)
target_compile_definitions(cpp2pybind11 PRIVATE -DNOMINMAX)
endif(WIN32)
endif()
endif()
#include <pinocchio/bindings/python/pybind11.hpp>
// This lines forces clang-format to keep the include split here
#include <pybind11/pybind11.h>
#include <boost/python.hpp>
#define SCALAR double
#define OPTIONS 0
#define JOINT_MODEL_COLLECTION ::pinocchio::JointCollectionDefaultTpl
#include <pinocchio/bindings/python/pybind11-all.hpp>
pinocchio::Model* make_model() {
pinocchio::Model* model = new pinocchio::Model;
std::cout << "make_model: " << reinterpret_cast<intptr_t>(model) << std::endl;
return model;
}
pinocchio::Model& return_same_model_copy(pinocchio::Model& m) { return m; }
pinocchio::Model* return_same_model_nocopy(pinocchio::Model& m) { return &m; }
pinocchio::SE3 multiply_se3(pinocchio::SE3 const& a, pinocchio::SE3 const& b) {
return a * b;
}
template <typename T>
intptr_t get_ptr(T& m) {
std::cout << &m << '\n' << m << std::endl;
return reinterpret_cast<intptr_t>(&m);
}
void test1(int i) { std::cout << "no conversion: " << ' ' << i << std::endl; }
void testModel1(pinocchio::Model& model) {
std::cout << "testModel1: " << &model << std::endl;
model.name = "testModel1: I modified the model name";
}
intptr_t testModel2(pinocchio::Model& model, int i) {
std::cout << "testModel2: " << &model << ' ' << i << std::endl;
model.name = "testModel2: I modified the model name";
return reinterpret_cast<intptr_t>(&model);
}
intptr_t testModel3(pinocchio::Model const& model, int i) {
std::cout << "testModel3: " << &model << ' ' << i << std::endl;
return reinterpret_cast<intptr_t>(&model);
}
void testModel_manual(pybind11::object model) {
testModel1(pinocchio::python::from<pinocchio::Model&>(model));
}
using pinocchio::python::make_pybind11_function;
PYBIND11_MODULE(cpp2pybind11, m) {
using namespace pybind11::literals; // For _a
pybind11::module::import("pinocchio");
m.def("testModel_manual", testModel_manual);
m.def("test1", make_pybind11_function(&test1));
m.def("make_model", make_pybind11_function(&make_model));
m.def("return_same_model_broken", make_pybind11_function(&return_same_model_copy),
pybind11::return_value_policy::reference);
m.def("return_same_model", make_pybind11_function(&return_same_model_nocopy),
pybind11::return_value_policy::reference);
m.def("get_ptr", make_pybind11_function(&get_ptr<pinocchio::Model>));
m.def("get_se3_ptr", make_pybind11_function(&get_ptr<pinocchio::SE3>));
m.def("multiply_se3_1", make_pybind11_function(&multiply_se3), "a"_a, "b"_a);
m.def("multiply_se3", make_pybind11_function(&multiply_se3), "a"_a,
"b"_a = pinocchio::python::default_arg(pinocchio::SE3::Identity()));
m.def("testModel1", make_pybind11_function(&testModel1));
m.def("testModel2", make_pybind11_function(&testModel2));
m.def("testModel3", make_pybind11_function(&testModel3));
pybind11::module no_wrapper = m.def_submodule("no_wrapper");
no_wrapper.def("multiply_se3", &multiply_se3, "a"_a,
"b"_a
// does not work = pinocchio::SE3::Identity()
);
no_wrapper.def("testModel1", &testModel1);
no_wrapper.def("testModel2", &testModel2);
no_wrapper.def("testModel3", &testModel3);
}
import cpp2pybind11, sys, gc
import pinocchio
a = pinocchio.SE3.Random()
b = pinocchio.SE3.Random()
assert cpp2pybind11.multiply_se3_1(a, b) == a * b
assert cpp2pybind11.multiply_se3(a,b) == a * b
assert cpp2pybind11.no_wrapper.multiply_se3(a, b) == a * b
assert cpp2pybind11.multiply_se3(a) == a
#assert cpp2pybind11.no_wrapper.multiply_se3(a) == a
def print_ref_count(v,what=""):
# - 2 because one for variable v and one for variable inside getrefcount
idv = id(v)
gc.collect()
#n = len(gc.get_referrers(v))
n = sys.getrefcount(v)
print("ref count of", what, idv, n)
m = cpp2pybind11.make_model()
print_ref_count(m, "m")
print(cpp2pybind11.get_ptr(m))
print_ref_count(m, "m")
m.name = ""
cpp2pybind11.testModel1(m)
print_ref_count(m, "m")
assert m.name.startswith("testModel1")
addr2 = cpp2pybind11.testModel2(m, 1)
print_ref_count(m, "m")
assert m.name.startswith("testModel2")
addr3 = cpp2pybind11.testModel3(m, 2)
print_ref_count(m, "m")
assert addr2 == addr3
mm = cpp2pybind11.return_same_model_broken(m)
assert cpp2pybind11.get_ptr(m) != cpp2pybind11.get_ptr(mm)
mm = cpp2pybind11.return_same_model(m)
# Not sure why but the ref count of m and mm sticks to one
print_ref_count(m, "m")
mmm = m
print_ref_count(m, "m")
print_ref_count(mm, "mm")
assert cpp2pybind11.get_ptr(m) == cpp2pybind11.get_ptr(mm)
if False:
print("deleting m")
del m
print("deleted m")
print("mm is", mm)
else:
print("deleting mm")
del mm
print("deleted mm")
print("m is", m)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment