Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Python API #99

Merged
merged 96 commits into from
Nov 18, 2022
Merged
Show file tree
Hide file tree
Changes from 57 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
226b0b0
basic boost::python wrappers
janEbert May 7, 2018
3ef0bd4
Properties
rhaschke May 11, 2018
135c9c2
solvers + stages
rhaschke May 12, 2018
f7ddd43
cleanup folder structure
rhaschke May 12, 2018
e615b7c
local names for python wrapper libs
rhaschke May 13, 2018
ee13295
separate .core and .stages modules
rhaschke May 13, 2018
47a2a28
cleanup, unittest for properties
rhaschke May 13, 2018
f4ffccd
handle std::unique_ptr<Stage>
rhaschke May 13, 2018
de9fa84
register ROS msg types with boost::python's type converters
rhaschke May 16, 2018
1f85b00
more wrappers, unittest, fixes
janEbert May 15, 2018
56546f1
roscpp_init: provide init_options AnonymousName, NoRosout
rhaschke May 17, 2018
09b9698
add ROS unittest
rhaschke May 17, 2018
93c62e8
container wrappers
janEbert May 18, 2018
f15cbd7
overload constructors
janEbert Jun 1, 2018
2345b6c
unit tests for all stages
janEbert Jun 1, 2018
e264bdd
PropertyMap iterator, PropertyMap.update(dict)
janEbert May 24, 2018
d5c7bfb
cleanup
rhaschke May 27, 2018
5e2bff5
cleanup Property access
rhaschke Jun 1, 2018
c0a1697
remove redundant exposure of smart pointers
rhaschke May 27, 2018
d3fdee9
expose solutions, publish + execute
rhaschke May 27, 2018
4cebd95
Merge branch master into boost-python
rhaschke Jun 3, 2018
4ac0b6e
merge fixes
rhaschke Jun 3, 2018
3c77fd1
add reference test for properties
janEbert Jun 1, 2018
b36d91c
import .core by default
rhaschke Jun 7, 2018
c807553
moved python includes to global include folder too
rhaschke Jun 9, 2018
ea4b1e0
Properties: exposeTo(), configureInitFrom()
rhaschke Jun 9, 2018
ef1bbaa
Task::init(): verbose exception output
rhaschke Jun 9, 2018
14bd867
expose MonitoringGenerator's setMonitoringStage()
rhaschke Jun 9, 2018
effa437
Solution.toMsg()
rhaschke Jun 10, 2018
47232dd
pass verbose InitStageException from C++ to python
voido Jun 11, 2018
fbbb395
allow PoseStamped as property
rhaschke Jun 12, 2018
c9e735d
fix API to match MoveTo / MoveRelative stages
rhaschke Jun 12, 2018
5bae196
fixes for Bionic
rhaschke Oct 12, 2018
5d78b10
fix compiler warnings
rhaschke Oct 16, 2018
3786ce5
protect fromPython / toPython
rhaschke Oct 16, 2018
1b26776
cleanup type conversion
rhaschke Oct 17, 2018
fdec258
ROSMsgConverter -> RosMsgConverter
rhaschke Oct 17, 2018
074a3b7
RosMsgConverter: do not allow custom message name
rhaschke Oct 17, 2018
c145b4c
better robustness against already registered boost::python type conve…
rhaschke Oct 17, 2018
14d10a2
replace MessageSignature with simple ros-msg-name string
rhaschke Oct 17, 2018
3ad03cf
generalize Property conversion between C++ and Python
rhaschke Oct 17, 2018
b511110
remove dummy file
rhaschke Oct 22, 2018
377dd11
Merge branch 'master' into boost-python
rhaschke Oct 25, 2018
ff6661e
python wrappers for new functionality since last merge
rhaschke Sep 25, 2018
ed44c2c
adapt API: MoveRelative::setGoal -> setDirection
rhaschke Oct 23, 2018
2f8ded2
fixup! generalize Property conversion between C++ and Python
rhaschke Oct 28, 2018
fab40f6
register enum Connect::MergeMode
janEbert Oct 26, 2018
e79f0f3
add converter for std::set<std::string>
janEbert Oct 26, 2018
df82073
cleanup converter for ros::Duration
rhaschke Oct 29, 2018
00d5b00
Provide default constructors for all stages
janEbert Oct 25, 2018
bb5b29d
disable python default constructors for some classes
rhaschke Oct 28, 2018
c59e410
Add test for all stages' PropertyMaps
janEbert Oct 17, 2018
95d05ea
expose PropagatingEitherWay::restrictDirection()
rhaschke Feb 19, 2019
36b63f0
fixup wrapping of solvers
rhaschke Feb 19, 2019
dd80c67
cannot use cmake generator expressions in COMMENT
rhaschke Feb 20, 2019
874994b
test packages are required
rhaschke Jul 12, 2019
fd9462c
Merge branch master into wip-python-api
rhaschke Jul 26, 2019
7d0daa6
boost::python: provide generic converter for std::map
rhaschke Sep 10, 2019
5293d2e
simplify method overloads
rhaschke Jan 10, 2020
f88bd8f
RosMsgConverter: ensure that python and C++ types match
rhaschke Jan 10, 2020
5a85343
Merge branch master into wip-python-api
rhaschke Jan 11, 2020
569836e
demo: add example cartesian.py
rhaschke Jan 11, 2020
d10565c
fix memory access issue
rhaschke Feb 18, 2020
7306878
Merge branch master into wip-python-api
rhaschke Sep 7, 2020
97e0b43
Silence -Wdeprecated-declarations due to std::auto_ptr
rhaschke Sep 7, 2020
745d673
fix typo
rhaschke Sep 22, 2020
49b2ff8
Test Merger
rhaschke Sep 22, 2020
ede5fe3
StagesWrapper: Add std::map setGoal overload.
cpetersmeier Oct 6, 2020
012a10b
StagesWrapper: Python Function Bindings
cpetersmeier Nov 12, 2020
8433e46
Merge branch 'master' into wip-python-api
rhaschke Nov 26, 2020
c97b462
migration: boost::python -> pybind11
rhaschke Dec 13, 2019
b2adcf0
Python3 compatibility
rhaschke Jul 15, 2020
10adb63
Travis: Testing on Noetic as well
rhaschke Nov 30, 2020
fd25a06
auto-format python code with black
rhaschke Mar 16, 2021
d7d54d8
Access to container's children
rhaschke Dec 10, 2020
1f5c684
Simplify wrapper code
rhaschke Mar 15, 2021
6ab50fc
PythonWrapper: Use collective includes
cpetersmeier Dec 24, 2020
14e0665
Towards inherited classes in Python
cpetersmeier Dec 10, 2020
d9b7aa3
clang-format python wrapping code
rhaschke Mar 16, 2021
3cf9244
Simplify generation of pybind11 modules
rhaschke Mar 17, 2021
045b358
rosdoc_lite configuration
rhaschke Mar 16, 2021
5b4ca1d
Rename wrapper -> bindings
rhaschke Mar 30, 2021
a204d94
Configure namespace package
rhaschke Mar 30, 2021
f006ff7
Allow casting of PoseStamped from string
rhaschke Mar 30, 2021
3c286b2
Augment license/disclaimer
rhaschke Mar 31, 2021
fc0abe9
Merge branch 'master' into wip-python-api
rhaschke May 19, 2021
defe144
Use py:overload_cast<>()
rhaschke May 19, 2021
a03ea5a
Use pybind11's smart_holder branch
rhaschke Mar 14, 2021
7d0b6da
Generator::spawn()
rhaschke May 19, 2021
1e7fe85
smart_holder: conservative mode
rhaschke May 20, 2021
0967aa8
Update pybind11 submodule
rhaschke May 20, 2021
9f7139f
Fix names of trampoline classes
rhaschke May 21, 2021
81fae53
PyMonitoringGenerator
rhaschke May 21, 2021
e3ee75d
Fix utf8 encoding
rhaschke Jun 9, 2021
244c999
Skip some python tests on incompatible pybind11 versions
rhaschke Jun 10, 2021
ced362f
Merge branch 'master' into wip-python-api
rhaschke Jun 11, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
*.swp

