From 0074639190af7b561a386c5e74870d3054faa529 Mon Sep 17 00:00:00 2001 From: Jorgen Fjermedal Date: Tue, 23 Dec 2025 20:45:48 +0100 Subject: [PATCH 1/2] split utils ros packages --- README.md | 31 +++++++++-- cpp_test/test_main.cpp | 6 --- CMakeLists.txt => vortex_utils/CMakeLists.txt | 12 ----- .../cpp_test}/CMakeLists.txt | 25 +-------- .../cpp_test}/test_concepts.cpp | 0 .../cpp_test}/test_math.cpp | 0 .../cpp_test}/test_types.cpp | 0 .../include}/vortex/utils/concepts.hpp | 0 .../include}/vortex/utils/math.hpp | 0 .../include}/vortex/utils/types.hpp | 0 package.xml => vortex_utils/package.xml | 6 --- .../py_test}/CMakeLists.txt | 0 .../py_test}/resources/test_video.h264 | Bin .../py_test}/test_utils.py | 38 ------------- .../scripts}/ci_install_dependencies.sh | 0 {src => vortex_utils/src}/math.cpp | 0 vortex_utils/{ => vortex_utils}/README.md | 0 vortex_utils/{ => vortex_utils}/__init__.py | 0 vortex_utils/{ => vortex_utils}/gst_utils.py | 0 .../{ => vortex_utils}/python_utils.py | 0 vortex_utils_ros/CMakeLists.txt | 49 +++++++++++++++++ vortex_utils_ros/cpp_test/CMakeLists.txt | 23 ++++++++ .../cpp_test}/test_ros_conversions.cpp | 6 +-- .../vortex/utils/ros}/qos_profiles.hpp | 0 .../vortex/utils/ros}/ros_conversions.hpp | 6 +-- vortex_utils_ros/package.xml | 27 ++++++++++ vortex_utils_ros/py_test/CMakeLists.txt | 13 +++++ .../py_test/resources/test_video.h264 | Bin 0 -> 32920 bytes vortex_utils_ros/py_test/test_utils_ros.py | 41 ++++++++++++++ vortex_utils_ros/vortex_utils_ros/__init__.py | 0 .../vortex_utils_ros}/qos_profiles.py | 0 .../vortex_utils_ros}/ros_converter.py | 0 vortex_utils_ros_tf/CMakeLists.txt | 51 ++++++++++++++++++ vortex_utils_ros_tf/cpp_test/CMakeLists.txt | 25 +++++++++ .../cpp_test}/test_ros_transforms.cpp | 2 +- .../vortex/utils/ros}/ros_transforms.hpp | 0 vortex_utils_ros_tf/package.xml | 25 +++++++++ 37 files changed, 288 insertions(+), 98 deletions(-) delete mode 100644 cpp_test/test_main.cpp rename CMakeLists.txt => vortex_utils/CMakeLists.txt (80%) rename {cpp_test => vortex_utils/cpp_test}/CMakeLists.txt (54%) rename {cpp_test => vortex_utils/cpp_test}/test_concepts.cpp (100%) rename {cpp_test => vortex_utils/cpp_test}/test_math.cpp (100%) rename {cpp_test => vortex_utils/cpp_test}/test_types.cpp (100%) rename {include => vortex_utils/include}/vortex/utils/concepts.hpp (100%) rename {include => vortex_utils/include}/vortex/utils/math.hpp (100%) rename {include => vortex_utils/include}/vortex/utils/types.hpp (100%) rename package.xml => vortex_utils/package.xml (83%) rename {py_test => vortex_utils/py_test}/CMakeLists.txt (100%) rename {py_test => vortex_utils/py_test}/resources/test_video.h264 (100%) rename {py_test => vortex_utils/py_test}/test_utils.py (88%) rename {scripts => vortex_utils/scripts}/ci_install_dependencies.sh (100%) rename {src => vortex_utils/src}/math.cpp (100%) rename vortex_utils/{ => vortex_utils}/README.md (100%) rename vortex_utils/{ => vortex_utils}/__init__.py (100%) rename vortex_utils/{ => vortex_utils}/gst_utils.py (100%) rename vortex_utils/{ => vortex_utils}/python_utils.py (100%) create mode 100644 vortex_utils_ros/CMakeLists.txt create mode 100644 vortex_utils_ros/cpp_test/CMakeLists.txt rename {cpp_test => vortex_utils_ros/cpp_test}/test_ros_conversions.cpp (98%) rename {include/vortex/utils => vortex_utils_ros/include/vortex/utils/ros}/qos_profiles.hpp (100%) rename {include/vortex/utils => vortex_utils_ros/include/vortex/utils/ros}/ros_conversions.hpp (98%) create mode 100644 vortex_utils_ros/package.xml create mode 100644 vortex_utils_ros/py_test/CMakeLists.txt create mode 100644 vortex_utils_ros/py_test/resources/test_video.h264 create mode 100644 vortex_utils_ros/py_test/test_utils_ros.py create mode 100644 vortex_utils_ros/vortex_utils_ros/__init__.py rename {vortex_utils => vortex_utils_ros/vortex_utils_ros}/qos_profiles.py (100%) rename {vortex_utils => vortex_utils_ros/vortex_utils_ros}/ros_converter.py (100%) create mode 100644 vortex_utils_ros_tf/CMakeLists.txt create mode 100644 vortex_utils_ros_tf/cpp_test/CMakeLists.txt rename {cpp_test => vortex_utils_ros_tf/cpp_test}/test_ros_transforms.cpp (98%) rename {include/vortex/utils => vortex_utils_ros_tf/include/vortex/utils/ros}/ros_transforms.hpp (100%) create mode 100644 vortex_utils_ros_tf/package.xml diff --git a/README.md b/README.md index 2417dfd..989f2d6 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,36 @@ [![codecov](https://codecov.io/github/vortexntnu/vortex-utils/graph/badge.svg?token=d6D7d5xNdf)](https://codecov.io/github/vortexntnu/vortex-utils) This package contains common definitions and often-used utility functions in C++ and Python. +It consist of the packages `vortex_utils`, `vortex_utils_ros` and `vortex_utils_ros_tf` # Usage +CMake: + +```CMake +find_package(vortex_utils REQUIRED) + +# For ROS utils +find_package(vortex_utils_ros REQUIRED) +find_package(vortex_utils_ros_tf REQUIRED) +``` +For cpp packages link against the target: +```CMake +target_link_libraries(my_target vortex_utils) + +# ROS libraries +target_link_libraries(my_target vortex_utils_ros) +# ROS tf2 transform library +target_link_libraries(my_target vortex_utils_ros_tf) + +``` In Python, import your desired function/dataclass like for example: ```python from vortex_utils.python_utils import ssa ``` +To import from `vortex_utils_ros` ```python -from vortex_utils.qos_profiles import sensor_data_profile, reliable_profile +from vortex_utils_ros.qos_profiles import sensor_data_profile, reliable_profile ``` In C++, include @@ -21,10 +42,10 @@ In C++, include ``` for mathematical functions, ```C++ -#include -``` -for common QoS profile definitions, and -```C++ #include ``` for common structs like 6DOF `PoseEuler`, `Pose` and `Twist`. +```C++ +#include +``` +for common QoS profile definitions from from `vortex_utils_ros` diff --git a/cpp_test/test_main.cpp b/cpp_test/test_main.cpp deleted file mode 100644 index 5ebbc76..0000000 --- a/cpp_test/test_main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include - -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/CMakeLists.txt b/vortex_utils/CMakeLists.txt similarity index 80% rename from CMakeLists.txt rename to vortex_utils/CMakeLists.txt index 4a75054..62423ce 100644 --- a/CMakeLists.txt +++ b/vortex_utils/CMakeLists.txt @@ -5,15 +5,8 @@ set(CMAKE_CXX_STANDARD 20) add_compile_options(-Wall -Wextra -Wpedantic) find_package(ament_cmake REQUIRED) -find_package(rclcpp REQUIRED) find_package(ament_cmake_python REQUIRED) find_package(Eigen3 REQUIRED) -find_package(tf2 REQUIRED) -find_package(tf2_ros REQUIRED) -find_package(tf2_geometry_msgs REQUIRED) -find_package(geometry_msgs REQUIRED) - -include_directories(include) add_library(${PROJECT_NAME} SHARED src/math.cpp) @@ -23,12 +16,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC $) ament_target_dependencies(${PROJECT_NAME} PUBLIC - rclcpp Eigen3 - tf2 - tf2_ros - tf2_geometry_msgs - geometry_msgs ) ament_export_targets(${PROJECT_NAME} HAS_LIBRARY_TARGET) diff --git a/cpp_test/CMakeLists.txt b/vortex_utils/cpp_test/CMakeLists.txt similarity index 54% rename from cpp_test/CMakeLists.txt rename to vortex_utils/cpp_test/CMakeLists.txt index 33f573c..b2eef42 100644 --- a/cpp_test/CMakeLists.txt +++ b/vortex_utils/cpp_test/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.8) find_package(GTest REQUIRED) +find_package(Eigen3 REQUIRED) include(GoogleTest) set(TEST_BINARY_NAME ${PROJECT_NAME}_test) @@ -9,7 +10,6 @@ add_executable( test_math.cpp test_types.cpp test_concepts.cpp - test_ros_conversions.cpp ) target_link_libraries( @@ -23,26 +23,3 @@ target_link_libraries( ament_target_dependencies(${TEST_BINARY_NAME} PUBLIC Eigen3) gtest_discover_tests(${TEST_BINARY_NAME}) - -set(TEST_BINARY_ROS_NAME ${PROJECT_NAME}_ros_test) - -add_executable( - ${TEST_BINARY_ROS_NAME} - test_ros_transforms.cpp -) - -target_link_libraries( - ${TEST_BINARY_ROS_NAME} - ${PROJECT_NAME} - GTest::GTest -) - -ament_target_dependencies( - ${TEST_BINARY_ROS_NAME} - rclcpp - tf2 - tf2_ros - tf2_geometry_msgs -) - -gtest_discover_tests(${TEST_BINARY_ROS_NAME}) diff --git a/cpp_test/test_concepts.cpp b/vortex_utils/cpp_test/test_concepts.cpp similarity index 100% rename from cpp_test/test_concepts.cpp rename to vortex_utils/cpp_test/test_concepts.cpp diff --git a/cpp_test/test_math.cpp b/vortex_utils/cpp_test/test_math.cpp similarity index 100% rename from cpp_test/test_math.cpp rename to vortex_utils/cpp_test/test_math.cpp diff --git a/cpp_test/test_types.cpp b/vortex_utils/cpp_test/test_types.cpp similarity index 100% rename from cpp_test/test_types.cpp rename to vortex_utils/cpp_test/test_types.cpp diff --git a/include/vortex/utils/concepts.hpp b/vortex_utils/include/vortex/utils/concepts.hpp similarity index 100% rename from include/vortex/utils/concepts.hpp rename to vortex_utils/include/vortex/utils/concepts.hpp diff --git a/include/vortex/utils/math.hpp b/vortex_utils/include/vortex/utils/math.hpp similarity index 100% rename from include/vortex/utils/math.hpp rename to vortex_utils/include/vortex/utils/math.hpp diff --git a/include/vortex/utils/types.hpp b/vortex_utils/include/vortex/utils/types.hpp similarity index 100% rename from include/vortex/utils/types.hpp rename to vortex_utils/include/vortex/utils/types.hpp diff --git a/package.xml b/vortex_utils/package.xml similarity index 83% rename from package.xml rename to vortex_utils/package.xml index 61ae562..22d8452 100644 --- a/package.xml +++ b/vortex_utils/package.xml @@ -10,15 +10,9 @@ ament_cmake ament_cmake_python - rclcpp eigen python3-numpy python3-scipy - tf2 - tf2_ros - tf2_geometry_msgs - geometry_msgs - ament_cmake_pytest ament_cmake_gtest diff --git a/py_test/CMakeLists.txt b/vortex_utils/py_test/CMakeLists.txt similarity index 100% rename from py_test/CMakeLists.txt rename to vortex_utils/py_test/CMakeLists.txt diff --git a/py_test/resources/test_video.h264 b/vortex_utils/py_test/resources/test_video.h264 similarity index 100% rename from py_test/resources/test_video.h264 rename to vortex_utils/py_test/resources/test_video.h264 diff --git a/py_test/test_utils.py b/vortex_utils/py_test/test_utils.py similarity index 88% rename from py_test/test_utils.py rename to vortex_utils/py_test/test_utils.py index 764a11d..f6064a0 100644 --- a/py_test/test_utils.py +++ b/vortex_utils/py_test/test_utils.py @@ -3,7 +3,6 @@ import numpy as np import pytest -from geometry_msgs.msg import Pose, Twist from gi.repository import Gst from vortex_utils.gst_utils import H264Decoder @@ -15,7 +14,6 @@ quat_to_euler, ssa, ) -from vortex_utils.ros_converter import pose_from_ros, twist_from_ros def test_ssa_zero(): @@ -285,39 +283,3 @@ def test_h264_decoder_stops_cleanly(decoder): assert decoder._pipeline.get_state(0)[1] == Gst.State.NULL, ( "Decoder did not shut down properly." ) - - -def test_pose_from_ros(): - pose_msg = Pose() - pose_msg.position.x = 1.0 - pose_msg.position.y = 2.0 - pose_msg.position.z = 3.0 - pose_msg.orientation.x = 0.1 - pose_msg.orientation.y = 0.2 - pose_msg.orientation.z = 0.3 - pose_msg.orientation.w = 0.4 - euler = quat_to_euler(0.1, 0.2, 0.3, 0.4) - pose = pose_from_ros(pose_msg) - assert pose.x == 1.0 - assert pose.y == 2.0 - assert pose.z == 3.0 - assert pose.roll == pytest.approx(euler[0], abs=0.01) - assert pose.pitch == pytest.approx(euler[1], abs=0.01) - assert pose.yaw == pytest.approx(euler[2], abs=0.01) - - -def test_twist_from_ros(): - twist_msg = Twist() - twist_msg.linear.x = 1.0 - twist_msg.linear.y = 2.0 - twist_msg.linear.z = 3.0 - twist_msg.angular.x = 0.1 - twist_msg.angular.y = 0.2 - twist_msg.angular.z = 0.3 - twist = twist_from_ros(twist_msg) - assert twist.linear_x == 1.0 - assert twist.linear_y == 2.0 - assert twist.linear_z == 3.0 - assert twist.angular_x == 0.1 - assert twist.angular_y == 0.2 - assert twist.angular_z == 0.3 diff --git a/scripts/ci_install_dependencies.sh b/vortex_utils/scripts/ci_install_dependencies.sh similarity index 100% rename from scripts/ci_install_dependencies.sh rename to vortex_utils/scripts/ci_install_dependencies.sh diff --git a/src/math.cpp b/vortex_utils/src/math.cpp similarity index 100% rename from src/math.cpp rename to vortex_utils/src/math.cpp diff --git a/vortex_utils/README.md b/vortex_utils/vortex_utils/README.md similarity index 100% rename from vortex_utils/README.md rename to vortex_utils/vortex_utils/README.md diff --git a/vortex_utils/__init__.py b/vortex_utils/vortex_utils/__init__.py similarity index 100% rename from vortex_utils/__init__.py rename to vortex_utils/vortex_utils/__init__.py diff --git a/vortex_utils/gst_utils.py b/vortex_utils/vortex_utils/gst_utils.py similarity index 100% rename from vortex_utils/gst_utils.py rename to vortex_utils/vortex_utils/gst_utils.py diff --git a/vortex_utils/python_utils.py b/vortex_utils/vortex_utils/python_utils.py similarity index 100% rename from vortex_utils/python_utils.py rename to vortex_utils/vortex_utils/python_utils.py diff --git a/vortex_utils_ros/CMakeLists.txt b/vortex_utils_ros/CMakeLists.txt new file mode 100644 index 0000000..d52800b --- /dev/null +++ b/vortex_utils_ros/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.8) +project(vortex_utils_ros) + +set(CMAKE_CXX_STANDARD 20) +add_compile_options(-Wall -Wextra -Wpedantic) + +find_package(ament_cmake REQUIRED) +find_package(vortex_utils REQUIRED) +find_package(rclcpp REQUIRED) +find_package(ament_cmake_python REQUIRED) +find_package(Eigen3 REQUIRED) +find_package(geometry_msgs REQUIRED) + +add_library(vortex_utils_ros INTERFACE) + +target_include_directories(vortex_utils_ros INTERFACE + $ + $ +) + +ament_target_dependencies(vortex_utils_ros INTERFACE + vortex_utils + rclcpp + Eigen3 + geometry_msgs +) + +install( + DIRECTORY include/ + DESTINATION include +) + +install( + TARGETS vortex_utils_ros + EXPORT vortex_utils_ros_targets +) + +ament_export_targets(vortex_utils_ros_targets) + +ament_export_dependencies() +ament_export_include_directories(include) +ament_python_install_package(${PROJECT_NAME}) + +if(BUILD_TESTING) + add_subdirectory(py_test) + add_subdirectory(cpp_test) +endif() + +ament_package() diff --git a/vortex_utils_ros/cpp_test/CMakeLists.txt b/vortex_utils_ros/cpp_test/CMakeLists.txt new file mode 100644 index 0000000..dd4b874 --- /dev/null +++ b/vortex_utils_ros/cpp_test/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 3.8) + +find_package(GTest REQUIRED) +include(GoogleTest) + +set(TEST_BINARY_NAME ${PROJECT_NAME}_test) + +add_executable(${TEST_BINARY_NAME} + test_ros_conversions.cpp +) + +target_link_libraries(${TEST_BINARY_NAME} + PRIVATE + ${PROJECT_NAME} + GTest::GTest + GTest::gtest_main +) + +ament_target_dependencies(${TEST_BINARY_NAME} PUBLIC + rclcpp +) + +gtest_discover_tests(${TEST_BINARY_NAME}) diff --git a/cpp_test/test_ros_conversions.cpp b/vortex_utils_ros/cpp_test/test_ros_conversions.cpp similarity index 98% rename from cpp_test/test_ros_conversions.cpp rename to vortex_utils_ros/cpp_test/test_ros_conversions.cpp index 69c7714..9eec425 100644 --- a/cpp_test/test_ros_conversions.cpp +++ b/vortex_utils_ros/cpp_test/test_ros_conversions.cpp @@ -8,9 +8,9 @@ #include #include -#include "vortex/utils/math.hpp" -#include "vortex/utils/ros_conversions.hpp" -#include "vortex/utils/types.hpp" +#include +#include +#include "vortex/utils/ros/ros_conversions.hpp" struct HasEulerPose { double x = 0, y = 0, z = 0; diff --git a/include/vortex/utils/qos_profiles.hpp b/vortex_utils_ros/include/vortex/utils/ros/qos_profiles.hpp similarity index 100% rename from include/vortex/utils/qos_profiles.hpp rename to vortex_utils_ros/include/vortex/utils/ros/qos_profiles.hpp diff --git a/include/vortex/utils/ros_conversions.hpp b/vortex_utils_ros/include/vortex/utils/ros/ros_conversions.hpp similarity index 98% rename from include/vortex/utils/ros_conversions.hpp rename to vortex_utils_ros/include/vortex/utils/ros/ros_conversions.hpp index 3dc7a31..eb3999c 100644 --- a/include/vortex/utils/ros_conversions.hpp +++ b/vortex_utils_ros/include/vortex/utils/ros/ros_conversions.hpp @@ -14,9 +14,9 @@ #include #include -#include "concepts.hpp" -#include "math.hpp" -#include "types.hpp" +#include +#include +#include namespace vortex::utils::ros_conversions { diff --git a/vortex_utils_ros/package.xml b/vortex_utils_ros/package.xml new file mode 100644 index 0000000..087735b --- /dev/null +++ b/vortex_utils_ros/package.xml @@ -0,0 +1,27 @@ + + + + vortex_utils_ros + 0.0.0 + Package containing ROS utility functions and data structures. + jorgenfj + MIT + + ament_cmake + ament_cmake_python + + rclcpp + vortex_utils + eigen + python3-numpy + python3-scipy + geometry_msgs + + + ament_cmake_pytest + ament_cmake_gtest + + + ament_cmake + + diff --git a/vortex_utils_ros/py_test/CMakeLists.txt b/vortex_utils_ros/py_test/CMakeLists.txt new file mode 100644 index 0000000..eed0cdf --- /dev/null +++ b/vortex_utils_ros/py_test/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.8) + +find_package(ament_cmake_pytest REQUIRED) +set(_pytest_tests + test_utils_ros.py +) +foreach(_test_path ${_pytest_tests}) + get_filename_component(_test_name ${_test_path} NAME_WE) + ament_add_pytest_test(${_test_name} ${_test_path} + APPEND_ENV PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +endforeach() diff --git a/vortex_utils_ros/py_test/resources/test_video.h264 b/vortex_utils_ros/py_test/resources/test_video.h264 new file mode 100644 index 0000000000000000000000000000000000000000..a56ce6a11fb362d5ec60830b4db4d1dff200eff4 GIT binary patch literal 32920 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

hlOeH48=5c>P2izf_iML&X{ZpP}) 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

BWd#?9 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 RHEH*)y7?Gpn-4Y;{~thsX~X~k literal 0 HcmV?d00001 diff --git a/vortex_utils_ros/py_test/test_utils_ros.py b/vortex_utils_ros/py_test/test_utils_ros.py new file mode 100644 index 0000000..03ecbd5 --- /dev/null +++ b/vortex_utils_ros/py_test/test_utils_ros.py @@ -0,0 +1,41 @@ +import pytest +from geometry_msgs.msg import Pose, Twist + +from vortex_utils.python_utils import quat_to_euler +from vortex_utils_ros.ros_converter import pose_from_ros, twist_from_ros + + +def test_pose_from_ros(): + pose_msg = Pose() + pose_msg.position.x = 1.0 + pose_msg.position.y = 2.0 + pose_msg.position.z = 3.0 + pose_msg.orientation.x = 0.1 + pose_msg.orientation.y = 0.2 + pose_msg.orientation.z = 0.3 + pose_msg.orientation.w = 0.4 + euler = quat_to_euler(0.1, 0.2, 0.3, 0.4) + pose = pose_from_ros(pose_msg) + assert pose.x == 1.0 + assert pose.y == 2.0 + assert pose.z == 3.0 + assert pose.roll == pytest.approx(euler[0], abs=0.01) + assert pose.pitch == pytest.approx(euler[1], abs=0.01) + assert pose.yaw == pytest.approx(euler[2], abs=0.01) + + +def test_twist_from_ros(): + twist_msg = Twist() + twist_msg.linear.x = 1.0 + twist_msg.linear.y = 2.0 + twist_msg.linear.z = 3.0 + twist_msg.angular.x = 0.1 + twist_msg.angular.y = 0.2 + twist_msg.angular.z = 0.3 + twist = twist_from_ros(twist_msg) + assert twist.linear_x == 1.0 + assert twist.linear_y == 2.0 + assert twist.linear_z == 3.0 + assert twist.angular_x == 0.1 + assert twist.angular_y == 0.2 + assert twist.angular_z == 0.3 diff --git a/vortex_utils_ros/vortex_utils_ros/__init__.py b/vortex_utils_ros/vortex_utils_ros/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vortex_utils/qos_profiles.py b/vortex_utils_ros/vortex_utils_ros/qos_profiles.py similarity index 100% rename from vortex_utils/qos_profiles.py rename to vortex_utils_ros/vortex_utils_ros/qos_profiles.py diff --git a/vortex_utils/ros_converter.py b/vortex_utils_ros/vortex_utils_ros/ros_converter.py similarity index 100% rename from vortex_utils/ros_converter.py rename to vortex_utils_ros/vortex_utils_ros/ros_converter.py diff --git a/vortex_utils_ros_tf/CMakeLists.txt b/vortex_utils_ros_tf/CMakeLists.txt new file mode 100644 index 0000000..6b21759 --- /dev/null +++ b/vortex_utils_ros_tf/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.8) +project(vortex_utils_ros_tf) + +set(CMAKE_CXX_STANDARD 20) +add_compile_options(-Wall -Wextra -Wpedantic) + +find_package(ament_cmake REQUIRED) +find_package(vortex_utils REQUIRED) +find_package(rclcpp REQUIRED) +find_package(ament_cmake_python REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(tf2 REQUIRED) +find_package(tf2_ros REQUIRED) +find_package(tf2_geometry_msgs REQUIRED) + +add_library(vortex_utils_ros_tf INTERFACE) + +target_include_directories(vortex_utils_ros_tf INTERFACE + $ + $ +) + +ament_target_dependencies(vortex_utils_ros_tf INTERFACE + vortex_utils + rclcpp + tf2 + tf2_ros + tf2_geometry_msgs + geometry_msgs +) + +install( + DIRECTORY include/ + DESTINATION include +) + +install( + TARGETS vortex_utils_ros_tf + EXPORT vortex_utils_ros_targets +) + +ament_export_targets(vortex_utils_ros_targets) + +ament_export_dependencies() +ament_export_include_directories(include) + +if(BUILD_TESTING) + add_subdirectory(cpp_test) +endif() + +ament_package() diff --git a/vortex_utils_ros_tf/cpp_test/CMakeLists.txt b/vortex_utils_ros_tf/cpp_test/CMakeLists.txt new file mode 100644 index 0000000..1e15427 --- /dev/null +++ b/vortex_utils_ros_tf/cpp_test/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.8) + +find_package(GTest REQUIRED) +include(GoogleTest) + +set(TEST_BINARY_TF_NAME ${PROJECT_NAME}_test) + + +add_executable(${TEST_BINARY_TF_NAME} + test_ros_transforms.cpp +) + +target_link_libraries(${TEST_BINARY_TF_NAME} + ${PROJECT_NAME} + GTest::GTest +) + +ament_target_dependencies(${TEST_BINARY_TF_NAME} + rclcpp + tf2 + tf2_ros + tf2_geometry_msgs +) + +gtest_discover_tests(${TEST_BINARY_TF_NAME}) diff --git a/cpp_test/test_ros_transforms.cpp b/vortex_utils_ros_tf/cpp_test/test_ros_transforms.cpp similarity index 98% rename from cpp_test/test_ros_transforms.cpp rename to vortex_utils_ros_tf/cpp_test/test_ros_transforms.cpp index 9335be3..f87feac 100644 --- a/cpp_test/test_ros_transforms.cpp +++ b/vortex_utils_ros_tf/cpp_test/test_ros_transforms.cpp @@ -12,7 +12,7 @@ #include #include -#include "vortex/utils/ros_transforms.hpp" +#include "vortex/utils/ros/ros_transforms.hpp" class RosTransformsTest : public ::testing::Test { protected: diff --git a/include/vortex/utils/ros_transforms.hpp b/vortex_utils_ros_tf/include/vortex/utils/ros/ros_transforms.hpp similarity index 100% rename from include/vortex/utils/ros_transforms.hpp rename to vortex_utils_ros_tf/include/vortex/utils/ros/ros_transforms.hpp diff --git a/vortex_utils_ros_tf/package.xml b/vortex_utils_ros_tf/package.xml new file mode 100644 index 0000000..e478262 --- /dev/null +++ b/vortex_utils_ros_tf/package.xml @@ -0,0 +1,25 @@ + + + + vortex_utils_ros_tf + 0.0.0 + Package containing ROS tf2 utility functions. + jorgenfj + MIT + + ament_cmake + + rclcpp + vortex_utils + eigen + tf2 + tf2_ros + tf2_geometry_msgs + geometry_msgs + + ament_cmake_gtest + + + ament_cmake + + From eb313dd8f503e07761ddbf3dfbce1bf4dc93b1a2 Mon Sep 17 00:00:00 2001 From: Jorgen Fjermedal Date: Tue, 23 Dec 2025 20:49:41 +0100 Subject: [PATCH 2/2] moved scripts folder to parent --- {vortex_utils/scripts => scripts}/ci_install_dependencies.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {vortex_utils/scripts => scripts}/ci_install_dependencies.sh (100%) diff --git a/vortex_utils/scripts/ci_install_dependencies.sh b/scripts/ci_install_dependencies.sh similarity index 100% rename from vortex_utils/scripts/ci_install_dependencies.sh rename to scripts/ci_install_dependencies.sh