Skip to content

Commit 071be6c

Browse files
committed
Refactor pybind11 common functionalities into a separate package
1 parent 334998a commit 071be6c

File tree

5 files changed

+216
-175
lines changed

5 files changed

+216
-175
lines changed

CMakeLists.txt

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ install(DIRECTORY include/ DESTINATION include)
213213
# Install shared resources
214214
install(DIRECTORY launch DESTINATION share/${PROJECT_NAME})
215215

216-
ament_export_targets(export_${PROJECT_NAME} HAS_LIBRARY_TARGET)
216+
ament_export_targets(export_${PROJECT_NAME} rviz_visual_tools_binding_utilsTargets HAS_LIBRARY_TARGET)
217217
ament_export_dependencies(geometry_msgs
218218
shape_msgs
219219
rclcpp
@@ -231,12 +231,6 @@ ament_export_dependencies(geometry_msgs
231231
rviz_ogre_vendor
232232
)
233233
ament_export_include_directories(include)
234-
ament_export_libraries(
235-
${PROJECT_NAME}
236-
${PROJECT_NAME}_remote_control
237-
${PROJECT_NAME}_imarker_simple
238-
${PROJECT_NAME}_gui
239-
)
240234

241235
##########
242236
## TEST ##
@@ -266,15 +260,30 @@ find_package(pybind11 REQUIRED)
266260

267261
ament_python_install_package(rviz_visual_tools PACKAGE_DIR python/rviz_visual_tools)
268262

263+
add_library(rviz_visual_tools_binding_utils SHARED python/src/binding_utils.cpp)
264+
ament_target_dependencies(rviz_visual_tools_binding_utils rclcpp pybind11)
265+
target_include_directories(rviz_visual_tools_binding_utils PUBLIC
266+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/python/include>
267+
$<INSTALL_INTERFACE:include/rviz_visual_tools>
268+
)
269+
269270
pybind11_add_module(pyrviz_visual_tools python/src/rviz_visual_tools.cpp)
270-
target_link_libraries(pyrviz_visual_tools PRIVATE rviz_visual_tools)
271+
target_link_libraries(pyrviz_visual_tools PRIVATE rviz_visual_tools rviz_visual_tools_binding_utils)
271272
ament_target_dependencies(pyrviz_visual_tools pybind11)
273+
install(
274+
TARGETS
275+
rviz_visual_tools_binding_utils
276+
EXPORT
277+
rviz_visual_tools_binding_utilsTargets
278+
ARCHIVE DESTINATION lib
279+
LIBRARY DESTINATION lib
280+
)
281+
install(DIRECTORY python/include/ DESTINATION include/rviz_visual_tools)
272282
install(TARGETS pyrviz_visual_tools
273283
LIBRARY DESTINATION ${PYTHON_INSTALL_DIR}/rviz_visual_tools)
274284
install(PROGRAMS
275285
python/examples/rviz_visual_tools_demo.py
276286
DESTINATION lib/${PROJECT_NAME}
277287
)
278288

279-
280289
ament_package()

python/examples/rviz_visual_tools_demo.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
#!/usr/bin/env python3
22

33
import rviz_visual_tools as rvt
4+
from rclpy.logging import get_logger
45
from geometry_msgs.msg import Point, Pose, Quaternion
56
from threading import Thread
67
import numpy as np
78
from copy import deepcopy
8-
import time
9+
10+
LOGGER = get_logger("rviz_visual_tools_demo")
911

1012
rvt.init()
1113
node = rvt.RvizVisualToolsNode("rviz_visual_tools_demo")
@@ -18,6 +20,7 @@
1820

1921
step_x = 0.1
2022

23+
LOGGER.info("Displaying range of colors red->green")
2124
visual_tools.publish_text(
2225
Pose(),
2326
"Shpere-Color-Range",
@@ -31,6 +34,7 @@
3134
visual_tools.trigger()
3235

3336

37+
LOGGER.info("Displaying Coordinate Axis")
3438
visual_tools.publish_text(
3539
Pose(position=Point(x=0.0, y=-0.2, z=0.0)),
3640
"Coordinate-Axis",
@@ -47,6 +51,7 @@
4751
)
4852
visual_tools.trigger()
4953

54+
LOGGER.info("Displaying Arrows")
5055
visual_tools.publish_text(
5156
Pose(position=Point(x=0.0, y=-0.4, z=0.0)),
5257
"Arrows",
@@ -77,6 +82,7 @@
7782
cuboid_max_size = 0.075
7883
cuboid_min_size = 0.01
7984
point1 = Point(x=0.0, y=-0.6, z=0.0)
85+
LOGGER.info("Displaying Rectangular Cuboid")
8086
visual_tools.publish_text(
8187
Pose(position=point1), "Cuboid", rvt.Colors.WHITE, rvt.Scales.XXLARGE, False
8288
)
@@ -92,6 +98,7 @@
9298
line_max_size = 0.075
9399
line_min_size = 0.01
94100
point1 = Point(x=0.0, y=-0.8, z=0.0)
101+
LOGGER.info("Displaying Lines")
95102
visual_tools.publish_text(
96103
Pose(position=point1), "Line", rvt.Colors.WHITE, rvt.Scales.XXLARGE, False
97104
)
@@ -104,11 +111,13 @@
104111
point1.x += step_x
105112
visual_tools.trigger()
106113

114+
LOGGER.info("Displaying Cylinder")
107115
visual_tools.publish_cylinder(
108116
Pose(position=Point(x=0.0, y=0.2, z=0.0)), color=rvt.Colors.RAND
109117
)
110118
visual_tools.trigger()
111119

120+
LOGGER.info("Displaying Cone")
112121
visual_tools.publish_cone(
113122
Pose(position=Point(x=0.0, y=0.4, z=0.0)),
114123
angle=np.pi,
@@ -117,15 +126,18 @@
117126
)
118127
visual_tools.trigger()
119128

129+
LOGGER.info("Displaying Planes")
120130
plane_pose = Pose(position=Point(x=0.0, y=0.6, z=0.0))
121131
visual_tools.publish_xy_plane(plane_pose, color=rvt.Colors.RED, scale=0.1)
122132
visual_tools.publish_xz_plane(plane_pose, color=rvt.Colors.GREEN, scale=0.1)
123133
visual_tools.publish_yz_plane(plane_pose, color=rvt.Colors.BLUE, scale=0.1)
124134
visual_tools.trigger()
125135

136+
LOGGER.info("Displaying Labeled Coordinate Axis")
126137
visual_tools.publish_axis_labeled(Pose(position=Point(x=0.0, y=0.8, z=0.0)), "MyAxis")
127138
visual_tools.trigger()
128139

140+
LOGGER.info("Displaying Path")
129141
visual_tools.publish_path(
130142
[Point(x=0.0, y=1.0, z=0.0), Point(x=0.2, y=1.0, z=0.0), Point(x=0.4, y=1.1, z=0.0)]
131143
)
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
#pragma once
2+
3+
#include <pybind11/pybind11.h>
4+
#include <rclcpp/executors/single_threaded_executor.hpp>
5+
#include <rclcpp/node.hpp>
6+
#include <rclcpp/serialization.hpp>
7+
#include <rclcpp/serialized_message.hpp>
8+
#include <rosidl_runtime_cpp/traits.hpp>
9+
10+
PYBIND11_EXPORT pybind11::object createMessage(const std::string& ros_msg_name);
11+
12+
PYBIND11_EXPORT bool convertible(const pybind11::handle& h, const std::string& ros_msg_name);
13+
14+
namespace pybind11
15+
{
16+
namespace detail
17+
{
18+
// Base class for type conversion (C++ <-> python) of ROS message types
19+
template <typename T>
20+
struct RosMsgTypeCaster
21+
{
22+
// C++ -> Python
23+
static handle cast(const T& src, return_value_policy /* policy */, handle /* parent */)
24+
{
25+
// serialize src
26+
rclcpp::Serialization<T> serializer;
27+
rclcpp::SerializedMessage serialized_msg;
28+
serializer.serialize_message(&src, &serialized_msg);
29+
pybind11::bytes bytes = pybind11::bytes(
30+
reinterpret_cast<const char*>(serialized_msg.get_rcl_serialized_message().buffer),
31+
serialized_msg.get_rcl_serialized_message().buffer_length);
32+
33+
// get Python object type
34+
const std::string ros_msg_name = rosidl_generator_traits::name<T>();
35+
36+
// find delimiting '/' in ros_msg_name
37+
std::size_t pos1 = ros_msg_name.find('/');
38+
std::size_t pos2 = ros_msg_name.find('/', pos1 + 1);
39+
pybind11::module m = pybind11::module::import((ros_msg_name.substr(0, pos1) + ".msg").c_str());
40+
41+
// retrieve type instance
42+
pybind11::object cls = m.attr(ros_msg_name.substr(pos2 + 1).c_str());
43+
44+
// deserialize into python object
45+
pybind11::module rclpy = pybind11::module::import("rclpy.serialization");
46+
pybind11::object msg = rclpy.attr("deserialize_message")(bytes, cls);
47+
48+
return msg.release();
49+
}
50+
51+
// Python -> C++
52+
bool load(handle src, bool /*convert*/)
53+
{
54+
// check datatype of src
55+
if (!convertible(src, rosidl_generator_traits::name<T>()))
56+
return false;
57+
58+
// serialize src into python buffer
59+
pybind11::module rclpy = pybind11::module::import("rclpy.serialization");
60+
pybind11::bytes bytes = rclpy.attr("serialize_message")(src);
61+
62+
// deserialize into C++ object
63+
rcl_serialized_message_t rcl_serialized_msg = rmw_get_zero_initialized_serialized_message();
64+
char* serialized_buffer;
65+
Py_ssize_t length;
66+
if (PYBIND11_BYTES_AS_STRING_AND_SIZE(bytes.ptr(), &serialized_buffer, &length))
67+
{
68+
throw pybind11::error_already_set();
69+
}
70+
if (length < 0)
71+
{
72+
throw pybind11::error_already_set();
73+
}
74+
rcl_serialized_msg.buffer_capacity = length;
75+
rcl_serialized_msg.buffer_length = length;
76+
rcl_serialized_msg.buffer = reinterpret_cast<uint8_t*>(serialized_buffer);
77+
rmw_ret_t rmw_ret = rmw_deserialize(
78+
&rcl_serialized_msg, rosidl_typesupport_cpp::get_message_type_support_handle<T>(), &value);
79+
if (RMW_RET_OK != rmw_ret)
80+
{
81+
throw std::runtime_error("failed to deserialize ROS message");
82+
}
83+
return true;
84+
}
85+
86+
PYBIND11_TYPE_CASTER(T, _<T>());
87+
};
88+
89+
template <typename T>
90+
struct type_caster<T, enable_if_t<rosidl_generator_traits::is_message<T>::value>>
91+
: RosMsgTypeCaster<T>
92+
{
93+
};
94+
} // namespace detail
95+
} // namespace pybind11
96+
97+
namespace rviz_visual_tools
98+
{
99+
// The idea was taken from
100+
// https://github.com/RobotLocomotion/drake-ros/blob/main/drake_ros/core/drake_ros.h
101+
/** A ROS interface that wraps a live rclcpp node.*/
102+
class RvizVisualToolsNode final : public rclcpp::Node
103+
{
104+
public:
105+
RCLCPP_SMART_PTR_DEFINITIONS(RvizVisualToolsNode)
106+
107+
RvizVisualToolsNode(const std::string& node_name);
108+
109+
~RvizVisualToolsNode();
110+
111+
void spin_all(int timeout_millis = 0);
112+
113+
void start_spin_thread();
114+
115+
void stop_spin_thread();
116+
117+
private:
118+
rclcpp::executors::SingleThreadedExecutor executor;
119+
std::thread executor_thread;
120+
};
121+
} // namespace rviz_visual_tools

python/src/binding_utils.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#include <rclcpp/rclcpp.hpp>
2+
#include <rclcpp/serialization.hpp>
3+
#include <rviz_visual_tools/binding_utils.hpp>
4+
5+
namespace py = pybind11;
6+
7+
PYBIND11_EXPORT py::object createMessage(const std::string& ros_msg_name)
8+
{
9+
// find delimiting '/' in ros msg name
10+
std::size_t pos = ros_msg_name.find('/');
11+
// import module
12+
py::module m = py::module::import((ros_msg_name.substr(0, pos) + ".msg").c_str());
13+
// retrieve type instance
14+
py::object cls = m.attr(ros_msg_name.substr(pos + 1).c_str());
15+
// create message instance
16+
return cls();
17+
}
18+
19+
PYBIND11_EXPORT bool convertible(const pybind11::handle& h, const std::string& ros_msg_name)
20+
{
21+
PyObject* o = h.attr("__class__").attr("__name__").ptr();
22+
std::size_t pos = ros_msg_name.find_last_of('/');
23+
std::string class_name = ros_msg_name.substr(pos + 1);
24+
return py::cast<std::string>(o) == class_name;
25+
}
26+
27+
rviz_visual_tools::RvizVisualToolsNode::RvizVisualToolsNode(const std::string& node_name)
28+
: Node(node_name)
29+
{
30+
executor.add_node(get_node_base_interface());
31+
}
32+
33+
rviz_visual_tools::RvizVisualToolsNode::~RvizVisualToolsNode()
34+
{
35+
executor.remove_node(get_node_base_interface());
36+
stop_spin_thread();
37+
}
38+
39+
void rviz_visual_tools::RvizVisualToolsNode::spin_all(int timeout_millis)
40+
{
41+
executor.spin_all(std::chrono::milliseconds(timeout_millis));
42+
}
43+
44+
void rviz_visual_tools::RvizVisualToolsNode::start_spin_thread()
45+
{
46+
stop_spin_thread();
47+
executor_thread = std::thread([this] { executor.spin(); });
48+
}
49+
50+
void rviz_visual_tools::RvizVisualToolsNode::stop_spin_thread()
51+
{
52+
executor.cancel();
53+
if (executor_thread.joinable())
54+
{
55+
executor_thread.join();
56+
}
57+
}

0 commit comments

Comments
 (0)