*.pyc
*.so
6 changes: 5 additions & 1 deletion core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ project(moveit_task_constructor_core)

find_package(Boost REQUIRED)
find_package(catkin REQUIRED COMPONENTS
roslint
eigen_conversions
geometry_msgs
moveit_core
Expand All @@ -14,6 +15,8 @@ find_package(catkin REQUIRED COMPONENTS
rviz_marker_tools
)

catkin_python_setup()

catkin_package(
LIBRARIES
${PROJECT_NAME}
Expand All @@ -33,9 +36,10 @@ else ()
set(CMAKE_CXX_STANDARD 14)
endif ()

set(PROJECT_INCLUDE ${CMAKE_CURRENT_SOURCE_DIR}/include/moveit/task_constructor)
set(PROJECT_INCLUDE ${PROJECT_SOURCE_DIR}/include/moveit/task_constructor)

add_subdirectory(src)
add_subdirectory(python)
add_subdirectory(test)

install(DIRECTORY include/ DESTINATION ${CATKIN_GLOBAL_INCLUDE_DESTINATION})
Expand Down
128 changes: 128 additions & 0 deletions core/include/moveit/python/python_tools/conversions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#pragma once

#include <boost/python.hpp>
#include <boost/python/stl_iterator.hpp>
#include <string>
#include <vector>
#include <map>
#include <memory>

#include <ros/serialization.h>

namespace moveit {
namespace python {

template <typename T>
std::vector<T> fromList(const boost::python::list& values) {
boost::python::stl_input_iterator<T> begin(values), end;
return std::vector<T>(begin, end);
}

template <typename T>
boost::python::list toList(const std::vector<T>& v) {
boost::python::list l;
for (const T& value : v)
l.append(value);
return l;
}

template <typename T>
std::map<std::string, T> fromDict(const boost::python::dict& values) {
std::map<std::string, T> m;
for (boost::python::stl_input_iterator<boost::python::tuple> it(values.iteritems()), end; it != end; ++it) {
const std::string& key = boost::python::extract<std::string>((*it)[0]);
const T& value = boost::python::extract<T>((*it)[1]);
m.insert(std::make_pair(key, value));
}
return m;
}

template <typename T>
boost::python::dict toDict(const std::map<std::string, T>& v) {
boost::python::dict d;
for (const std::pair<std::string, T>& p : v)
d[p.first] = p.second;
return d;
}

/// convert a ROS msg to a string
template <typename T>
std::string serializeMsg(const T& msg) {
static_assert(sizeof(uint8_t) == sizeof(char), "Assuming char has same size as uint8_t");
std::size_t size = ros::serialization::serializationLength(msg);
std::string result(size, '\0');
if (size) {
ros::serialization::OStream stream(reinterpret_cast<uint8_t*>(&result[0]), size);
ros::serialization::serialize(stream, msg);
}
return result;
}

/// convert a string to a ROS message
template <typename T>
void deserializeMsg(const std::string& data, T& msg) {
ros::serialization::IStream stream(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&data[0])), data.size());
ros::serialization::deserialize(stream, msg);
}

/// retrieve name of ROS msg from object instance
std::string rosMsgName(PyObject* object);

/// non-templated base class for RosMsgConverter<T> providing common methods
class RosMsgConverterBase
{
protected:
/// Register type internally and return true if registered first time
static bool insert(const boost::python::type_info& type_info, const std::string& ros_msg_name);

/// Determine if python object can be converted into C++ msg type
static void* convertible(PyObject* object);

/// Convert a ROS message (from python) to a string
static std::string fromPython(const boost::python::object& msg);

/// Convert a string to a python ROS message of given type
static PyObject* toPython(const std::string& data, const boost::python::type_info& type_info);
};

/// converter type to be registered with boost::python type conversion
/// https://sixty-north.com/blog/how-to-write-boost-python-type-converters.html
template <typename T>
struct RosMsgConverter : RosMsgConverterBase
{
/// constructor registers the type converter
RosMsgConverter() {
auto type_info = boost::python::type_id<T>();
// register type internally
if (insert(type_info, ros::message_traits::DataType<T>::value())) {
// https://stackoverflow.com/questions/9888289/checking-whether-a-converter-has-already-been-registered
const boost::python::converter::registration* reg = boost::python::converter::registry::query(type_info);
if (!reg || !reg->m_to_python) {
/// register type with boost::python converter system
boost::python::converter::registry::push_back(&convertible, &construct, type_info);
boost::python::to_python_converter<T, RosMsgConverter<T>>();
}
}
}

/// Conversion from Python object to C++ object, using pre-allocated memory block
static void construct(PyObject* object, boost::python::converter::rvalue_from_python_stage1_data* data) {
// serialize python msgs into string
std::string serialized = fromPython(boost::python::object(boost::python::borrowed(object)));

// Obtain a pointer to the memory block that the converter has allocated for the C++ type.
void* storage = reinterpret_cast<boost::python::converter::rvalue_from_python_storage<T>*>(data)->storage.bytes;
// Allocate the C++ type into the pre-allocated memory block, and assign its pointer to the converter's
// convertible variable.
T* result = new (storage) T();
data->convertible = result;

// deserialize string into C++ msg
deserializeMsg(serialized, *result);
}

/// Conversion from C++ object to Python object
static PyObject* convert(const T& msg) { return toPython(serializeMsg(msg), typeid(T)); }
};
}
}
31 changes: 31 additions & 0 deletions core/include/moveit/python/python_tools/ros_init.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <memory>
#include <boost/python.hpp>
#include <boost/thread/mutex.hpp>
#include <ros/init.h>

namespace moveit {
namespace python {

/// singleton class to initialize ROS C++ (once) from Python
class InitProxy
{
public:
static void init(const std::string& node_name = "moveit_python_wrapper",
const boost::python::dict& remappings = boost::python::dict(), uint32_t options = 0);
static void shutdown();

~InitProxy();

private:
InitProxy(const std::string& node_name, const boost::python::dict& remappings, uint32_t options);

static boost::mutex lock;
static std::unique_ptr<InitProxy> singleton_instance;

private:
std::unique_ptr<ros::AsyncSpinner> spinner;
};
}
}
91 changes: 91 additions & 0 deletions core/include/moveit/python/task_constructor/properties.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
#include <boost/python/class.hpp>
#include <boost/mpl/vector.hpp>

#include <moveit/task_constructor/properties.h>
#include <moveit/python/python_tools/conversions.h>

namespace moveit {
namespace python {

void export_properties();

class PropertyConverterBase
{
public:
typedef boost::python::object (*to_python_converter_function)(const boost::any&);
typedef boost::any (*from_python_converter_function)(const boost::python::object&);

protected:
static bool insert(const boost::python::type_info& type_info, const std::string& ros_msg_name,
to_python_converter_function to, from_python_converter_function from);
};

/// utility class to register C++ / Python converters for a property of type T
template <typename T>
class PropertyConverter : protected PropertyConverterBase
{
public:
PropertyConverter() {
auto type_info = boost::python::type_id<T>();
insert(type_info, rosMsgName<T>(), &toPython, &fromPython);
}

private:
static boost::python::object toPython(const boost::any& value) {
return boost::python::object(boost::any_cast<T>(value));
}
static boost::any fromPython(const boost::python::object& bpo) { return T(boost::python::extract<T>(bpo)); }

template <class Q = T>
typename std::enable_if<ros::message_traits::IsMessage<Q>::value, std::string>::type rosMsgName() {
RosMsgConverter<T>(); // register ROS msg converter
return ros::message_traits::DataType<T>::value();
}

template <class Q = T>
typename std::enable_if<!ros::message_traits::IsMessage<Q>::value, std::string>::type rosMsgName() {
return std::string();
} // empty string if T isn't a ROS msg
};

namespace properties {

/** Extension for boost::python::class_ to allow convienient definition of properties
*
* New method property<PropertyType>(const char* name) adds a property getter/setter.
*/

template <class W // class being wrapped
,
class X1 = ::boost::python::detail::not_specified, class X2 = ::boost::python::detail::not_specified,
class X3 = ::boost::python::detail::not_specified>
class class_ : public boost::python::class_<W, X1, X2, X3>
{
public:
typedef class_<W, X1, X2, X3> self;
// forward all constructors
using boost::python::class_<W, X1, X2, X3>::class_;

template <typename PropertyType>
self& property(const char* name, const char* docstr = 0) {
auto getter = [name](const W& me) {
const moveit::task_constructor::PropertyMap& props = me.properties();
return props.get<PropertyType>(name);
};
auto setter = [name](W& me, const PropertyType& value) {
moveit::task_constructor::PropertyMap& props = me.properties();
props.set(name, boost::any(value));
};

boost::python::class_<W, X1, X2, X3>::add_property(
name, boost::python::make_function(getter, boost::python::default_call_policies(),
boost::mpl::vector<PropertyType, const W&>()),
boost::python::make_function(setter, boost::python::default_call_policies(),
boost::mpl::vector<void, W&, const PropertyType&>()),
docstr);
return *this;
}
};
}
}
}
15 changes: 15 additions & 0 deletions core/python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# C++ wrapper code
add_subdirectory(wrapper)

if(CATKIN_ENABLE_TESTING)
find_package(moveit_resources REQUIRED)
find_package(rostest REQUIRED)

# Add folders to be run by python nosetests
catkin_add_nosetests(test)

# Add rostests
add_rostest(test/rostest_mtc.test)
endif()

roslint_python()
Empty file.
1 change: 1 addition & 0 deletions core/python/src/moveit/python_tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from _python_tools import *
2 changes: 2 additions & 0 deletions core/python/src/moveit/task_constructor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from core import *
import stages
1 change: 1 addition & 0 deletions core/python/src/moveit/task_constructor/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from _core import *
1 change: 1 addition & 0 deletions core/python/src/moveit/task_constructor/stages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from _stages import *
45 changes: 45 additions & 0 deletions core/python/test/rostest_mtc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import unittest
import rostest
from moveit.python_tools import roscpp_init
from moveit.task_constructor import core, stages
from geometry_msgs.msg import Pose
from geometry_msgs.msg import PoseStamped
import rospy


class Test(unittest.TestCase):
PLANNING_GROUP = "manipulator"

@classmethod
def setUpClass(self):
pass

@classmethod
def tearDown(self):
pass

def test_MoveRelative(self):
task = core.Task()
task.add(stages.CurrentState("current"))
move = stages.MoveRelative("move", core.PipelinePlanner())
move.group = self.PLANNING_GROUP
move.setDirection({"joint_1" : 0.2, "joint_2" : 0.4})
task.add(move)

task.enableIntrospection()
task.init()
task.plan()

self.assertEqual(len(task.solutions), 1)
for s in task.solutions:
print s
s = task.solutions[0]
task.execute(s)


if __name__ == '__main__':
roscpp_init("test_mtc")
rostest.rosrun("", "", Test)
4 changes: 4 additions & 0 deletions core/python/test/rostest_mtc.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<launch>
<include file="$(find moveit_resources)/fanuc_moveit_config/launch/test_environment.launch"/>
<test pkg="moveit_task_constructor_core" type="rostest_mtc.py" test-name="rostest_mtc" time-limit="300" args=""/>
</launch>
Loading