├── .dockerignore ├── BuildROS2 ├── __init__.py ├── setup_ros2_pkgs.sh ├── setup_ros2_base.sh ├── patches │ ├── foxy │ │ ├── rcpputils.patch │ │ ├── asio.patch │ │ ├── Fast-DDS.patch │ │ └── rcutils.patch │ ├── humble │ │ ├── rcpputils.patch │ │ ├── asio.patch │ │ ├── Fast-DDS.patch │ │ └── rcutils.patch │ └── jazzy │ │ ├── rcpputils.patch │ │ ├── asio.patch │ │ ├── Fast-DDS.patch │ │ └── rcutils.patch ├── build_ros2_pkgs.sh ├── build_ros2_base.sh ├── build_and_install_ros2_pkgs.py ├── build_and_install_ros2.py ├── build_and_install_ros2_base.py ├── pre_build_ros2_base.sh └── libs_utils.py ├── requirements.txt ├── .gitignore ├── __pycache__ └── build_install_codegen.cpython-38.pyc ├── ros2_custom_pkgs.repos ├── entrypoint.sh ├── CodeGen ├── format.sh ├── templates │ ├── Msg.cpp │ ├── Msg.h │ ├── Srv.cpp │ ├── Srv.h │ ├── Action.cpp │ └── Action.h ├── post_generate.py └── gen_ue_from_ros.py ├── ros2_additional_pkgs.repos ├── CHANGELOG.md ├── Dockerfile.foxy ├── Dockerfile.humble ├── Dockerfile.jazzy ├── custom_config.yaml ├── config_ue_msg_update_example.yaml ├── default_config.yaml.foxy ├── default_config.yaml.humble ├── default_config.yaml.jazzy ├── docker_build_install_codegen.py └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | ros2_ws -------------------------------------------------------------------------------- /BuildROS2/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | jinja2 3 | vcstool 4 | colcon-common-extensions 5 | numpy 6 | docker 7 | empy==3.3.4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | */__pycache__/* 3 | CodeGen/.vscode/ 4 | CodeGen/*.log 5 | CodeGen/*/*.h 6 | CodeGen/*/*.cpp 7 | ros2_ws/ 8 | tmp/ 9 | -------------------------------------------------------------------------------- /__pycache__/build_install_codegen.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rapyuta-robotics/UE_tools/HEAD/__pycache__/build_install_codegen.cpython-38.pyc -------------------------------------------------------------------------------- /ros2_custom_pkgs.repos: -------------------------------------------------------------------------------- 1 | repositories: 2 | navigation2: 3 | type: git 4 | url: https://github.com/ros-planning/navigation2.git 5 | version: humble -------------------------------------------------------------------------------- /BuildROS2/setup_ros2_pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROS2_WS=$1 4 | ROS_DISTRO=$2 5 | PKGS=$3 6 | 7 | # build ros2 8 | ./build_ros2_pkgs.sh $ROS2_WS $ROS_DISTRO "$PKGS" 9 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Set User ID: ${USER_ID:-1000}" 4 | usermod -u ${USER_ID:-1000} admin 5 | 6 | echo "Set Group ID: ${GROUP_ID:-1000}" 7 | sudo groupmod -g ${GROUP_ID:-1000} admin 8 | 9 | exec "$@" -------------------------------------------------------------------------------- /CodeGen/format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find . \ 4 | -not \( -path "*/templates/*" -prune \) \ 5 | \( -name \*.h -o -name \*.hpp -o -name \*.c -o -name \*.cc -o -name \*.cpp \) \ 6 | | xargs clang-format-10 -style=file -i -------------------------------------------------------------------------------- /BuildROS2/setup_ros2_base.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ROS2_WS=$1 4 | ROS_DISTRO=$2 5 | 6 | # install dependency and get ros2 sources 7 | ./pre_build_ros2_base.sh $ROS2_WS $ROS_DISTRO 8 | 9 | # build ros2 10 | ./build_ros2_base.sh $ROS2_WS $ROS_DISTRO 11 | -------------------------------------------------------------------------------- /BuildROS2/patches/foxy/rcpputils.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/find_library.cpp b/src/find_library.cpp 2 | index 4fe18f2..4042980 100644 3 | --- a/src/find_library.cpp 4 | +++ b/src/find_library.cpp 5 | @@ -67,7 +67,7 @@ std::string find_library_path(const std::string & library_name) 6 | return path; 7 | } 8 | } 9 | - return ""; 10 | + return "lib" + library_name + ".so"; 11 | } 12 | 13 | } // namespace rcpputils 14 | -------------------------------------------------------------------------------- /BuildROS2/patches/humble/rcpputils.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/find_library.cpp b/src/find_library.cpp 2 | index ce23987..6a333c0 100644 3 | --- a/src/find_library.cpp 4 | +++ b/src/find_library.cpp 5 | @@ -66,7 +66,7 @@ std::string find_library_path(const std::string & library_name) 6 | return path; 7 | } 8 | } 9 | - return ""; 10 | + return "lib" + library_name + ".so"; 11 | } 12 | 13 | std::string path_for_library(const std::string & directory, const std::string & library_name) 14 | -------------------------------------------------------------------------------- /BuildROS2/patches/jazzy/rcpputils.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/find_library.cpp b/src/find_library.cpp 2 | index ce23987..6a333c0 100644 3 | --- a/src/find_library.cpp 4 | +++ b/src/find_library.cpp 5 | @@ -66,7 +66,7 @@ std::string find_library_path(const std::string & library_name) 6 | return path; 7 | } 8 | } 9 | - return ""; 10 | + return "lib" + library_name + ".so"; 11 | } 12 | 13 | std::string path_for_library(const std::string & directory, const std::string & library_name) 14 | -------------------------------------------------------------------------------- /ros2_additional_pkgs.repos: -------------------------------------------------------------------------------- 1 | repositories: 2 | ros-perception/pcl_msgs: 3 | type: git 4 | url: https://github.com/ros-perception/pcl_msgs.git 5 | version: ros2 6 | UE_msgs: 7 | type: git 8 | url: https://github.com/rapyuta-robotics/UE_msgs.git 9 | version: devel 10 | example_interfaces: 11 | type: git 12 | url: https://github.com/rapyuta-robotics/example_interfaces.git 13 | version: master 14 | simulation_interfaces: 15 | type: git 16 | url: https://github.com/ros-simulation/simulation_interfaces.git 17 | version: main -------------------------------------------------------------------------------- /BuildROS2/patches/foxy/asio.patch: -------------------------------------------------------------------------------- 1 | diff --git a/asio/include/asio/system_error.hpp b/asio/include/asio/system_error.hpp 2 | index 63908941..c69f1123 100644 3 | --- a/asio/include/asio/system_error.hpp 4 | +++ b/asio/include/asio/system_error.hpp 5 | @@ -17,7 +17,7 @@ 6 | 7 | #include "asio/detail/config.hpp" 8 | 9 | -#if defined(ASIO_HAS_STD_SYSTEM_ERROR) 10 | +#if 0 11 | # include 12 | #else // defined(ASIO_HAS_STD_SYSTEM_ERROR) 13 | # include 14 | @@ -31,7 +31,7 @@ 15 | 16 | namespace asio { 17 | 18 | -#if defined(ASIO_HAS_STD_SYSTEM_ERROR) 19 | +#if 0 20 | 21 | typedef std::system_error system_error; 22 | 23 | -------------------------------------------------------------------------------- /BuildROS2/patches/humble/asio.patch: -------------------------------------------------------------------------------- 1 | diff --git a/asio/include/asio/system_error.hpp b/asio/include/asio/system_error.hpp 2 | index 63908941..c69f1123 100644 3 | --- a/asio/include/asio/system_error.hpp 4 | +++ b/asio/include/asio/system_error.hpp 5 | @@ -17,7 +17,7 @@ 6 | 7 | #include "asio/detail/config.hpp" 8 | 9 | -#if defined(ASIO_HAS_STD_SYSTEM_ERROR) 10 | +#if 0 11 | # include 12 | #else // defined(ASIO_HAS_STD_SYSTEM_ERROR) 13 | # include 14 | @@ -31,7 +31,7 @@ 15 | 16 | namespace asio { 17 | 18 | -#if defined(ASIO_HAS_STD_SYSTEM_ERROR) 19 | +#if 0 20 | 21 | typedef std::system_error system_error; 22 | 23 | -------------------------------------------------------------------------------- /BuildROS2/patches/jazzy/asio.patch: -------------------------------------------------------------------------------- 1 | diff --git a/asio/include/asio/system_error.hpp b/asio/include/asio/system_error.hpp 2 | index 63908941..c69f1123 100644 3 | --- a/asio/include/asio/system_error.hpp 4 | +++ b/asio/include/asio/system_error.hpp 5 | @@ -17,7 +17,7 @@ 6 | 7 | #include "asio/detail/config.hpp" 8 | 9 | -#if defined(ASIO_HAS_STD_SYSTEM_ERROR) 10 | +#if 0 11 | # include 12 | #else // defined(ASIO_HAS_STD_SYSTEM_ERROR) 13 | # include 14 | @@ -31,7 +31,7 @@ 15 | 16 | namespace asio { 17 | 18 | -#if defined(ASIO_HAS_STD_SYSTEM_ERROR) 19 | +#if 0 20 | 21 | typedef std::system_error system_error; 22 | 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for UE_tools repository 2 | 3 | ## 0.0.3 ## 4 | * Refactor to include ros2 lib build scripts #7 5 | 6 | ## 0.0.2 ## 7 | CodeGeneratorFromPath.py 8 | * Remove all '_' in outputted UE struct type name 9 | * Add `is_valid_group_name()` before assigning `types_cpp[group_name]` to `info[~Types, ~SetFromROS2, SetROS2]` 10 | * `templates/Action.h, Msg.h, Srv.h` No underscore in UE struct name 11 | * `get_var_names()` -> `get_ue_var_names`() returns a dict {(type, size):var_name} 12 | * `convert_to_cpp_type()` -> `convert_to_ue_type()`: add TArray output 13 | * Add `get_type_default_value_str()` to auto fill-in default value if possible 14 | 15 | ## 0.0.1 ## 16 | * Add CHANGELOG 17 | * Update maintainers 18 | -------------------------------------------------------------------------------- /BuildROS2/patches/humble/Fast-DDS.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CMakeLists.txt b/CMakeLists.txt 2 | index e4e8c41aa..d2feb3d29 100644 3 | --- a/CMakeLists.txt 4 | +++ b/CMakeLists.txt 5 | @@ -161,7 +161,9 @@ if(NOT BUILD_SHARED_LIBS) 6 | endif() 7 | 8 | eprosima_find_package(fastcdr REQUIRED) 9 | +set(EPROSIMA_BUILD ON) 10 | eprosima_find_thirdparty(Asio asio VERSION 1.10.8) 11 | +set(EPROSIMA_BUILD OFF) 12 | eprosima_find_thirdparty(TinyXML2 tinyxml2) 13 | 14 | find_package(foonathan_memory REQUIRED) 15 | diff --git a/thirdparty/asio b/thirdparty/asio 16 | --- a/thirdparty/asio 17 | +++ b/thirdparty/asio 18 | @@ -1 +1 @@ 19 | -Subproject commit b84e6c16b2ea907dbad94206b7510d85aafc0b42 20 | +Subproject commit b84e6c16b2ea907dbad94206b7510d85aafc0b42-dirty 21 | -------------------------------------------------------------------------------- /BuildROS2/patches/jazzy/Fast-DDS.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CMakeLists.txt b/CMakeLists.txt 2 | index e4e8c41aa..d2feb3d29 100644 3 | --- a/CMakeLists.txt 4 | +++ b/CMakeLists.txt 5 | @@ -161,7 +161,9 @@ if(NOT BUILD_SHARED_LIBS) 6 | endif() 7 | 8 | eprosima_find_package(fastcdr REQUIRED) 9 | +set(EPROSIMA_BUILD ON) 10 | eprosima_find_thirdparty(Asio asio VERSION 1.10.8) 11 | +set(EPROSIMA_BUILD OFF) 12 | eprosima_find_thirdparty(TinyXML2 tinyxml2) 13 | 14 | find_package(foonathan_memory REQUIRED) 15 | diff --git a/thirdparty/asio b/thirdparty/asio 16 | --- a/thirdparty/asio 17 | +++ b/thirdparty/asio 18 | @@ -1 +1 @@ 19 | -Subproject commit b84e6c16b2ea907dbad94206b7510d85aafc0b42 20 | +Subproject commit b84e6c16b2ea907dbad94206b7510d85aafc0b42-dirty 21 | -------------------------------------------------------------------------------- /Dockerfile.foxy: -------------------------------------------------------------------------------- 1 | ARG UBUNTU_VER="20.04" 2 | 3 | FROM ubuntu:${UBUNTU_VER} 4 | ARG ROSDISTRO="foxy" 5 | 6 | RUN apt update && apt install sudo -y 7 | 8 | RUN adduser --disabled-password --gecos '' admin && \ 9 | adduser admin sudo && \ 10 | echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 11 | 12 | USER admin 13 | WORKDIR /home/admin 14 | 15 | RUN mkdir UE_tools 16 | 17 | COPY --chown=admin:admin . UE_tools/ 18 | 19 | WORKDIR /home/admin/UE_tools 20 | 21 | # install dependency 22 | ENV DEBIAN_FRONTEND=noninteractive 23 | RUN sudo apt install -y tzdata && \ 24 | sudo apt install -y python3 python3-pip git wget curl software-properties-common && \ 25 | sudo pip3 install -r requirements.txt 26 | 27 | # build base libs and msgs 28 | RUN python3 build_install_codegen.py --type base --build --rosdistro $ROSDISTRO && \ 29 | python3 build_install_codegen.py --type pkgs --build --rosdistro $ROSDISTRO && \ 30 | rm -r ros2_ws/src ros2_ws/build ros2_ws/log 31 | 32 | 33 | ADD entrypoint.sh /entrypoint.sh 34 | ENTRYPOINT [ "/entrypoint.sh" ] -------------------------------------------------------------------------------- /Dockerfile.humble: -------------------------------------------------------------------------------- 1 | ARG UBUNTU_VER="22.04" 2 | 3 | FROM ubuntu:${UBUNTU_VER} 4 | ARG ROSDISTRO="humble" 5 | 6 | RUN apt update && apt install sudo -y 7 | 8 | RUN adduser --disabled-password --gecos '' admin && \ 9 | adduser admin sudo && \ 10 | echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 11 | 12 | USER admin 13 | WORKDIR /home/admin 14 | 15 | RUN mkdir UE_tools 16 | 17 | COPY --chown=admin:admin . UE_tools/ 18 | 19 | WORKDIR /home/admin/UE_tools 20 | 21 | # install dependency 22 | ENV DEBIAN_FRONTEND=noninteractive 23 | RUN sudo apt install -y tzdata && \ 24 | sudo apt install -y python3 python3-pip git wget curl software-properties-common && \ 25 | sudo pip3 install -r requirements.txt 26 | 27 | # build base libs and msgs 28 | RUN python3 build_install_codegen.py --type base --build --rosdistro $ROSDISTRO && \ 29 | python3 build_install_codegen.py --type pkgs --build --rosdistro $ROSDISTRO && \ 30 | rm -r ros2_ws/src ros2_ws/build ros2_ws/log 31 | 32 | 33 | ADD entrypoint.sh /entrypoint.sh 34 | ENTRYPOINT [ "/entrypoint.sh" ] -------------------------------------------------------------------------------- /BuildROS2/patches/foxy/Fast-DDS.patch: -------------------------------------------------------------------------------- 1 | diff --git a/CMakeLists.txt b/CMakeLists.txt 2 | index f74b1378f..a9aa7f3ec 100644 3 | --- a/CMakeLists.txt 4 | +++ b/CMakeLists.txt 5 | @@ -218,7 +218,9 @@ option(BUILD_SHARED_LIBS "Create shared libraries by default" ON) 6 | include(${PROJECT_SOURCE_DIR}/cmake/common/eprosima_libraries.cmake) 7 | 8 | eprosima_find_package(fastcdr REQUIRED) 9 | +set(EPROSIMA_BUILD ON) 10 | eprosima_find_thirdparty(Asio asio) 11 | +set(EPROSIMA_BUILD OFF) 12 | eprosima_find_thirdparty(TinyXML2 tinyxml2) 13 | 14 | find_package(foonathan_memory REQUIRED) 15 | diff --git a/thirdparty/asio b/thirdparty/asio 16 | --- a/thirdparty/asio 17 | +++ b/thirdparty/asio 18 | @@ -1 +1 @@ 19 | -Subproject commit 22afb86087a77037cd296d27134756c9b0d2cb75 20 | +Subproject commit 22afb86087a77037cd296d27134756c9b0d2cb75-dirty 21 | diff --git a/thirdparty/fastcdr b/thirdparty/fastcdr 22 | --- a/thirdparty/fastcdr 23 | +++ b/thirdparty/fastcdr 24 | @@ -1 +1 @@ 25 | -Subproject commit 53a0b8cae0b9083db69821be0edb97c944755591 26 | +Subproject commit 53a0b8cae0b9083db69821be0edb97c944755591-dirty 27 | -------------------------------------------------------------------------------- /BuildROS2/patches/foxy/rcutils.patch: -------------------------------------------------------------------------------- 1 | Index: Source/ThirdParty/ros/include/rcutils/error_handling.h 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/ThirdParty/ros/include/rcutils/error_handling.h b/ThirdParty/ros/include/rcutils/error_handling.h 7 | --- a/ThirdParty/ros/include/rcutils/error_handling.h (revision 584767ce5b22125a3cffe1e9e84618068f1431b4) 8 | +++ b/ThirdParty/ros/include/rcutils/error_handling.h (date 1652721130821) 9 | @@ -88,7 +88,7 @@ 10 | } rcutils_error_state_t; 11 | 12 | // make sure our math is right... 13 | -#if __STDC_VERSION__ >= 201112L 14 | +//#if __STDC_VERSION__ >= 201112L 15 | static_assert( 16 | sizeof(rcutils_error_string_t) == ( 17 | RCUTILS_ERROR_STATE_MESSAGE_MAX_LENGTH + 18 | @@ -97,7 +97,7 @@ 19 | RCUTILS_ERROR_FORMATTING_CHARACTERS + 20 | 1 /* null terminating character */), 21 | "Maximum length calculations incorrect"); 22 | -#endif 23 | +//#endif 24 | 25 | /// Forces initialization of thread-local storage if called in a newly created thread. 26 | /** 27 | -------------------------------------------------------------------------------- /BuildROS2/patches/humble/rcutils.patch: -------------------------------------------------------------------------------- 1 | Index: Source/ThirdParty/ros/include/rcutils/error_handling.h 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/ThirdParty/ros/include/rcutils/rcutils/error_handling.h b/ThirdParty/ros/include/rcutils/error_handling.h 7 | --- a/ThirdParty/ros/include/rcutils/rcutils/error_handling.h (revision 584767ce5b22125a3cffe1e9e84618068f1431b4) 8 | +++ b/ThirdParty/ros/include/rcutils/rcutils/error_handling.h (date 1652721130821) 9 | @@ -88,7 +88,7 @@ 10 | } rcutils_error_state_t; 11 | 12 | // make sure our math is right... 13 | -#if __STDC_VERSION__ >= 201112L 14 | +//#if __STDC_VERSION__ >= 201112L 15 | static_assert( 16 | sizeof(rcutils_error_string_t) == ( 17 | RCUTILS_ERROR_STATE_MESSAGE_MAX_LENGTH + 18 | @@ -97,7 +97,7 @@ 19 | RCUTILS_ERROR_FORMATTING_CHARACTERS + 20 | 1 /* null terminating character */), 21 | "Maximum length calculations incorrect"); 22 | -#endif 23 | +//#endif 24 | 25 | /// Forces initialization of thread-local storage if called in a newly created thread. 26 | /** 27 | -------------------------------------------------------------------------------- /BuildROS2/patches/jazzy/rcutils.patch: -------------------------------------------------------------------------------- 1 | Index: Source/ThirdParty/ros/include/rcutils/error_handling.h 2 | IDEA additional info: 3 | Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP 4 | <+>UTF-8 5 | =================================================================== 6 | diff --git a/ThirdParty/ros/include/rcutils/rcutils/error_handling.h b/ThirdParty/ros/include/rcutils/error_handling.h 7 | --- a/ThirdParty/ros/include/rcutils/rcutils/error_handling.h (revision 584767ce5b22125a3cffe1e9e84618068f1431b4) 8 | +++ b/ThirdParty/ros/include/rcutils/rcutils/error_handling.h (date 1652721130821) 9 | @@ -88,7 +88,7 @@ 10 | } rcutils_error_state_t; 11 | 12 | // make sure our math is right... 13 | -#if __STDC_VERSION__ >= 201112L 14 | +//#if __STDC_VERSION__ >= 201112L 15 | static_assert( 16 | sizeof(rcutils_error_string_t) == ( 17 | RCUTILS_ERROR_STATE_MESSAGE_MAX_LENGTH + 18 | @@ -97,7 +97,7 @@ 19 | RCUTILS_ERROR_FORMATTING_CHARACTERS + 20 | 1 /* null terminating character */), 21 | "Maximum length calculations incorrect"); 22 | -#endif 23 | +//#endif 24 | 25 | /// Forces initialization of thread-local storage if called in a newly created thread. 26 | /** 27 | -------------------------------------------------------------------------------- /Dockerfile.jazzy: -------------------------------------------------------------------------------- 1 | ARG UBUNTU_VER="24.04" 2 | 3 | FROM ubuntu:${UBUNTU_VER} 4 | ARG ROSDISTRO="jazzy" 5 | 6 | RUN apt update && apt install sudo -y 7 | 8 | # in Ubuntu24.04, there is 'ubuntu' with id=1000. 9 | RUN userdel -r ubuntu && apt install adduser && adduser --uid 1000 --disabled-password --gecos '' admin && \ 10 | adduser admin sudo && \ 11 | echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers 12 | 13 | USER admin 14 | WORKDIR /home/admin 15 | 16 | RUN mkdir UE_tools 17 | 18 | COPY --chown=admin:admin . UE_tools/ 19 | 20 | WORKDIR /home/admin/UE_tools 21 | 22 | # install dependency 23 | ENV DEBIAN_FRONTEND=noninteractive 24 | RUN sudo apt install -y tzdata && \ 25 | sudo apt install -y python3 python3-pip git wget curl software-properties-common && \ 26 | pip3 install --break-system-packages -r requirements.txt 27 | 28 | # build base libs and msgs 29 | RUN python3 build_install_codegen.py --type base --build --rosdistro $ROSDISTRO && \ 30 | python3 build_install_codegen.py --type pkgs --build --rosdistro $ROSDISTRO && \ 31 | rm -r ros2_ws/src ros2_ws/build ros2_ws/log 32 | 33 | 34 | ADD entrypoint.sh entrypoint.sh 35 | ENTRYPOINT [ "/home/admin/UE_tools/entrypoint.sh" ] -------------------------------------------------------------------------------- /BuildROS2/build_ros2_pkgs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROS2_WS=$1 4 | ROS_DISTRO=$2 5 | PKGS=$3 6 | 7 | # cleanup 8 | for d in $PKGS ; 9 | do 10 | rm -r $ROS2_WS/build/$d 11 | rm -r $ROS2_WS/install/$d 12 | done 13 | 14 | export LANG=en_US.UTF-8 15 | 16 | # export ROS_DOMAIN_ID=10 17 | # pay attention it can be 'rmw_fastrtps_dynamic_cpp' too 18 | export RMW_IMPLEMENTATION=rmw_fastrtps_cpp 19 | 20 | # use locally installed clang 21 | CLANG_VER=13 22 | if [ $ROS_DISTRO == "jazzy" ]; then 23 | CLANG_VER=18 24 | fi 25 | 26 | export CC="/usr/bin/clang" 27 | export CXX="/usr/bin/clang++" 28 | 29 | 30 | # -latomic issue - see more here https://github.com/ros2/ros2/issues/418 31 | export MY_LINKER_FLAGS="-latomic "\ 32 | "-Wl,-rpath=\${ORIGIN} "\ 33 | "-Wl,-rpath-link=/usr/lib/x86_64-linux-gnu "\ 34 | "-Wl,-rpath-link=/usr/lib " 35 | 36 | echo build $PKGS 37 | 38 | pushd $ROS2_WS 39 | source install/setup.bash 40 | colcon build \ 41 | --packages-skip-build-finished \ 42 | --packages-up-to $PKGS \ 43 | --cmake-args \ 44 | "-DCMAKE_SHARED_LINKER_FLAGS='$MY_LINKER_FLAGS'" \ 45 | "-DCMAKE_EXE_LINKER_FLAGS='$MY_LINKER_FLAGS'" \ 46 | -DBUILD_TESTING=OFF --no-warn-unused-cli 47 | popd -------------------------------------------------------------------------------- /CodeGen/templates/Msg.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | // This code has been autogenerated from {{data.Filename}} - do not modify 3 | 4 | #include "Msgs/ROS2{{data.UEName}}.h" 5 | 6 | #include "Kismet/GameplayStatics.h" 7 | 8 | {{data.ConstantsDef}} 9 | 10 | void UROS2{{data.UEName}}Msg::Init() 11 | { 12 | {{data.Group}}__msg__{{data.NameCap}}__init(&{{data.Name}}_msg); 13 | } 14 | 15 | void UROS2{{data.UEName}}Msg::Fini() 16 | { 17 | {{data.Group}}__msg__{{data.NameCap}}__fini(&{{data.Name}}_msg); 18 | } 19 | 20 | const rosidl_message_type_support_t* UROS2{{data.UEName}}Msg::GetTypeSupport() const 21 | { 22 | return ROSIDL_GET_MSG_TYPE_SUPPORT({{data.Group}}, msg, {{data.NameCap}}); 23 | } 24 | 25 | void UROS2{{data.UEName}}Msg::SetMsg(const FROS{{data.UEName}}& Inputs) 26 | { 27 | Inputs.SetROS2({{data.Name}}_msg); 28 | } 29 | 30 | void UROS2{{data.UEName}}Msg::GetMsg(FROS{{data.UEName}}& Outputs) const 31 | { 32 | Outputs.SetFromROS2({{data.Name}}_msg); 33 | } 34 | 35 | void* UROS2{{data.UEName}}Msg::Get() 36 | { 37 | return &{{data.Name}}_msg; 38 | } 39 | 40 | FString UROS2{{data.UEName}}Msg::MsgToString() const 41 | { 42 | /* TODO: Fill here */ 43 | checkNoEntry(); 44 | return FString(); 45 | } -------------------------------------------------------------------------------- /CodeGen/templates/Msg.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | // This code has been autogenerated from {{data.Filename}} - do not modify 3 | 4 | #pragma once 5 | 6 | // UE 7 | #include "CoreMinimal.h" 8 | 9 | // ROS 10 | #include "{{data.Group}}/msg/{{data.Name}}.h" 11 | 12 | // rclUE 13 | #include "Msgs/ROS2GenericMsg.h" 14 | #include "rclcUtilities.h" 15 | 16 | // Generated Msg/Srv/Action(can be empty) 17 | {{data.Headers}} 18 | 19 | // Generated 20 | #include "ROS2{{data.UEName}}.generated.h" 21 | 22 | USTRUCT(Blueprintable) 23 | struct {{data.ModuleAPI}} FROS{{data.UEName}} 24 | { 25 | GENERATED_BODY() 26 | 27 | public: 28 | {{data.ConstantsDec}} 29 | 30 | {{data.Types}} 31 | 32 | FROS{{data.UEName}}() 33 | { 34 | {{data.Constructor}} 35 | } 36 | 37 | void SetFromROS2(const {{data.Group}}__msg__{{data.NameCap}}& in_ros_data) 38 | { 39 | {{data.SetFromROS2}} 40 | } 41 | 42 | void SetROS2({{data.Group}}__msg__{{data.NameCap}}& out_ros_data) const 43 | { 44 | {{data.SetROS2}} 45 | } 46 | }; 47 | 48 | UCLASS() 49 | class {{data.ModuleAPI}} UROS2{{data.UEName}}Msg : public UROS2GenericMsg 50 | { 51 | GENERATED_BODY() 52 | 53 | public: 54 | virtual void Init() override; 55 | virtual void Fini() override; 56 | 57 | virtual const rosidl_message_type_support_t* GetTypeSupport() const override; 58 | 59 | UFUNCTION(BlueprintCallable) 60 | void SetMsg(const FROS{{data.UEName}}& Input); 61 | 62 | UFUNCTION(BlueprintCallable) 63 | void GetMsg(FROS{{data.UEName}}& Output) const; 64 | 65 | virtual void* Get() override; 66 | 67 | {{data.ConstantsGetter}} 68 | 69 | 70 | private: 71 | virtual FString MsgToString() const override; 72 | 73 | {{data.Group}}__msg__{{data.NameCap}} {{data.Name}}_msg; 74 | }; 75 | -------------------------------------------------------------------------------- /CodeGen/templates/Srv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | // This code has been autogenerated from {{data.Filename}} - do not modify 3 | 4 | #include "Srvs/ROS2{{data.UEName}}.h" 5 | 6 | 7 | {{data.ReqConstantsDef}} 8 | {{data.ResConstantsDef}} 9 | 10 | const rosidl_service_type_support_t* UROS2{{data.UEName}}Srv::GetTypeSupport() const 11 | { 12 | return ROSIDL_GET_SRV_TYPE_SUPPORT({{data.Group}}, srv, {{data.NameCap}}); 13 | } 14 | 15 | void UROS2{{data.UEName}}Srv::Init() 16 | { 17 | {{data.Group}}__srv__{{data.NameCap}}_Request__init(&{{data.NameCap}}_req); 18 | {{data.Group}}__srv__{{data.NameCap}}_Response__init(&{{data.NameCap}}_res); 19 | } 20 | 21 | void UROS2{{data.UEName}}Srv::Fini() 22 | { 23 | {{data.Group}}__srv__{{data.NameCap}}_Request__fini(&{{data.NameCap}}_req); 24 | {{data.Group}}__srv__{{data.NameCap}}_Response__fini(&{{data.NameCap}}_res); 25 | } 26 | 27 | void UROS2{{data.UEName}}Srv::SetRequest(const FROS{{data.UEName}}Req& Request) 28 | { 29 | Request.SetROS2({{data.NameCap}}_req); 30 | } 31 | 32 | void UROS2{{data.UEName}}Srv::GetRequest(FROS{{data.UEName}}Req& Request) const 33 | { 34 | Request.SetFromROS2({{data.NameCap}}_req); 35 | } 36 | 37 | void UROS2{{data.UEName}}Srv::SetResponse(const FROS{{data.UEName}}Res& Response) 38 | { 39 | Response.SetROS2({{data.NameCap}}_res); 40 | } 41 | 42 | void UROS2{{data.UEName}}Srv::GetResponse(FROS{{data.UEName}}Res& Response) const 43 | { 44 | Response.SetFromROS2({{data.NameCap}}_res); 45 | } 46 | 47 | void* UROS2{{data.UEName}}Srv::GetRequest() 48 | { 49 | return &{{data.NameCap}}_req; 50 | } 51 | 52 | void* UROS2{{data.UEName}}Srv::GetResponse() 53 | { 54 | return &{{data.NameCap}}_res; 55 | } 56 | 57 | FString UROS2{{data.UEName}}Srv::SrvRequestToString() const 58 | { 59 | /* TODO: Fill here */ 60 | checkNoEntry(); 61 | return FString(); 62 | } 63 | 64 | FString UROS2{{data.UEName}}Srv::SrvResponseToString() const 65 | { 66 | /* TODO: Fill here */ 67 | checkNoEntry(); 68 | return FString(); 69 | } 70 | -------------------------------------------------------------------------------- /custom_config.yaml: -------------------------------------------------------------------------------- 1 | args: #path is relative from home 2 | ue_path: UnrealEngine 3 | ue_proj_path: rclUE-Examples 4 | ue_plugin_name: rclUE 5 | ue_plugin_folder_name: rclUE 6 | ue_target_3rd_name: ros # sub dir name under ThirdParty 7 | 8 | 9 | # additional repos file path. path is relative from home 10 | repos: UE_tools/ros2_custom_pkgs.repos 11 | 12 | ignore_deprecated_msg: False # needs to be false to generate deprecated std_msgs 13 | 14 | ## build and code gen target msgs 15 | ## pkgs and prefix for class 16 | target_pkgs: 17 | std_msgs: 'StdMsg' 18 | nav2_msgs: '' 19 | 20 | ## if dependency is "targe", it used same value as target 21 | ## elif dependency is "default", it uses default value defined in script 22 | # dependency: "target" 23 | # dependency: "default" 24 | dependency: 25 | action_msgs: '' 26 | builtin_interfaces: '' 27 | geometry_msgs: '' 28 | action_msgs: '' 29 | nav_msgs: '' 30 | geographic_msgs: '' 31 | unique_identifier_msgs: '' 32 | 33 | ## additional dependency 34 | ## this dict is appended on dependency. 35 | ## Used to add dependency without overwriting original dependency. 36 | # dependency_append: '' 37 | # your_msg: '' 38 | 39 | name_mapping: 40 | Covariance: 'Cov' 41 | Accel: 'Acc' 42 | Vector: 'Vec' 43 | Quaternion: 'Quat' 44 | Trajectory: 'Traj' 45 | Transform: 'TF' 46 | Compressed: 'Comp' 47 | Error: 'Err' 48 | Diagnostics: 'Diag' 49 | MultiArray: 'MA' 50 | Dimension: 'Dim' 51 | With: '' 52 | Destination: 'Dest' 53 | Reservation: 'Reserv' 54 | Exception: 'Except' 55 | Config: 'Conf' 56 | InteractiveMarker: 'IM' 57 | Control: 'Ctrl' 58 | Request: 'Req' 59 | Response: 'Res' 60 | Message: 'Msg' 61 | String: 'Str' 62 | Image: 'Img' 63 | Odometry: 'Odom' 64 | Hardware: 'HW' 65 | 66 | ## additional name_mapping 67 | ## this dict is appended on name_mapping. 68 | ## Used to add name_mapping without overwriting original dependency. 69 | # name_mapping_append: 70 | # your_mapping_key: 'your_mapping_value' 71 | 72 | 73 | # msgs which is not copied to project. 74 | # black_list_msgs: 75 | # - 'ROS2WStr' # can't handle wstring in UE. -------------------------------------------------------------------------------- /BuildROS2/build_ros2_base.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ROS2_WS=$1 4 | ROS_DISTRO=$2 5 | 6 | cleanup() { 7 | sudo rm -r -f $ROS2_WS/build $ROS2_WS/install $ROS2_WS/log 8 | sudo rm -r -f $ROS2_WS/build_renamed $ROS2_WS/install_renamed 9 | } 10 | 11 | cleanup $ROS2_WS 12 | 13 | export LANG=en_US.UTF-8 14 | 15 | # export ROS_DOMAIN_ID=10 16 | # pay attention it can be 'rmw_fastrtps_dynamic_cpp' too 17 | export RMW_IMPLEMENTATION=rmw_fastrtps_cpp 18 | 19 | # Building ros by exact UE clang toolchain 20 | # not work. temporary commented out 21 | # UE4 22 | # export MY_SYS_ROOT_PATH=$UE_PATH"/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v19_clang-11.0.1-centos7/x86_64-unknown-linux-gnu" 23 | # UE5 24 | # export MY_SYS_ROOT_PATH=$UE_PATH"/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/v20_clang-13.0.1-centos7/x86_64-unknown-linux-gnu" 25 | # export CC=$MY_SYS_ROOT_PATH"/bin/clang" 26 | # export CXX=$MY_SYS_ROOT_PATH"/bin/clang++" 27 | 28 | # use locally installed clang 29 | CLANG_VER=13 30 | if [ $ROS_DISTRO == "jazzy" ]; then 31 | CLANG_VER=18 32 | fi 33 | 34 | export CC="/usr/bin/clang" 35 | export CXX="/usr/bin/clang++" 36 | 37 | # -latomic issue - see more here https://github.com/ros2/ros2/issues/418 38 | export MY_LINKER_FLAGS="-latomic "\ 39 | "-Wl,-rpath=\${ORIGIN} "\ 40 | "-Wl,-rpath-link=/usr/lib/x86_64-linux-gnu "\ 41 | "-Wl,-rpath-link=/usr/lib " 42 | 43 | # options when you build with UE's build tool chaain 44 | # "-Wl,-rpath-link="$MY_SYS_ROOT_PATH" "\ 45 | # "-Wl,-rpath-link="$MY_SYS_ROOT_PATH"/usr/lib "\ 46 | # "-Wl,-rpath-link="$MY_SYS_ROOT_PATH"/usr/lib64 "\ 47 | # "-Wl,-rpath-link="$MY_SYS_ROOT_PATH"/lib "\ 48 | # "-Wl,-rpath-link="$MY_SYS_ROOT_PATH"/lib64 "\ 49 | 50 | 51 | pushd $ROS2_WS 52 | colcon build \ 53 | --cmake-clean-cache \ 54 | --cmake-force-configure \ 55 | --continue-on-error \ 56 | --cmake-args \ 57 | "-DCMAKE_SHARED_LINKER_FLAGS='$MY_LINKER_FLAGS'"\ 58 | "-DCMAKE_EXE_LINKER_FLAGS='$MY_LINKER_FLAGS'"\ 59 | "-DCMAKE_CXX_FLAGS='-stdlib=libstdc++'"\ 60 | "-DCMAKE_CXX_FLAGS='-fpermissive'"\ 61 | -DBUILD_TESTING=OFF \ 62 | --no-warn-unused-cli \ 63 | --packages-up-to rclc rcl_action 64 | popd -------------------------------------------------------------------------------- /config_ue_msg_update_example.yaml: -------------------------------------------------------------------------------- 1 | # example of update config file for ue msg generation 2 | # no need to update base lib but just update ue_msg 3 | 4 | args: #path is relative from home 5 | ue_path: UnrealEngine 6 | ue_proj_path: turtlebot3-UE 7 | ue_plugin_name: rclUE 8 | ue_plugin_folder_name: rclUE 9 | ue_target_3rd_name: ros # sub dir name under ThirdParty 10 | 11 | 12 | # additional repos file path. path is relative from home 13 | repos: UE_tools/ros2_additional_pkgs.repos 14 | 15 | # target is ue_msgs only 16 | target_pkgs: 17 | ue_msgs: '' 18 | simulation_interfaces: 'SI' 19 | 20 | # since ue depends on other msgs, those are listed here. 21 | # not add in target_pkgs since those msg source are not cloned as a default 22 | dependency: 23 | actionlib_msgs: '' 24 | builtin_interfaces: '' 25 | unique_identifier_msgs: '' 26 | diagnostic_msgs: '' 27 | rosgraph_msgs: '' 28 | geometry_msgs: '' 29 | nav_msgs: '' 30 | sensor_msgs: '' 31 | shape_msgs: '' 32 | std_msgs: 'Std' 33 | std_srvs: 'StdSrv' 34 | stereo_msgs: '' 35 | trajectory_msgs: '' 36 | visualization_msgs: '' 37 | tf2_msgs: '' 38 | pcl_msgs: '' 39 | #ackermann_msgs: '' 40 | example_interfaces: '' 41 | nav_msgs: '' 42 | ## additional dependency 43 | ## this dict is appended on dependency. 44 | ## Used to add dependency without overwriting original dependency. 45 | # dependency_append: '' 46 | # your_msg: '' 47 | 48 | name_mapping: 49 | Covariance: 'Cov' 50 | Accel: 'Acc' 51 | Vector: 'Vec' 52 | Quaternion: 'Quat' 53 | Trajectory: 'Traj' 54 | Transform: 'TF' 55 | Compressed: 'Comp' 56 | Error: 'Err' 57 | Diagnostics: 'Diag' 58 | MultiArray: 'MA' 59 | Dimension: 'Dim' 60 | With: '' 61 | Destination: 'Dest' 62 | Reservation: 'Reserv' 63 | Exception: 'Except' 64 | Config: 'Conf' 65 | InteractiveMarker: 'IM' 66 | Control: 'Ctrl' 67 | Request: 'Req' 68 | Response: 'Res' 69 | Message: 'Msg' 70 | String: 'Str' 71 | Image: 'Img' 72 | Odometry: 'Odom' 73 | Hardware: 'HW' 74 | 75 | ## additional name_mapping 76 | ## this dict is appended on name_mapping. 77 | ## Used to add name_mapping without overwriting original dependency. 78 | # name_mapping_append: 79 | # your_mapping_key: 'your_mapping_value' 80 | 81 | 82 | # msgs which is not copied to project. 83 | # black_list_msgs: 84 | # - 'ROS2WStr' # can't handle wstring in UE. -------------------------------------------------------------------------------- /default_config.yaml.foxy: -------------------------------------------------------------------------------- 1 | args: #path is relative from home 2 | ue_path: UnrealEngine 3 | ue_proj_path: turtlebot3-UE 4 | ue_plugin_name: rclUE 5 | ue_plugin_folder_name: rclUE 6 | ue_target_3rd_name: ros # sub dir name under ThirdParty 7 | 8 | 9 | # additional repos file path. path is relative from home 10 | repos: UE_tools/ros2_additional_pkgs.repos 11 | 12 | ## build and code gen target msgs 13 | ## pkgs and prefix for class 14 | target_pkgs: 15 | action_msgs: '' 16 | actionlib_msgs: '' 17 | builtin_interfaces: '' 18 | unique_identifier_msgs: '' 19 | diagnostic_msgs: '' 20 | rosgraph_msgs: '' 21 | geometry_msgs: '' 22 | nav_msgs: '' 23 | sensor_msgs: '' 24 | shape_msgs: '' 25 | std_msgs: 'Std' 26 | std_srvs: 'StdSrv' 27 | stereo_msgs: '' 28 | trajectory_msgs: '' 29 | visualization_msgs: '' 30 | tf2_msgs: '' 31 | pcl_msgs: '' 32 | #ackermann_msgs: '' 33 | example_interfaces: '' 34 | nav_msgs: '' 35 | ue_msgs: '' 36 | simulation_interfaces: 'SI' 37 | 38 | ## if dependency is "targe", it used same value as target 39 | ## elif dependency is "default", it uses default value defined in script 40 | dependency: "target" 41 | # dependency: "default" 42 | # dependency: 43 | # action_msgs: '' 44 | 45 | ## additional dependency 46 | ## this dict is appended on dependency. 47 | ## Used to add dependency without overwriting original dependency. 48 | # dependency_append: '' 49 | # your_msg: '' 50 | 51 | name_mapping: 52 | Covariance: 'Cov' 53 | Accel: 'Acc' 54 | Vector: 'Vec' 55 | Quaternion: 'Quat' 56 | Trajectory: 'Traj' 57 | Transform: 'TF' 58 | Compressed: 'Comp' 59 | Error: 'Err' 60 | Diagnostics: 'Diag' 61 | MultiArray: 'MA' 62 | Dimension: 'Dim' 63 | With: '' 64 | Destination: 'Dest' 65 | Reservation: 'Reserv' 66 | Exception: 'Except' 67 | Config: 'Conf' 68 | InteractiveMarker: 'IM' 69 | Control: 'Ctrl' 70 | Request: 'Req' 71 | Response: 'Res' 72 | Message: 'Msg' 73 | String: 'Str' 74 | Image: 'Img' 75 | Odometry: 'Odom' 76 | Hardware: 'HW' 77 | 78 | ## additional name_mapping 79 | ## this dict is appended on name_mapping. 80 | ## Used to add name_mapping without overwriting original dependency. 81 | # name_mapping_append: 82 | # your_mapping_key: 'your_mapping_value' 83 | 84 | 85 | # msgs which is not copied to project. 86 | # black_list_msgs: 87 | # - 'ROS2WStr' # can't handle wstring in UE. -------------------------------------------------------------------------------- /default_config.yaml.humble: -------------------------------------------------------------------------------- 1 | args: #path is relative from home 2 | ue_path: UnrealEngine 3 | ue_proj_path: turtlebot3-UE 4 | ue_plugin_name: rclUE 5 | ue_plugin_folder_name: rclUE 6 | ue_target_3rd_name: ros # sub dir name under ThirdParty 7 | 8 | 9 | # additional repos file path. path is relative from home 10 | repos: UE_tools/ros2_additional_pkgs.repos 11 | 12 | ## build and code gen target msgs 13 | ## pkgs and prefix for class 14 | target_pkgs: 15 | action_msgs: '' 16 | actionlib_msgs: '' 17 | builtin_interfaces: '' 18 | unique_identifier_msgs: '' 19 | diagnostic_msgs: '' 20 | rosgraph_msgs: '' 21 | geometry_msgs: '' 22 | nav_msgs: '' 23 | sensor_msgs: '' 24 | shape_msgs: '' 25 | std_msgs: 'Std' 26 | std_srvs: 'StdSrv' 27 | stereo_msgs: '' 28 | trajectory_msgs: '' 29 | visualization_msgs: '' 30 | tf2_msgs: '' 31 | pcl_msgs: '' 32 | #ackermann_msgs: '' 33 | example_interfaces: '' 34 | nav_msgs: '' 35 | ue_msgs: '' 36 | simulation_interfaces: 'SI' 37 | 38 | ## if dependency is "targe", it used same value as target 39 | ## elif dependency is "default", it uses default value defined in script 40 | dependency: "target" 41 | # dependency: "default" 42 | # dependency: 43 | # action_msgs: '' 44 | 45 | ## additional dependency 46 | ## this dict is appended on dependency. 47 | ## Used to add dependency without overwriting original dependency. 48 | # dependency_append: '' 49 | # your_msg: '' 50 | 51 | name_mapping: 52 | Covariance: 'Cov' 53 | Accel: 'Acc' 54 | Vector: 'Vec' 55 | Quaternion: 'Quat' 56 | Trajectory: 'Traj' 57 | Transform: 'TF' 58 | Compressed: 'Comp' 59 | Error: 'Err' 60 | Diagnostics: 'Diag' 61 | MultiArray: 'MA' 62 | Dimension: 'Dim' 63 | With: '' 64 | Destination: 'Dest' 65 | Reservation: 'Reserv' 66 | Exception: 'Except' 67 | Config: 'Conf' 68 | InteractiveMarker: 'IM' 69 | Control: 'Ctrl' 70 | Request: 'Req' 71 | Response: 'Res' 72 | Message: 'Msg' 73 | String: 'Str' 74 | Image: 'Img' 75 | Odometry: 'Odom' 76 | Hardware: 'HW' 77 | 78 | ## additional name_mapping 79 | ## this dict is appended on name_mapping. 80 | ## Used to add name_mapping without overwriting original dependency. 81 | # name_mapping_append: 82 | # your_mapping_key: 'your_mapping_value' 83 | 84 | 85 | # msgs which is not copied to project. 86 | # black_list_msgs: 87 | # - 'ROS2WStr' # can't handle wstring in UE. -------------------------------------------------------------------------------- /default_config.yaml.jazzy: -------------------------------------------------------------------------------- 1 | args: #path is relative from home 2 | ue_path: UnrealEngine 3 | ue_proj_path: turtlebot3-UE 4 | ue_plugin_name: rclUE 5 | ue_plugin_folder_name: rclUE 6 | ue_target_3rd_name: ros # sub dir name under ThirdParty 7 | 8 | 9 | # additional repos file path. path is relative from home 10 | repos: UE_tools/ros2_additional_pkgs.repos 11 | 12 | ## build and code gen target msgs 13 | ## pkgs and prefix for class 14 | target_pkgs: 15 | action_msgs: '' 16 | actionlib_msgs: '' 17 | builtin_interfaces: '' 18 | service_msgs: '' 19 | type_description_interfaces: 'TDI' 20 | unique_identifier_msgs: '' 21 | diagnostic_msgs: '' 22 | rosgraph_msgs: '' 23 | geometry_msgs: '' 24 | nav_msgs: '' 25 | sensor_msgs: '' 26 | shape_msgs: '' 27 | std_msgs: 'Std' 28 | std_srvs: 'StdSrv' 29 | stereo_msgs: '' 30 | trajectory_msgs: '' 31 | visualization_msgs: '' 32 | tf2_msgs: '' 33 | pcl_msgs: '' 34 | #ackermann_msgs: '' 35 | example_interfaces: '' 36 | nav_msgs: '' 37 | ue_msgs: '' 38 | simulation_interfaces: 'SI' 39 | 40 | ## if dependency is "targe", it used same value as target 41 | ## elif dependency is "default", it uses default value defined in script 42 | dependency: "target" 43 | # dependency: "default" 44 | # dependency: 45 | # action_msgs: '' 46 | 47 | ## additional dependency 48 | ## this dict is appended on dependency. 49 | ## Used to add dependency without overwriting original dependency. 50 | # dependency_append: '' 51 | # your_msg: '' 52 | 53 | name_mapping: 54 | Covariance: 'Cov' 55 | Accel: 'Acc' 56 | Vector: 'Vec' 57 | Quaternion: 'Quat' 58 | Trajectory: 'Traj' 59 | Transform: 'TF' 60 | Compressed: 'Comp' 61 | Error: 'Err' 62 | Diagnostics: 'Diag' 63 | MultiArray: 'MA' 64 | Dimension: 'Dim' 65 | With: '' 66 | Destination: 'Dest' 67 | Reservation: 'Reserv' 68 | Exception: 'Except' 69 | Config: 'Conf' 70 | InteractiveMarker: 'IM' 71 | Control: 'Ctrl' 72 | Request: 'Req' 73 | Response: 'Res' 74 | Message: 'Msg' 75 | String: 'Str' 76 | Image: 'Img' 77 | Odometry: 'Odom' 78 | Hardware: 'HW' 79 | 80 | ## additional name_mapping 81 | ## this dict is appended on name_mapping. 82 | ## Used to add name_mapping without overwriting original dependency. 83 | # name_mapping_append: 84 | # your_mapping_key: 'your_mapping_value' 85 | 86 | 87 | # msgs which is not copied to project. 88 | # black_list_msgs: 89 | # - 'ROS2WStr' # can't handle wstring in UE. -------------------------------------------------------------------------------- /BuildROS2/build_and_install_ros2_pkgs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | from build_and_install_ros2 import build_ros2, install_ros2 6 | 7 | DEFAULT_PKGS = [ 8 | 'action_msgs', 9 | 'actionlib_msgs', 10 | 'builtin_interfaces', 11 | 'service_msgs', 12 | 'type_description_interfaces', 13 | 'unique_identifier_msgs', 14 | 'diagnostic_msgs', 15 | 'rosgraph_msgs', 16 | 'geometry_msgs', 17 | 'nav_msgs', 18 | 'sensor_msgs', 19 | 'shape_msgs', 20 | 'std_msgs', 21 | 'std_srvs', 22 | 'stereo_msgs', 23 | 'trajectory_msgs', 24 | 'visualization_msgs', 25 | 'tf2_msgs', 26 | 'pcl_msgs', 27 | # 'ackermann_msgs', 28 | 'example_interfaces', 29 | 'ue_msgs' 30 | ] 31 | 32 | if __name__ == '__main__': 33 | parser = argparse.ArgumentParser( 34 | description="Build ros2 from source with necessasary patches to be used with UnrealEngine. And copy lib and header files under Unreal Project folder." 35 | ) 36 | parser.add_argument( 37 | "--ue_path", 38 | help="Path to UE", 39 | required=True 40 | ) 41 | parser.add_argument( 42 | "--ue_proj_path", 43 | help="Path to target UE project", 44 | required=True 45 | ) 46 | parser.add_argument( 47 | "--ue_plugin_name", 48 | help="UE plugin module name, eg: rclUE", 49 | default='rclUE' 50 | ) 51 | parser.add_argument( 52 | "--ue_plugin_folder_name", 53 | help="UE plugin folder name, eg: rclUE", 54 | default='' 55 | ) 56 | parser.add_argument( 57 | "--ue_target_3rd_name", 58 | help="Target 3rd name under ThirdParty, eg: ros", 59 | default='ros' 60 | ) 61 | parser.add_argument( 62 | "--ros_pkgs", 63 | help="ROS package name, eg: geometry_msgs", 64 | nargs='*', 65 | default=DEFAULT_PKGS 66 | ) 67 | args = parser.parse_args() 68 | 69 | ue_plugin_folder_name = args.ue_plugin_folder_name 70 | if ue_plugin_folder_name == '': 71 | ue_plugin_folder_name = args.ue_plugin_name 72 | 73 | build_ros2( 74 | buildType = 'pkgs', 75 | allowed_spaces = args.ros_pkgs, 76 | pkgs = args.ros_pkgs, 77 | ros_ws = os.path.join(os.getcwd(), '../ros2_ws') 78 | ) 79 | install_ros2( 80 | projectPath = args.ue_proj_path, 81 | pluginName = args.ue_plugin_name, 82 | pluginFolderName = ue_plugin_folder_name, 83 | targetThirdpartyFolderName = args.ue_target_3rd_name, 84 | buildType = 'pkgs', 85 | ros_ws = os.path.join(os.getcwd(), '../ros2_ws'), 86 | allowed_spaces = args.ros_pkgs, 87 | not_allowed_spaces = [], 88 | ) -------------------------------------------------------------------------------- /CodeGen/templates/Srv.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | // This code has been autogenerated from {{data.Filename}} - do not modify 3 | 4 | #pragma once 5 | 6 | // UE 7 | #include "CoreMinimal.h" 8 | 9 | // ROS 10 | #include "{{data.Group}}/srv/{{data.Name}}.h" 11 | 12 | // rclUE 13 | #include "Srvs/ROS2GenericSrv.h" 14 | #include "rclcUtilities.h" 15 | 16 | // Generated Msg/Srv/Action(can be empty) 17 | {{data.ReqHeaders}} 18 | {{data.ResHeaders}} 19 | 20 | // Generated 21 | #include "ROS2{{data.UEName}}.generated.h" 22 | 23 | // potential problem: if this struct is defined multiple times! 24 | USTRUCT(Blueprintable) 25 | struct {{data.ModuleAPI}} FROS{{data.UEName}}Req 26 | { 27 | GENERATED_BODY() 28 | 29 | public: 30 | {{data.ReqConstantsDec}} 31 | 32 | {{data.ReqTypes}} 33 | 34 | FROS{{data.UEName}}Req() 35 | { 36 | {{data.ReqConstructor}} 37 | } 38 | 39 | void SetFromROS2(const {{data.Group}}__srv__{{data.NameCap}}_Request& in_ros_data) 40 | { 41 | {{data.ReqSetFromROS2}} 42 | } 43 | 44 | void SetROS2({{data.Group}}__srv__{{data.NameCap}}_Request& out_ros_data) const 45 | { 46 | {{data.ReqSetROS2}} 47 | } 48 | }; 49 | 50 | USTRUCT(Blueprintable) 51 | struct {{data.ModuleAPI}} FROS{{data.UEName}}Res 52 | { 53 | GENERATED_BODY() 54 | 55 | public: 56 | {{data.ResConstantsDec}} 57 | 58 | {{data.ResTypes}} 59 | 60 | FROS{{data.UEName}}Res() 61 | { 62 | {{data.ResConstructor}} 63 | } 64 | 65 | void SetFromROS2(const {{data.Group}}__srv__{{data.NameCap}}_Response& in_ros_data) 66 | { 67 | {{data.ResSetFromROS2}} 68 | } 69 | 70 | void SetROS2({{data.Group}}__srv__{{data.NameCap}}_Response& out_ros_data) const 71 | { 72 | {{data.ResSetROS2}} 73 | } 74 | }; 75 | 76 | UCLASS() 77 | class {{data.ModuleAPI}} UROS2{{data.UEName}}Srv : public UROS2GenericSrv 78 | { 79 | GENERATED_BODY() 80 | 81 | public: 82 | UFUNCTION(BlueprintCallable) 83 | virtual void Init() override; 84 | 85 | UFUNCTION(BlueprintCallable) 86 | virtual void Fini() override; 87 | 88 | virtual const rosidl_service_type_support_t* GetTypeSupport() const override; 89 | 90 | // used by client 91 | UFUNCTION(BlueprintCallable) 92 | void SetRequest(const FROS{{data.UEName}}Req& Request); 93 | 94 | // used by service 95 | UFUNCTION(BlueprintCallable) 96 | void GetRequest(FROS{{data.UEName}}Req& Request) const; 97 | 98 | // used by service 99 | UFUNCTION(BlueprintCallable) 100 | void SetResponse(const FROS{{data.UEName}}Res& Response); 101 | 102 | // used by client 103 | UFUNCTION(BlueprintCallable) 104 | void GetResponse(FROS{{data.UEName}}Res& Response) const; 105 | 106 | virtual void* GetRequest() override; 107 | virtual void* GetResponse() override; 108 | 109 | {{data.ReqConstantsGetter}} 110 | {{data.ResConstantsGetter}} 111 | 112 | private: 113 | virtual FString SrvRequestToString() const override; 114 | virtual FString SrvResponseToString() const override; 115 | 116 | {{data.Group}}__srv__{{data.NameCap}}_Request {{data.NameCap}}_req; 117 | {{data.Group}}__srv__{{data.NameCap}}_Response {{data.NameCap}}_res; 118 | }; 119 | -------------------------------------------------------------------------------- /CodeGen/post_generate.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | import argparse 3 | import os 4 | import shutil 5 | import glob 6 | 7 | # temporary black list msgs which can't be parsed properly 8 | DEFAULT_BLACK_LIST = [ 9 | # "ROS2WStr", # can't handle wstring in UE. 10 | 11 | # # array parser issue 12 | # "ROS2CancelGoalSrv", 13 | # "ROS2MeshMsg", 14 | # "ROS2SolidPrimitiveMsg" 15 | 16 | # "ROS2TFMessageMsg", # memcpy/free issues. fixed version in rclUE but can't autogenerate. 17 | ] 18 | 19 | def check_blacklist(file_name, black_list=DEFAULT_BLACK_LIST): 20 | for b in black_list: 21 | if b in file_name: 22 | return True 23 | return False 24 | 25 | def copy_files(target_path, type_name, extension, black_list, remove=True): 26 | current_dir = os.getcwd() 27 | target_path = os.path.join(target_path, f'{type_name}s') 28 | if remove and os.path.exists(target_path): 29 | files = glob.glob(f'{target_path}/*{extension}') 30 | for f in files: 31 | print(f) 32 | if f != f'{target_path}/ROS2Generic{type_name}{extension}': 33 | os.remove(f) 34 | 35 | os.makedirs(target_path, exist_ok=True) 36 | print('Copy generated files to ' + target_path) 37 | for file_name in glob.glob(f'{type_name}s/*{extension}'): 38 | if check_blacklist(file_name, black_list): 39 | print(' *' + file_name + ' is in BLACK_LIST and not copied.') 40 | continue 41 | shutil.copy(os.path.join(current_dir, file_name), target_path) 42 | print(' Copied ' + file_name) 43 | 44 | def copy_ros_to_ue(ue_project_path, ue_plugin_name, ue_plugin_folder_name, black_list=DEFAULT_BLACK_LIST, remove=True): 45 | 46 | ue_target_src_path = os.path.join(ue_project_path, 'Plugins', ue_plugin_folder_name, 'Source') 47 | ue_public_path = os.path.join(ue_target_src_path, ue_plugin_name, 'Public') 48 | ue_private_path = os.path.join(ue_target_src_path, ue_plugin_name, 'Private') 49 | 50 | # Copy UE wrapper of ros src 51 | for type_name in ['Action','Srv','Msg']: 52 | copy_files(ue_public_path, type_name, '.h', black_list, remove) 53 | copy_files(ue_private_path, type_name,'.cpp', black_list, remove) 54 | 55 | return 56 | 57 | if __name__ == "__main__": 58 | parser = argparse.ArgumentParser( 59 | description="Copy ros package's include + lib from installation folder to target UE plugin's ThirdParty folder" 60 | ) 61 | parser.add_argument( 62 | "--ue_proj_path", 63 | help="Path to target UE project", 64 | required=True 65 | ) 66 | parser.add_argument( 67 | "--ue_plugin_name", 68 | help="UE plugin module name, eg: rclUE", 69 | default='rclUE' 70 | ) 71 | parser.add_argument( 72 | "--ue_plugin_folder_name", 73 | help="UE plugin folder name, eg: rclUE", 74 | default='rclUE' 75 | ) 76 | 77 | args = parser.parse_args() 78 | 79 | copy_ros_to_ue( 80 | args.ue_proj_path, 81 | args.ue_plugin_name, 82 | args.ue_plugin_folder_name, 83 | DEFAULT_BLACK_LIST 84 | ) 85 | 86 | -------------------------------------------------------------------------------- /BuildROS2/build_and_install_ros2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os, time 4 | from libs_utils import * 5 | 6 | def build_ros2( 7 | buildType, 8 | allowed_spaces = [], 9 | pkgs = [], 10 | ros_ws = os.path.join(os.getcwd(), '../ros2_ws'), 11 | remove = True, 12 | rosdistro = 'humble' 13 | ): 14 | 15 | buildRosScript = os.path.join(os.getcwd(), 'setup_ros2_' + buildType + '.sh') 16 | 17 | allowed_spaces.extend(pkgs) 18 | 19 | if remove: 20 | if os.path.exists(ros_ws): 21 | print('Cleanup workspace') 22 | shutil.rmtree(ros_ws) 23 | 24 | 25 | 26 | print('Building ros ' + buildType + '...') 27 | os.system('chmod +x ' + buildRosScript) 28 | os.system('bash ' + buildRosScript + ' ' + ros_ws + ' ' + rosdistro + ' ' + ' "' + ' '.join(pkgs) + '"') 29 | 30 | def install_ros2( 31 | projectPath, 32 | pluginName, 33 | pluginFolderName, #pluginFolderName is not always same as pluginName 34 | targetThirdpartyFolderName, 35 | buildType, 36 | ros_ws = os.path.join(os.getcwd(), '../ros2_ws'), 37 | allowed_spaces = [], 38 | not_allowed_spaces = [], 39 | remove = True, 40 | rosdistro = 'humble' 41 | ): 42 | # Assume pluginFolderName is same as PluginName if it is empty. 43 | if pluginFolderName == '': 44 | pluginFolderName = pluginName 45 | 46 | # ros paths 47 | rosInstall = os.path.join(ros_ws, 'install') 48 | 49 | # UE paths 50 | pluginPath = os.path.join(projectPath, 'Plugins', pluginFolderName) 51 | pluginPathBinaries = os.path.join(pluginPath, 'Binaries') 52 | pluginPathRos = os.path.join(pluginPath, 'ThirdParty', targetThirdpartyFolderName) 53 | pluginPathRosInclude = os.path.join(pluginPathRos, 'include') 54 | pluginPathRosLib = os.path.join(pluginPathRos, 'lib') 55 | pluginPathBuildCS = os.path.join( pluginPath, 'Source', pluginName, pluginName + '.Build.cs') 56 | projectPathBinaries = os.path.join(projectPath, 'Binaries' ) 57 | 58 | if remove: 59 | if os.path.exists(pluginPathRosInclude): 60 | shutil.rmtree(pluginPathRosInclude) 61 | if os.path.exists(pluginPathRosLib): 62 | shutil.rmtree(pluginPathRosLib) 63 | 64 | os.makedirs(pluginPathRosInclude, exist_ok=True) 65 | os.makedirs(pluginPathRosLib, exist_ok=True) 66 | 67 | print('Grabbing includes...') 68 | GrabIncludes(rosInstall, pluginPathRosInclude, allowed_spaces) 69 | CleanIncludes(pluginPathRosInclude, not_allowed_spaces) 70 | 71 | if buildType == 'base': 72 | print('Applying includes patch...') 73 | os.system('cd ' + pluginPath + '; git apply ' + 74 | os.path.join(os.getcwd(), 'patches', rosdistro, 'rcutils.patch')) 75 | 76 | print('Grabbing libs...') 77 | GrabLibs(rosInstall, pluginPathRosLib, allowed_spaces) 78 | CleanLibs(pluginPathRosLib, not_allowed_spaces) 79 | 80 | RenameLibsWithVersion(pluginPathRosLib, projectPath) 81 | SetRPATH(pluginPathRosLib) 82 | InvalidateBinaries(projectPathBinaries, pluginPathBinaries, pluginPathBuildCS) 83 | RemovePyDependency(pluginPathRosLib, projectPath) 84 | # You also can try this: 85 | # CreateInfoForAll('objdump -x', pluginPathRosLib, infoRosOutput) # see also 'ldd' 86 | # CheckLibs(pluginPathRosLib) 87 | 88 | if __name__ == '__main__': 89 | print("Please use build_ros function from other wrapper.") 90 | 91 | -------------------------------------------------------------------------------- /BuildROS2/build_and_install_ros2_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | from build_and_install_ros2 import build_ros2, install_ros2 6 | 7 | DEFAULT_ALLOWED_SPACES = [ 8 | 'fastcdr', 9 | 'fastrtps', 10 | 'rcl', 11 | 'rcutils', 12 | 'rcpputils', 13 | 'rmw', 14 | 'rosidl', 15 | 'tracetools', 16 | 'ament' 17 | ] 18 | DEFAULT_NOT_ALLOWED_SPACES = [ 19 | '.so.', 20 | 'python', 21 | 'rclcpp', 22 | 'rclpy', 23 | 'rcl_logging_log4cxx', 24 | 'rcl_logging_noop', 25 | 'rclc_examples', 26 | 'rclc_parameter', 27 | 'connext', 28 | 'cyclonedds', 29 | 'action_tutorials', 30 | 'turtlesim', 31 | 'rmw_fastrtps_dynamic_cpp', 32 | 'rosidl_cmake', 33 | 'rosidl_default', 34 | 'rosidl_generator_cpp', 35 | 'rosidl_generator_py', 36 | 'rosidl_generator_dds_idl', 37 | # 'rosidl_generator_py', 38 | 'rosidl_runtime_cpp', 39 | 'rosidl_runtime_py', 40 | #msgs 41 | 'action_msgs', 42 | 'unique_identifier_msgs' , 43 | 'builtin_interfaces', 44 | 'example_interfaces', 45 | 'actionlib_msgs', 46 | 'composition_interfaces', 47 | 'diagnostic_msgs', 48 | 'lifecycle_msgs', 49 | 'logging_demo', 50 | 'map_msgs', 51 | 'move_base_msgs', 52 | 'pendulum_msgs', 53 | 'shape_msgs', 54 | 'statistics_msgs', 55 | 'std_srvs', 56 | 'stereo_msgs', 57 | 'test_msgs', 58 | 'trajectory_msgs', 59 | 'visualization_msgs', 60 | 'geometry_msgs', 61 | 'nav_msgs', 62 | 'sensor_msgs', 63 | 'std_msgs', 64 | 'tf2_msgs', 65 | 'sensor_msgs_py', 66 | 'tf2_sensor_msgs', 67 | 'rosgraph_msgs', 68 | 'tracetools_', 69 | 'Codec_', 70 | 'Plugin_', 71 | 'RenderSystem_', 72 | '_test_type_support' 73 | ] 74 | 75 | if __name__ == '__main__': 76 | parser = argparse.ArgumentParser( 77 | description="Build ros2 from source with necessasary patches to be used with UnrealEngine. And copy lib and header files under Unreal Project folder." 78 | ) 79 | parser.add_argument( 80 | "--ue_path", 81 | help="Path to UE", 82 | required=True 83 | ) 84 | parser.add_argument( 85 | "--ue_proj_path", 86 | help="Path to target UE project", 87 | required=True 88 | ) 89 | parser.add_argument( 90 | "--ue_plugin_name", 91 | help="UE plugin module name, eg: rclUE", 92 | default='rclUE' 93 | ) 94 | parser.add_argument( 95 | "--ue_plugin_folder_name", 96 | help="UE plugin folder name, eg: rclUE", 97 | default='' 98 | ) 99 | parser.add_argument( 100 | "--ue_target_3rd_name", 101 | help="Target 3rd name under ThirdParty, eg: ros", 102 | default='ros' 103 | ) 104 | args = parser.parse_args() 105 | 106 | ue_plugin_folder_name = args.ue_plugin_folder_name 107 | if ue_plugin_folder_name == '': 108 | ue_plugin_folder_name = args.ue_plugin_name 109 | 110 | build_ros2( 111 | buildType = 'base', 112 | allowed_spaces = DEFAULT_ALLOWED_SPACES, 113 | pkgs = ['ue_msgs'], 114 | ros_ws = os.path.join(os.getcwd(), '../ros2_ws') 115 | ) 116 | install_ros2( 117 | projectPath = args.ue_proj_path, 118 | pluginName = args.ue_plugin_name, 119 | pluginFolderName = ue_plugin_folder_name, 120 | targetThirdpartyFolderName = args.ue_target_3rd_name, 121 | buildType = 'base', 122 | ros_ws = os.path.join(os.getcwd(), '../ros2_ws'), 123 | allowed_spaces = DEFAULT_ALLOWED_SPACES, 124 | not_allowed_spaces = DEFAULT_NOT_ALLOWED_SPACES, 125 | ) -------------------------------------------------------------------------------- /CodeGen/templates/Action.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | // This code has been autogenerated from {{data.Filename}} - do not modify 3 | 4 | #include "Actions/ROS2{{data.UEName}}.h" 5 | 6 | {{data.GoalConstantsDef}} 7 | {{data.ResultConstantsDef}} 8 | {{data.FeedbackConstantsDef}} 9 | 10 | const rosidl_action_type_support_t* UROS2{{data.UEName}}Action::GetTypeSupport() const 11 | { 12 | return ROSIDL_GET_ACTION_TYPE_SUPPORT({{data.Group}}, {{data.NameCap}}); 13 | } 14 | 15 | void UROS2{{data.UEName}}Action::Init() 16 | { 17 | Super::Init(); 18 | {{data.Group}}__action__{{data.NameCap}}_SendGoal_Request__init(&{{data.NameCap}}_goal_request); 19 | {{data.Group}}__action__{{data.NameCap}}_SendGoal_Response__init(&{{data.NameCap}}_goal_response); 20 | {{data.Group}}__action__{{data.NameCap}}_GetResult_Request__init(&{{data.NameCap}}_result_request); 21 | {{data.Group}}__action__{{data.NameCap}}_GetResult_Response__init(&{{data.NameCap}}_result_response); 22 | {{data.Group}}__action__{{data.NameCap}}_FeedbackMessage__init(&{{data.NameCap}}_feedback_message); 23 | } 24 | 25 | void UROS2{{data.UEName}}Action::Fini() 26 | { 27 | {{data.Group}}__action__{{data.NameCap}}_SendGoal_Request__fini(&{{data.NameCap}}_goal_request); 28 | {{data.Group}}__action__{{data.NameCap}}_SendGoal_Response__fini(&{{data.NameCap}}_goal_response); 29 | {{data.Group}}__action__{{data.NameCap}}_GetResult_Request__fini(&{{data.NameCap}}_result_request); 30 | {{data.Group}}__action__{{data.NameCap}}_GetResult_Response__fini(&{{data.NameCap}}_result_response); 31 | {{data.Group}}__action__{{data.NameCap}}_FeedbackMessage__fini(&{{data.NameCap}}_feedback_message); 32 | Super::Fini(); 33 | } 34 | 35 | void UROS2{{data.UEName}}Action::SetGoalRequest(const FROS{{data.UEName}}SGReq& Goal) 36 | { 37 | Goal.SetROS2({{data.NameCap}}_goal_request); 38 | } 39 | 40 | void UROS2{{data.UEName}}Action::GetGoalRequest(FROS{{data.UEName}}SGReq& Goal) const 41 | { 42 | Goal.SetFromROS2({{data.NameCap}}_goal_request); 43 | } 44 | 45 | void UROS2{{data.UEName}}Action::SetGoalResponse(const FROS{{data.UEName}}SGRes& Goal) 46 | { 47 | Goal.SetROS2({{data.NameCap}}_goal_response); 48 | } 49 | 50 | void UROS2{{data.UEName}}Action::GetGoalResponse(FROS{{data.UEName}}SGRes& Goal) const 51 | { 52 | Goal.SetFromROS2({{data.NameCap}}_goal_response); 53 | } 54 | 55 | void UROS2{{data.UEName}}Action::SetResultRequest(const FROS{{data.UEName}}GRReq& Result) 56 | { 57 | Result.SetROS2({{data.NameCap}}_result_request); 58 | } 59 | 60 | void UROS2{{data.UEName}}Action::GetResultRequest(FROS{{data.UEName}}GRReq& Result) const 61 | { 62 | Result.SetFromROS2({{data.NameCap}}_result_request); 63 | } 64 | 65 | void UROS2{{data.UEName}}Action::SetGoalIdToResultRequest(FROS{{data.UEName}}GRReq& Result) 66 | { 67 | for (int i = 0; i < 16; i++) 68 | { 69 | Result.GoalId[i] = {{data.NameCap}}_goal_request.goal_id.uuid[i]; 70 | } 71 | } 72 | 73 | void UROS2{{data.UEName}}Action::SetResultResponse(const FROS{{data.UEName}}GRRes& Result) 74 | { 75 | Result.SetROS2({{data.NameCap}}_result_response); 76 | } 77 | 78 | void UROS2{{data.UEName}}Action::GetResultResponse(FROS{{data.UEName}}GRRes& Result) const 79 | { 80 | Result.SetFromROS2({{data.NameCap}}_result_response); 81 | } 82 | 83 | 84 | void UROS2{{data.UEName}}Action::SetFeedback(const FROS{{data.UEName}}FB& Feedback) 85 | { 86 | Feedback.SetROS2({{data.NameCap}}_feedback_message); 87 | } 88 | 89 | void UROS2{{data.UEName}}Action::GetFeedback(FROS{{data.UEName}}FB& Feedback) const 90 | { 91 | Feedback.SetFromROS2({{data.NameCap}}_feedback_message); 92 | } 93 | 94 | void UROS2{{data.UEName}}Action::SetGoalIdToFeedback(FROS{{data.UEName}}FB& Feedback) 95 | { 96 | for (int i = 0; i < 16; i++) 97 | { 98 | Feedback.GoalId[i] = {{data.NameCap}}_goal_request.goal_id.uuid[i]; 99 | } 100 | } 101 | 102 | void* UROS2{{data.UEName}}Action::GetGoalRequest() 103 | { 104 | return &{{data.NameCap}}_goal_request; 105 | } 106 | 107 | void* UROS2{{data.UEName}}Action::GetGoalResponse() 108 | { 109 | return &{{data.NameCap}}_goal_response; 110 | } 111 | 112 | void* UROS2{{data.UEName}}Action::GetResultRequest() 113 | { 114 | return &{{data.NameCap}}_result_request; 115 | } 116 | 117 | void* UROS2{{data.UEName}}Action::GetResultResponse() 118 | { 119 | return &{{data.NameCap}}_result_response; 120 | } 121 | 122 | void* UROS2{{data.UEName}}Action::GetFeedbackMessage() 123 | { 124 | return &{{data.NameCap}}_feedback_message; 125 | } 126 | -------------------------------------------------------------------------------- /BuildROS2/pre_build_ros2_base.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ######################################################################### 4 | # install ros2 $ROS_DISTRO 5 | # https://docs.ros.org/en/$ROS_DISTRO/Installation/Ubuntu-Development-Setup.html 6 | ######################################################################### 7 | 8 | ROS2_WS=$1 9 | ROS_DISTRO=$2 10 | 11 | echo " 12 | ######################## 13 | Install ROS2 $ROS_DISTRO 14 | ######################## 15 | " 16 | 17 | # cleanup 18 | sudo rm /etc/apt/sources.list.d/ros2.list* 19 | sudo rm /etc/ros/rosdep/sources.list.d/20-default.list 20 | sudo rm $ROS2_WS/ros2.repos* 21 | 22 | ## Set locale 23 | locale # check for UTF-8 24 | 25 | sudo apt update && sudo apt install locales 26 | sudo locale-gen en_US en_US.UTF-8 27 | sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 28 | export LANG=en_US.UTF-8 29 | #locale # verify settings 30 | 31 | 32 | ## Add the ROS 2 apt repository 33 | apt-cache policy | grep universe 34 | 35 | sudo apt install software-properties-common 36 | sudo add-apt-repository universe 37 | 38 | sudo apt update && sudo apt install curl gnupg2 lsb-release 39 | sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg 40 | 41 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(source /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null 42 | 43 | ## Install development tools and ROS tools 44 | sudo apt update && sudo apt install -y \ 45 | build-essential \ 46 | cmake \ 47 | git \ 48 | libbullet-dev \ 49 | python3-colcon-common-extensions \ 50 | python3-flake8 \ 51 | python3-pip \ 52 | python3-pytest-cov \ 53 | python3-rosdep \ 54 | python3-setuptools \ 55 | python3-vcstool \ 56 | wget 57 | # install some pip packages needed for testing 58 | if [ $ROS_DISTRO == "jazzy" ]; then 59 | ADDITIONAL_PYTHON_OPTION=--break-system-packages 60 | fi 61 | python3 -m pip install -U \ 62 | $ADDITIONAL_PYTHON_OPTION \ 63 | argcomplete \ 64 | flake8-blind-except \ 65 | flake8-builtins \ 66 | flake8-class-newline \ 67 | flake8-comprehensions \ 68 | flake8-deprecated \ 69 | flake8-docstrings \ 70 | flake8-import-order \ 71 | flake8-quotes \ 72 | pytest-repeat \ 73 | pytest-rerunfailures \ 74 | pytest 75 | # install Fast-RTPS dependencies 76 | sudo apt install --no-install-recommends -y \ 77 | libasio-dev \ 78 | libtinyxml2-dev 79 | # install Cyclone DDS dependencies 80 | sudo apt install --no-install-recommends -y \ 81 | libcunit1-dev 82 | 83 | 84 | echo " 85 | ####################### 86 | Get ROS2 source 87 | ######################## 88 | " 89 | ## Get ROS2 code 90 | mkdir -p $ROS2_WS/src 91 | pushd $ROS2_WS 92 | if [ -z "$SKIP_VCS_IMPORT" ]; then 93 | echo "Running vcs import..." 94 | wget https://raw.githubusercontent.com/ros2/ros2/$ROS_DISTRO/ros2.repos 95 | vcs import src < ros2.repos 96 | else 97 | echo "Skipping vcs import (SKIP_VCS_IMPORT is set)" 98 | fi 99 | 100 | echo " 101 | ############################################## 102 | Ignore ros1_bridge and example_interfaces. 103 | ############################################### 104 | " 105 | touch src/ros2/ros1_bridge/COLCON_IGNORE 106 | touch src/ros2/example_interfaces/COLCON_IGNORE 107 | 108 | # Install dependencies using rosdep 109 | #sudo apt upgrade 110 | sudo rosdep init 111 | rosdep update 112 | rosdep install --from-paths src --ignore-src -ry --skip-keys "fastcdr rti-connext-dds-5.3.1 rti-connext-dds-6.0.1 urdfdom_headers" --rosdistro $ROS_DISTRO 113 | 114 | # remove unused dds 115 | sudo rm -r src/eclipse-cyclonedds 116 | 117 | popd 118 | 119 | ######################################################################### 120 | # Reinstall python package due to issues 121 | # https://github.com/ros-visualization/qt_gui_core/issues/212: 122 | ######################################################################### 123 | 124 | echo " 125 | ####################### 126 | Reinstall python package due to issues 127 | ######################## 128 | " 129 | 130 | sudo apt remove shiboken2 libshiboken2-dev libshiboken2-py3-5.14 -y 131 | pip3 install $ADDITIONAL_PYTHON_OPTION shiboken2 132 | 133 | echo " 134 | ####################### 135 | Clone rclc 136 | ######################## 137 | " 138 | git clone --branch $ROS_DISTRO https://github.com/ros2/rclc.git $ROS2_WS/src/ros2/rclc 139 | 140 | echo " 141 | ####################### 142 | Patch rcpputils 143 | ######################## 144 | " 145 | patch_path=$(pwd)/patches/$ROS_DISTRO 146 | echo $patch_path 147 | pushd $ROS2_WS/src/ros2/rcpputils 148 | git apply $patch_path/rcpputils.patch 149 | popd 150 | 151 | # can't patch here. patch after copied header under UE project. 152 | # cd $ROS2_WS/src/ros2/rcutils 153 | # git apply ../../../../patches/rcutils.patch 154 | 155 | echo " 156 | ############################################################ 157 | [Temp hack]Patch Fast-DDS and asio to avod crash in Editor 158 | ############################################################# 159 | " 160 | pushd $ROS2_WS/src/eProsima/Fast-DDS 161 | if [ $ROS_DISTRO == "foxy" ]; then 162 | git checkout 2.1.2 #temp hack since 2.1.x is deleted 163 | fi 164 | git apply $patch_path/Fast-DDS.patch 165 | git submodule init 166 | git submodule update 167 | pushd thirdparty/asio 168 | git apply $patch_path/asio.patch 169 | popd 170 | popd 171 | 172 | # remove locally installed asio in case. 173 | sudo apt purge -y libasio-* 174 | 175 | echo " 176 | ####################### 177 | Other dependency to build and copy lib to UE 178 | ######################## 179 | " 180 | 181 | # patchelf 182 | sudo apt install patchelf -y 183 | 184 | # iceoryx_hoofs dependency 185 | sudo apt-get install libacl1-dev -y 186 | 187 | # clang-13 188 | sudo su -c "echo 'deb http://archive.ubuntu.com/ubuntu/ focal-proposed universe' >> /etc/apt/sources.list" 189 | sudo apt update 190 | 191 | # CLANG_VER=13 192 | # if [ $ROS_DISTRO == "jazzy" ]; then 193 | # CLANG_VER=18 194 | # fi 195 | 196 | # sudo apt install clang-$CLANG_VER -y 197 | sudo apt install clang -y -------------------------------------------------------------------------------- /CodeGen/templates/Action.h: -------------------------------------------------------------------------------- 1 | // Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | // This code has been autogenerated from {{data.Filename}} - do not modify 3 | 4 | #pragma once 5 | 6 | // UE 7 | #include 8 | 9 | // ROS 10 | #include "unique_identifier_msgs/msg/uuid.h" 11 | #include "{{data.Group}}/action/{{data.Name}}.h" 12 | #include "action_msgs/srv/cancel_goal.h" 13 | 14 | // rclUE 15 | #include "Actions/ROS2GenericAction.h" 16 | #include "rclcUtilities.h" 17 | 18 | 19 | // Generated Msg/Srv/Action(can be empty) 20 | {{data.GoalHeaders}} 21 | {{data.ResultHeaders}} 22 | {{data.FeedbackHeaders}} 23 | 24 | // Generated 25 | #include "ROS2{{data.UEName}}.generated.h" 26 | 27 | USTRUCT(Blueprintable) 28 | struct {{data.ModuleAPI}} FROS{{data.UEName}}SGReq 29 | { 30 | GENERATED_BODY() 31 | 32 | public: 33 | {{data.GoalConstantsDec}} 34 | 35 | TArray> GoalId; 36 | 37 | {{data.GoalTypes}} 38 | 39 | FROS{{data.UEName}}SGReq() 40 | { 41 | UROS2Utils::GenerateRandomUUID16(GoalId); 42 | {{data.GoalConstructor}} 43 | } 44 | 45 | void SetFromROS2(const {{data.Group}}__action__{{data.NameCap}}_SendGoal_Request& in_ros_data) 46 | { 47 | for (int i=0; i<16; i++) 48 | { 49 | GoalId[i] = in_ros_data.goal_id.uuid[i]; 50 | } 51 | 52 | {{data.GoalSetFromROS2}} 53 | } 54 | 55 | void SetROS2({{data.Group}}__action__{{data.NameCap}}_SendGoal_Request& out_ros_data) const 56 | { 57 | for (int i=0; i<16; i++) 58 | { 59 | out_ros_data.goal_id.uuid[i] = GoalId[i]; 60 | } 61 | 62 | {{data.GoalSetROS2}} 63 | } 64 | }; 65 | 66 | USTRUCT(Blueprintable) 67 | struct {{data.ModuleAPI}} FROS{{data.UEName}}SGRes 68 | { 69 | GENERATED_BODY() 70 | 71 | public: 72 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 73 | bool bAccepted = false; 74 | 75 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 76 | float Stamp = 0.f; 77 | 78 | void SetFromROS2(const {{data.Group}}__action__{{data.NameCap}}_SendGoal_Response& in_ros_data) 79 | { 80 | bAccepted = in_ros_data.accepted; 81 | Stamp = UROS2Utils::ROSStampToFloat(in_ros_data.stamp); 82 | } 83 | 84 | void SetROS2({{data.Group}}__action__{{data.NameCap}}_SendGoal_Response& out_ros_data) const 85 | { 86 | out_ros_data.accepted = bAccepted; 87 | out_ros_data.stamp = UROS2Utils::FloatToROSStamp(Stamp); 88 | } 89 | }; 90 | 91 | USTRUCT(Blueprintable) 92 | struct {{data.ModuleAPI}} FROS{{data.UEName}}GRReq 93 | { 94 | GENERATED_BODY() 95 | 96 | public: 97 | TArray> GoalId; 98 | 99 | FROS{{data.UEName}}GRReq() 100 | { 101 | GoalId.Init(0, 16); 102 | } 103 | 104 | void SetFromROS2(const {{data.Group}}__action__{{data.NameCap}}_GetResult_Request& in_ros_data) 105 | { 106 | for (int i=0; i<16; i++) 107 | { 108 | GoalId[i] = in_ros_data.goal_id.uuid[i]; 109 | } 110 | 111 | } 112 | 113 | void SetROS2({{data.Group}}__action__{{data.NameCap}}_GetResult_Request& out_ros_data) const 114 | { 115 | for (int i=0; i<16; i++) 116 | { 117 | out_ros_data.goal_id.uuid[i] = GoalId[i]; 118 | } 119 | } 120 | }; 121 | 122 | USTRUCT(Blueprintable) 123 | struct {{data.ModuleAPI}} FROS{{data.UEName}}GRRes 124 | { 125 | GENERATED_BODY() 126 | 127 | public: 128 | {{data.ResultConstantsDec}} 129 | 130 | UPROPERTY() 131 | int8 GRResStatus = 0; 132 | 133 | {{data.ResultTypes}} 134 | 135 | FROS{{data.UEName}}GRRes() 136 | { 137 | {{data.ResultConstructor}} 138 | } 139 | 140 | void SetFromROS2(const {{data.Group}}__action__{{data.NameCap}}_GetResult_Response& in_ros_data) 141 | { 142 | GRResStatus = in_ros_data.status; 143 | {{data.ResultSetFromROS2}} 144 | } 145 | 146 | void SetROS2({{data.Group}}__action__{{data.NameCap}}_GetResult_Response& out_ros_data) const 147 | { 148 | out_ros_data.status = GRResStatus; 149 | {{data.ResultSetROS2}} 150 | } 151 | }; 152 | 153 | USTRUCT(Blueprintable) 154 | struct {{data.ModuleAPI}} FROS{{data.UEName}}FB 155 | { 156 | GENERATED_BODY() 157 | 158 | public: 159 | {{data.FeedbackConstantsDec}} 160 | 161 | TArray> GoalId; 162 | 163 | {{data.FeedbackTypes}} 164 | 165 | 166 | FROS{{data.UEName}}FB() 167 | { 168 | GoalId.Init(0, 16); 169 | {{data.FeedbackConstructor}} 170 | } 171 | 172 | void SetFromROS2(const {{data.Group}}__action__{{data.NameCap}}_FeedbackMessage& in_ros_data) 173 | { 174 | for (int i=0; i<16; i++) 175 | { 176 | GoalId[i] = in_ros_data.goal_id.uuid[i]; 177 | } 178 | 179 | {{data.FeedbackSetFromROS2}} 180 | } 181 | 182 | void SetROS2({{data.Group}}__action__{{data.NameCap}}_FeedbackMessage& out_ros_data) const 183 | { 184 | for (int i=0; i<16; i++) 185 | { 186 | out_ros_data.goal_id.uuid[i] = GoalId[i]; 187 | } 188 | 189 | {{data.FeedbackSetROS2}} 190 | } 191 | }; 192 | 193 | UCLASS() 194 | class {{data.ModuleAPI}} UROS2{{data.UEName}}Action : public UROS2GenericAction 195 | { 196 | GENERATED_BODY() 197 | 198 | public: 199 | virtual void Init() override; 200 | 201 | virtual void Fini() override; 202 | 203 | virtual const rosidl_action_type_support_t* GetTypeSupport() const override; 204 | 205 | UFUNCTION(BlueprintCallable) 206 | void SetGoalRequest(const FROS{{data.UEName}}SGReq& Goal); 207 | 208 | UFUNCTION(BlueprintCallable) 209 | void GetGoalRequest(FROS{{data.UEName}}SGReq& Goal) const; 210 | 211 | UFUNCTION(BlueprintCallable) 212 | void SetGoalResponse(const FROS{{data.UEName}}SGRes& Goal); 213 | 214 | UFUNCTION(BlueprintCallable) 215 | void GetGoalResponse(FROS{{data.UEName}}SGRes& Goal) const; 216 | 217 | UFUNCTION(BlueprintCallable) 218 | void SetResultRequest(const FROS{{data.UEName}}GRReq& Result); 219 | 220 | UFUNCTION(BlueprintCallable) 221 | void GetResultRequest(FROS{{data.UEName}}GRReq& Result) const; 222 | 223 | UFUNCTION(BlueprintCallable) 224 | void SetGoalIdToResultRequest(FROS{{data.UEName}}GRReq& Result); 225 | 226 | UFUNCTION(BlueprintCallable) 227 | void SetResultResponse(const FROS{{data.UEName}}GRRes& Result); 228 | 229 | UFUNCTION(BlueprintCallable) 230 | void GetResultResponse(FROS{{data.UEName}}GRRes& Result) const; 231 | 232 | UFUNCTION(BlueprintCallable) 233 | void SetFeedback(const FROS{{data.UEName}}FB& Feedback); 234 | 235 | UFUNCTION(BlueprintCallable) 236 | void GetFeedback(FROS{{data.UEName}}FB& Feedback) const; 237 | 238 | UFUNCTION(BlueprintCallable) 239 | void SetGoalIdToFeedback(FROS{{data.UEName}}FB& Feedback); 240 | 241 | virtual void* GetGoalRequest() override; 242 | virtual void* GetGoalResponse() override; 243 | virtual void* GetResultRequest() override; 244 | virtual void* GetResultResponse() override; 245 | virtual void* GetFeedbackMessage() override; 246 | 247 | {{data.GoalConstantsGetter}} 248 | {{data.ResultConstantsGetter}} 249 | {{data.FeedbackConstantsGetter}} 250 | 251 | private: 252 | {{data.Group}}__action__{{data.NameCap}}_SendGoal_Request {{data.NameCap}}_goal_request; 253 | {{data.Group}}__action__{{data.NameCap}}_SendGoal_Response {{data.NameCap}}_goal_response; 254 | {{data.Group}}__action__{{data.NameCap}}_GetResult_Request {{data.NameCap}}_result_request; 255 | {{data.Group}}__action__{{data.NameCap}}_GetResult_Response {{data.NameCap}}_result_response; 256 | {{data.Group}}__action__{{data.NameCap}}_FeedbackMessage {{data.NameCap}}_feedback_message; 257 | }; 258 | -------------------------------------------------------------------------------- /docker_build_install_codegen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import docker 5 | import uuid 6 | from build_install_codegen import args_setup, load_from_configs 7 | 8 | DOCKER_IMAGE = 'yuokamoto1988/ue_ros2_base' 9 | 10 | def create_dir_mount(dir=os.getcwd(), target='', exception=[]): 11 | volumes = [] 12 | files = os.listdir(dir) 13 | for f in files: 14 | if f in exception: 15 | continue 16 | else: 17 | volumes.append(os.path.join(dir, f) + ':' + os.path.join(target, f)) 18 | return volumes 19 | 20 | def exec_run_with_log(container, command, user): 21 | _, stream = container.exec_run(command, user=user, stream=True) 22 | for data in stream: 23 | print(data.decode()) 24 | 25 | if __name__ == '__main__': 26 | 27 | parser = args_setup() 28 | parser.add_argument( 29 | '--pull_inside_docker', 30 | help='pull additonal repos inside docker or outside docker and mount', 31 | action='store_true' 32 | ) 33 | parser.add_argument( 34 | '--docker_image', 35 | help='docker image name. if this is not provided, yuokamoto1988/ue_ros2_base:$ROSDISTRO is used', 36 | default="" 37 | ) 38 | parser.add_argument( 39 | '--create_intermediate_image', 40 | help='if user id is not 1000, this script overwrite id of files inside docker. Since it takes time to chown many files, you can save image by this option', 41 | action='store_true' 42 | ) 43 | args = parser.parse_args() 44 | 45 | config_files = ['default_config.yaml.'+args.rosdistro] 46 | if args.config is not None: 47 | config_files.extend(args.config) 48 | 49 | UEPath, projectPath, pluginName, \ 50 | pluginFolderName, targetThirdpartyFolderName, \ 51 | target, black_list, dependency, name_mapping, repos, ignore_deprecated_msg = load_from_configs(config_files, args.ros_ws, False, False) 52 | 53 | # common params 54 | user = 'admin' 55 | cur_dir = os.getcwd() 56 | home = os.environ['HOME'] 57 | docker_hoeme_dir = '/home/' + user 58 | 59 | # initialization of command and volumes for docker 60 | volumes = [projectPath + ':' + docker_hoeme_dir + projectPath.replace(home, '')] 61 | 62 | command = 'python3 build_install_codegen.py ' 63 | 64 | # pass arg to command inside docker. 65 | arg_dict = vars(args) 66 | for arg in arg_dict: 67 | if arg in ['ros_ws', 'config', 'docker_image', 'create_intermediate_image']: #skip some args 68 | continue 69 | arg_value = arg_dict[arg] 70 | if type(arg_value) == type(True): 71 | if arg_value: 72 | command += ' --' + arg 73 | elif arg_value is not None : 74 | command += ' --' + arg + ' ' + str(arg_value) 75 | 76 | # mount UE_tools except for ros2_ws 77 | volumes.extend(create_dir_mount(dir=cur_dir, target=docker_hoeme_dir + '/UE_tools', exception=['ros2_ws', 'tmp'])) 78 | 79 | # handle additional repos 80 | command += ' --skip_pull ' 81 | env_vars = ['-e', 'SKIP_VCS_IMPORT=1'] 82 | if args.build: 83 | if not args.pull_inside_docker: 84 | if not os.path.exists('tmp'): 85 | os.makedirs('tmp') 86 | if not os.path.exists('tmp/pkgs'): 87 | os.makedirs('tmp/pkgs') 88 | if not os.path.exists('tmp/ros2'): 89 | os.makedirs('tmp/ros2') 90 | 91 | print('Pull repos') 92 | os.system('vcs import --repos --debug ' + 'tmp/pkgs' + ' < ' + os.path.join(home, repos)) 93 | if args.type == 'base': 94 | print('======================================') 95 | print('Building BASE type - importing ros2.repos') 96 | print('======================================') 97 | os.system('wget https://raw.githubusercontent.com/ros2/ros2/' + args.rosdistro + '/ros2.repos') 98 | os.system('vcs import --repos --debug tmp/ros2 < ros2.repos') 99 | os.system('touch tmp/ros2/ros2/example_interfaces/COLCON_IGNORE') 100 | else: 101 | print('======================================') 102 | print(f'Building {args.type.upper()} type - skipping ros2.repos import') 103 | print('======================================') 104 | 105 | volumes.extend(create_dir_mount(os.path.join(cur_dir, 'tmp'), docker_hoeme_dir + '/UE_tools/ros2_ws/src')) 106 | elif repos: 107 | volumes.append(os.path.join(os.environ['HOME'], repos) + ':' + docker_hoeme_dir + repos.replace(home, '')) 108 | 109 | # mount config 110 | for config_file in config_files: 111 | id = str(uuid.uuid4()) 112 | target_path = docker_hoeme_dir + '/config_' + id + '.yaml' 113 | volumes.append(os.path.abspath(config_file) + ':' + target_path) 114 | command += ' --config ' + target_path 115 | 116 | # update volume mode 117 | for i, v in enumerate(volumes): 118 | volumes[i] += ':rw' 119 | # volumes.append('/etc/group:/etc/group:ro') 120 | # volumes.append('/etc/passwd:/etc/passwd:ro') 121 | 122 | # create containers 123 | client = docker.from_env() 124 | container_name = 'ue_ros2_base' 125 | try: #if docker exists, remove 126 | c = client.containers.get(container_name) 127 | c.stop() 128 | c.remove() 129 | except: 130 | pass 131 | 132 | docker_image = args.docker_image 133 | if not args.docker_image: 134 | docker_image = DOCKER_IMAGE + ':' + args.rosdistro 135 | print('Run docker conatiner named:' + container_name) 136 | print(' image name:', docker_image) 137 | # print(' mount volumes:', volumes) 138 | container = client.containers.run( 139 | docker_image, 140 | 'sleep infinity', 141 | user=0, #os.getuid(), 142 | environment={ 143 | "USER_ID":str(os.getuid()), 144 | "GROUP_ID":str(os.getgid()), 145 | "SKIP_VCS_IMPORT":"1" 146 | }, 147 | name=container_name, 148 | volumes=volumes, 149 | detach=True) 150 | 151 | if os.getuid() != 1000: 152 | print('Change dir owner to same id as current user') 153 | exec_run_with_log(container, 'chown -R admin:admin /home/admin', user='root') 154 | exec_run_with_log(container, 'chown -R admin:admin tmp', user='root') 155 | if args.create_intermediate_image: 156 | os.system('docker commit ' + container_name + ' ' + docker_image + '_chown') 157 | print('Commit image after chown as ' + docker_image + '_chown') 158 | 159 | 160 | if args.build and repos and args.pull_inside_docker: 161 | pull_cmd = '/bin/bash -c "vcs import --repos --debug ' + \ 162 | docker_hoeme_dir + '/UE_tools/ros2_ws/src/pkgs' + ' < ' + \ 163 | docker_hoeme_dir + repos.replace(home, '') + '"' 164 | print('Pull repo inside container with ' + pull_cmd) 165 | exec_run_with_log(container, pull_cmd, user='admin') 166 | 167 | # execute command 168 | 169 | # command = "/bin/bash -c 'source .python3_venv/bin/activate && " + command + "'" 170 | print('Execute command in conatainer: ' + command) 171 | if args.rosdistro == 'jazzy': # todo: this works for humble and foxy as well? 172 | os.system('docker exec -u admin -it ' + container_name + ' ' + command) 173 | else: 174 | exec_run_with_log(container, command, user='admin') # this does not work with jazzy somehow -------------------------------------------------------------------------------- /BuildROS2/libs_utils.py: -------------------------------------------------------------------------------- 1 | import os, sys, shutil, re, glob 2 | from subprocess import check_output 3 | 4 | # this script forces to link libraries again on next IDE run 5 | # (inside present a workaround for build.cs file, that's trick is to avoid deleting 'intermediate' folder at all) 6 | def InvalidateBinaries(projectPathBinaries, rclUEBinaries, rclUEBuildCS): 7 | cwd = os.getcwd() 8 | 9 | if os.path.isdir(projectPathBinaries): 10 | print('Removing:', projectPathBinaries) 11 | shutil.rmtree(projectPathBinaries) 12 | 13 | if os.path.isdir(rclUEBinaries): 14 | print('Removing:', rclUEBinaries) 15 | shutil.rmtree(rclUEBinaries) 16 | 17 | if os.path.isfile(rclUEBuildCS): 18 | print('Invalidating:', rclUEBuildCS) 19 | content = '' 20 | 21 | with open(rclUEBuildCS, 'r') as f: 22 | content = f.read() 23 | 24 | # workaround with extra-new symbol, flip-flop with-without that symbol 25 | if content[-1] == '\n': 26 | content = content[:-1] 27 | else: 28 | content += '\n' 29 | 30 | with open(rclUEBuildCS, 'w') as f: 31 | f.write(content) 32 | 33 | # To find lib dependencies check colcon command: 34 | # colcon graph --packages-up-to rmw_cyclonedds_cpp --packages-above fastrtps 35 | def FindLibsDependencies(): 36 | pass 37 | 38 | def GetLibs(folder): 39 | libs = list() 40 | 41 | for dirpath,subdirs,files in os.walk(folder): 42 | for file in files: 43 | if file.startswith('lib') and (file.endswith('.so') or '.so.' in file): 44 | fullName = os.path.join(dirpath, file) 45 | libs.append(fullName) 46 | 47 | return libs 48 | 49 | # Runs command for every lib in the folder, creates folder with output 'txt' file for every lib 50 | # commandName: 51 | # 'readelf -d' 52 | # 'readelf -Wa' 53 | # 'readelf -Ws' 54 | # 'objdump -x' 55 | # 'ld' 56 | # 'ldd' 57 | # 'nm --demangle' 58 | def CreateInfoForAll(commandName, folderWithLibs, folderOut): 59 | if os.path.isdir(folderOut): 60 | shutil.rmtree(folderOut) 61 | 62 | os.mkdir(folderOut) 63 | 64 | for lib in GetLibs(folderWithLibs): 65 | head, tail = os.path.split(lib) 66 | command = commandName + ' ' + lib + ' > ' + folderOut + '/' + tail + '.txt' # ' |head -20 > ' ' |grep RPATH' 67 | os.system(command) 68 | 69 | # we really need to be sure that we don't use libraries from 'build' or 'install' folders 70 | # which are metntioned in 'RPATH', 'LD_LIBRARY_PATH', 'RUNPATH' values 71 | def RenameDir(dir, suffix): 72 | if os.path.isdir(dir): 73 | print('> rename', dir, dir + suffix) 74 | os.rename(dir, dir + suffix) 75 | 76 | return dir + suffix 77 | 78 | def GrabLibs(folderFrom, folderTo, allowed_spaces): 79 | filesCount = 0 80 | 81 | for dirpath,subdirs,files in os.walk(folderFrom): 82 | for file in files: 83 | if file.startswith('lib') and (file.endswith('.so') or '.so.' in file) and \ 84 | any(elem in file for elem in allowed_spaces): 85 | filesCount += 1 86 | fileFrom = os.path.join(dirpath, file) 87 | fileTo = folderTo + '/' + file 88 | shutil.copy(fileFrom, fileTo) 89 | 90 | print('Grabbed libs (from ' + folderFrom + ' to ' + folderTo + '): ' + str(filesCount)) 91 | 92 | def GrabIncludes(folderFrom, folderTo, allowed_spaces): 93 | foldersCount = 0 94 | 95 | for elemName in os.listdir(folderFrom): 96 | dirPath = os.path.join(folderFrom, elemName) 97 | includeFolder = os.path.join(dirPath, 'include') 98 | 99 | if os.path.isdir(dirPath) and os.path.isdir(includeFolder) and \ 100 | any(elem in elemName for elem in allowed_spaces): 101 | for subincludeElem in os.listdir(includeFolder): 102 | subincludePath = os.path.join(includeFolder, subincludeElem) 103 | 104 | if os.path.isdir(subincludePath): 105 | folderToFull = os.path.join(folderTo, subincludeElem) 106 | shutil.copytree(subincludePath, folderToFull, dirs_exist_ok=True) 107 | foldersCount += 1 108 | 109 | print('Grabbed include folders (from ' + folderFrom + ' to ' + folderTo + '): ' + str(foldersCount)) 110 | 111 | def CleanLibs(dir, not_allowed_spaces): 112 | removedCount = 0 113 | for dirpath,subdirs,files in os.walk(dir): 114 | for file in files: 115 | if any(elem in file for elem in not_allowed_spaces): 116 | fileName = os.path.join(dirpath, file) 117 | removedCount += 1 118 | os.remove(fileName) 119 | 120 | print('Libs files cleaned:', removedCount) 121 | 122 | def CleanIncludes(dir, not_allowed_spaces): 123 | removedCount = 0 124 | for elemName in os.listdir(dir): 125 | dirPath = os.path.join(dir, elemName) 126 | 127 | if os.path.isdir(dirPath) and any(elem in elemName for elem in not_allowed_spaces): 128 | shutil.rmtree(dirPath) 129 | removedCount += 1 130 | 131 | print('Include folders cleaned:', removedCount) 132 | 133 | def RunCommandForEveryLib(folderName, commandArgsList): 134 | print('>', ' '.join(commandArgsList)) 135 | for fullName in GetLibs(folderName): 136 | resultRaw = '' 137 | command = commandArgsList 138 | command.append(fullName) 139 | try: 140 | resultRaw = check_output(command) 141 | except Exception: 142 | print('[Error] file:', fullName) 143 | 144 | def ReplaceSonameWithFileRemove(folderName, libDir, soname, fileName): 145 | libFileNameNew = libDir + '/' + soname 146 | libFileNameOld = libDir + '/' + fileName 147 | # leave only one file, starting from *.so.*, but rename it after all to *.so 148 | if libFileNameNew != libFileNameOld and os.path.exists(libFileNameOld): 149 | if os.path.exists(libFileNameNew): 150 | os.remove(libFileNameNew) 151 | 152 | os.rename(libFileNameOld, libFileNameNew) 153 | 154 | command = 'patchelf --set-soname ' + soname + ' ' + libFileNameNew 155 | print('> ', command) 156 | os.system(command) 157 | 158 | RunCommandForEveryLib(folderName, ['patchelf', '--replace-needed', fileName, soname]) 159 | 160 | def RemoveLibraryDependnecy(folderName, soname): 161 | RunCommandForEveryLib(folderName, ['patchelf', '--remove-needed', soname]) 162 | 163 | def RemovePyDependency(pluginPath, projectPath): 164 | print('Looking for python related libs...') 165 | libsReplacements = dict() 166 | lib_files = {os.path.basename(x):os.path.dirname(x) for x in glob.glob(os.path.join(projectPath, 'Plugins/**/*.so'), recursive=True)} 167 | for fullName in GetLibs(pluginPath): 168 | lddInfoRaw = os.popen('ldd ' + fullName).read() 169 | for rawInfoLine in lddInfoRaw.split('\n'): 170 | if 'python' in rawInfoLine or 'rosidl_generator_py' in rawInfoLine: 171 | soname = rawInfoLine.split('=>')[0].lstrip().rstrip() 172 | os.system('patchelf --remove-needed ' + soname + ' ' + fullName) 173 | 174 | 175 | # UE4.27 can't deal with so libs with version (for example libmyname.so.2.0.3) 176 | # you can fix it by adding: 177 | # /home/vilkun/UE/UnrealEngine/Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxToolChain.cs 178 | # in LinkFiles() add 1 line: 179 | # string LibName = Path.GetFileNameWithoutExtension(AdditionalLibrary); 180 | # +++LibName = LibName.Split('.')[0]; 181 | # but it's not a proper fix, since we provide plugin for end users, and can't change their UE4.27 182 | # So solution can be run renaming for all of this libraries 'SONAME' and links to this libraries inside all others 183 | def RenameLibsWithVersion(pluginPath, projectPath): 184 | print('Looking for libs with version...') 185 | versionMarker = '.so.' 186 | libsReplacements = dict() 187 | # map {libname:dir} 188 | lib_files = {os.path.basename(x):os.path.dirname(x) for x in glob.glob(os.path.join(projectPath, 'Plugins/**/*.so'), recursive=True)} 189 | for fullName in GetLibs(pluginPath): 190 | lddInfoRaw = os.popen('ldd ' + fullName).read() 191 | for rawInfoLine in lddInfoRaw.split('\n'): 192 | if versionMarker in rawInfoLine and 'not found' in rawInfoLine: 193 | libNameVersioning = rawInfoLine.split('=>')[0].lstrip().rstrip() 194 | soname = libNameVersioning.split(versionMarker)[0] + '.so' 195 | if soname in lib_files: 196 | libsReplacements[libNameVersioning] = [soname, lib_files[soname]] 197 | else: 198 | print('[Error] Failed to find potential lib:', soname) 199 | 200 | libsReplacements = dict(sorted(libsReplacements.items())) 201 | print('Libs with version which needed to be renamed: {}'.format(libsReplacements)) 202 | for libNameVersioning, soname_and_dir in libsReplacements.items(): 203 | ReplaceSonameWithFileRemove(pluginPath, soname_and_dir[1], soname_and_dir[0], libNameVersioning) 204 | 205 | # this arguments order like '--remove-rpath' and '--force-rpath --set-rpath' is a hack 206 | # check https://github.com/NixOS/patchelf/issues/94 207 | 208 | # every lib already contains ORIGIN due to linker flag, which I provided by '-rpath', see: 209 | # build_ros_libs.sh 210 | # but here we can 'reset' all other additional user paths which appears inside RPATH during build 211 | def SetRPATH(folderName): 212 | RunCommandForEveryLib(folderName, ['patchelf', '--remove-rpath']) 213 | RunCommandForEveryLib(folderName, ['patchelf', '--force-rpath', '--set-rpath', r'${ORIGIN}']) 214 | 215 | # help function during investigation 216 | def CheckLibs(folderName): 217 | libs = GetLibs(folderName) 218 | libs.sort() 219 | libsSet = set() 220 | 221 | print('Found libs:', len(libs)) 222 | print('"' + '",\n"'.join(libs) + '"') 223 | 224 | print('Checking duplications...') 225 | 226 | for lib in libs: 227 | if lib in libsSet: 228 | print('Duplication:', lib) 229 | else: 230 | libsSet.add(lib) 231 | 232 | print('Error. Duplications were found') if len(libs) != len(libsSet) else print('All ok. Duplications are not found') 233 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | UE_tools 2 | ========== 3 | # Overview 4 | This repo has tools to build and install ros2 lib to UnrealEngine Plugins. 5 | [rclUE](https://github.com/rapyuta-robotics/rclUE) has lib and codes build/generated from this repo. 6 | This repo can be used to build custom msg and install into your own UE plugins as well. 7 | 8 | *[Details](#details) and [Module Overview](#module-overview) is for developers. 9 | 10 | # Build and install with docker 11 | Execute operation inside docker container and copy inside from container. 12 | 13 | Available images: 14 | - `yuokamoto1988/ue_ros2_base:foxy` : Ubuntu 20.04 with ROS2 foxy 15 | - `yuokamoto1988/ue_ros2_base:humble` : Ubuntu 22.04 with ROS2 humble 16 | - `yuokamoto1988/ue_ros2_base:jazzy` : Ubuntu 24.04 with ROS2 jazzy 17 | 18 | ## General usage 19 | - 20 | `python3 docker_build_install_codegen.py --type [--build] [--install] [--codegen] [--rosdistro ] [--config ]` 21 | - options 22 | - --type: 23 | - base: Build core ros2 libs and copy to target plugin. see [ROS2 Lib Update](#ros2-lib-update) 24 | - pkgs: Build given pkgs in config and copy to target plugin . see [Interface update](#interface-update) 25 | - --build: 26 | 27 | Build core lib or pkgs in config under ros2_ws. `--type base --build` is done as part of Docker image build. 28 | - --install: 29 | 30 | install lib and header from ros2_ws. lib and headers are installed into UE project written in config file specify by `--config` option. 31 | - --codegen: 32 | 33 | Generate UE code and copy to target plugins. Only valid with `--type pkgs`. Generated codes are copied to UE project written in config file specify by `--config` option. 34 | - --config: 35 | 36 | config file path. please refer default_config.yaml. you can provide multiple config file and parameters are overwritten by later config files. Scripts loads `default.config` always as a first yaml file. 37 | - --pull_inside_docker: 38 | 39 | Pull additional repo specified in config file inside or outside of docker. If you not specify this option, repo is copied in `/tmp` dir, which is useful to clone private repos. Please refer ros2_additional_pkgs.repos. 40 | 41 | ## Usage/example 42 | ### Add custom lib to your Plugin 43 | 1. Create yaml file which has path to your project and plugin which you want to install ros2 libs. Please refer default.yaml file as a config file template. 44 | 2. `python3 docker_build_install_codegen.py --type pkgs --build --install --codegen --rosdistro --config ` 45 | 1. example is `custom_config.yaml` 46 | 3. Update your plugin build.cs to build with lib and headers. Please refer rclUE.buid.cs. 47 | 48 | *Please check [CustomMsgExample](https://github.com/yuokamoto/rclUE-Examples/blob/custom_msg_example/Plugins/CustomMsgExample/README.md) as a example of custom msg in different plugin then rclUE. 49 | 50 | 51 | ## Docker Image build and update rclUE(for developer) 52 | 1. build image: `./build_docker.sh ` 53 | *This build operation is done as part of image build process. Please check Dockerfile. 54 | *rclUE already has installed lib and headers and generated codes. 55 | 2. copy base libs: `python3 docker_build_install_codegen.py --type base --install --codegen --rosdistro ` 56 | 3. copy base msgs: `python3 docker_build_install_codegen.py --type pkgs --install --codegen --rosdistro ` 57 | 58 | # Build and install without docker 59 | *build inside docker is recommended. 60 | *This can broke your locally installed ROS2. Please re-run `sudo apt install ros--destop` after this operation. 61 | 62 | - `python3 build_install_codegen.py --type --codegen --build --config ` 63 | - options 64 | - --type: 65 | - base: Build core ros2 libs and copy to target plugin. see [ROS2 Lib Update](#ros2-lib-update) 66 | - pkgs: Build given pkgs in config and copy to target plugin . see [Interface update](#interface-update) 67 | - --build: 68 | 69 | Build core lib or pkgs in config under BuildROS2/ros2_ws. You need to copy custom src manually if you need. 70 | - --codegen: 71 | 72 | Generate UE code and copy to target plugins. Only valid with --type==pkgs 73 | - --config: 74 | 75 | Config file path. please refer default_config.yaml. you can provide multiple config file and parameters are overwritten by later config files. 76 | - --skip_pull: 77 | 78 | Avoid pulling repos. 79 | 80 | 81 | 82 | *need to build core lib once with `--type base` to build other pkgs. 83 | 84 | *this helper script uses [ROS2 Lib Update](#ros2-lib-update) and [Interface update](#interface-update) internally. 85 | 86 | ## Usage/example 87 | ### Add custom lib to your Plugin 88 | 1. Create yaml file which has path to your project and plugin which you want to install ros2 libs. Please refer default.yaml file as a config file template. 89 | 2. build ros2 base and base msg. This will build pkgs inside `ros2_ws` dir 90 | ``` 91 | python3 build_install_codegen.py --type base --build 92 | python3 build_install_codegen.py --type pkgs --build 93 | ``` 94 | 3. build your custom msgs 95 | ``` 96 | python3 build_install_codegen.py --type pkgs --build --codegen --install --config custom_config.yaml 97 | 98 | ``` 99 | 4. Update your plugin build.cs to build with lib and headers. Please refer rclUE.buid.cs. 100 | 101 | # Module Overview 102 | 103 | ## ROS2 Lib update 104 | 105 | [rclUE](https://github.com/rapyuta-robotics/rclUE) has ros2 lib and header files to use ROS2 core 106 | functionality from UnrealEngine. 107 | 108 | If you want to update ros2 lib in rclUE, need to follow [Build core lib](#build-core-lib) which build libs and copy lib and header files under rclUE. 109 | 110 | ## Interface update 111 | 112 | Commonly used interfaces such as std_msgs are inside rclUE, but sometime you want to use your custom msg from UnrealEngine. 113 | 114 | To use new msg in UnrealEngine Project, 115 | 116 | 1. [Build msg lib](#build-msg-lib) :build msg lib with patches 117 | 2. [Generate source files](#generate-source-files) :generate .h and .cpp files which are used inside UnrealEngine 118 | 3. [Copy source files to Unreal Project](#copy-source-files-to-unreal-project) :copy generated .h and .cpp files to UnrealEngine project. 119 | 4. Build your UnrealEngine project. 120 | 121 | 122 | # Details 123 | 124 | ## BuiildROS2 125 | Python scripts to build ros2 foxy pkgs from [source](https://docs.ros.org/en/foxy/Installation/Ubuntu-Development-Setup.html) with necessary changes to be used inside UnrealEngine project. Generated lib and header files are used inside UnrealEngine project, mainly by [rclUE](https://github.com/rapyuta-robotics/rclUE). 126 | 127 | ### Patches 128 | We apply patch for ros2 to avoid setting LD_LIBRARY_PATH environment variable to the dynamic libs paths. 129 | You can find changes in BuildROS2/patches 130 | - rcpputils: return library names instead of empty string to make it use without setting env variable LD_LIBRARY_PATH. 131 | - rcutils: comment out \_\_STDC_VERSION\_\_ since it is not always defined. 132 | 133 | ### Usage 134 | #### Build core lib 135 | 136 | 1. cd UE_tools/BuildROS2 137 | 2. python3 build_and_install_ros2_base.py --ue_path /home/user/UnrealEngine/ --ue_proj_path /home/user/turtlebot3-UE/` 138 | 139 | e.g. 140 | 141 | `python3 build_and_install_ros2_base.py --ue_path /home/user/UnrealEngine --ue_proj_path /home/user/turtlebot3-UE` 142 | 143 | #### Build msg lib 144 | 145 | 1. cd UE_tools/BuildROS2 146 | 2. python3 build_and_install_ros2_pkgs.py --ue_path --ue_proj_path --ros_pkgs 147 | 148 | e.g. 149 | 150 | `python3 build_and_install_ros2_pkgs.py --ue_path /home/user/UnrealEngine/ --ue_proj_path /home/user/turtlebot3-UE/ --ros_pkgs ue_msgs std_msgs example_interfaces` 151 | 152 | ### Scripts 153 | - **build_and_install_ros2_base.py**: 154 | 155 | Build ros2 foxy from source with patches and copy lib and header files to UnrealEngine project folder. 156 | 157 | This is mainly used to update ros2 lib in the [rclUE](https://github.com/rapyuta-robotics/rclUE). This is required to create lib and header for core ros2 functionality, e.g. creating node, publisher, subscriber, etc. 158 | 159 | 160 | This script will create **ros2_ws** under BuildROS2 and build there. 161 | 162 | \* This script will modify local ros2 installation. Need to `sudo apt install ros-foxy-desktop` if you want to use standard ros2 foxy. todo: build inside docker to avoid affecting local setup. 163 | 164 | 165 | \* **Note** 166 | 167 | - Reinstall python package due to issues https://github.com/ros-visualization/qt_gui_core/issues/212: 168 | - [Apply patch](#patches) Apply patch for ros2 to avoid setting LD_LIBRARY_PATH environment variable to the dynamic libs paths 169 | - 170 | 171 | 172 | - **build_and_install_ros2_pkgs.py**: 173 | 174 | Build ros2 foxy package inside **ros2_ws** which is created by `build_and_install_ros2_pkgs.py` and copy lib and header files to UnrealEngine project folder. This is mainly used to add/update new msgs to UnrealEngine project. 175 | 176 | This script will get source listed in **ros2_additional_pkgs.repos** and build pkgs listed in top of the script or given arg. 177 | 178 | You need to follow CodeGen tutorial as well to use msg from UnrealEngine. 179 | 180 | ## CodeGen 181 | Python script to generate UE4 .h and .cpp files for UnrealEngine to interface with ROS2 messages. 182 | 183 | \* Based on Jinja2 to generate msg 184 | 185 | \* Ignores message types deprecated in Foxy. 186 | 187 | ### Usage 188 | 189 | #### Generate source files 190 | 1. cd UE_tools/CodeGen 191 | 2. python3 gen_ue_from_ros.py --module --dependency --target 192 | 193 | e.g. 194 | 195 | `python3 gen_ue_from_ros.py` 196 | 197 | \* default dependency and target pkgs are defined at top of gen_ue_from_ros.py 198 | 199 | #### Copy source files to Unreal Project 200 | 1. cd UE_tools/CodeGen 201 | 2. python3 post_genrate.py --ue_proj_path 202 | 203 | e.g. 204 | 205 | `python3 post_generate.py --ue_proj_path /home/user/turtlebot3-UE` 206 | 207 | 208 | 209 | ### Scripts 210 | - **gen_ue_from_ros.py**: 211 | 212 | Generate .h and cpp files for UnrealEngine project from msg/srv/action definitions. Generated class/struct can be used from UnrealEngine C++ and Blueprint. 213 | 214 | All properties can be accessible from C++. Not all properties but only Blueprint compatible type member can be accessible from Blueprint. 215 | 216 | If your custom msg need other msg dependency, you need to specify that dependency from arg. 217 | 218 | \* Default dependencies and targets are defined near the top of files. 219 | 220 | 221 | \* Please check [limitations](#limitation) as well. 222 | 223 | - **post_generate.py**: 224 | 225 | Copy generated .h and cpp to target UnrealEngine project folder such as **turtlebot3-UE/Plugin/rclUE/Source/Thirdparty/ros**. 226 | 227 | \* Black list is defined near the beginning of the script which are not copied to Unreal project since there are [Limitations](#limitation) 228 | 229 | ### Limitation 230 | - only works with ROS2 message interface (in particular, ROS had built-in data types, such as `time`, defined in libraries and ROS2 now implements those as messages) 231 | - currently it has only been tested with messages used in RR projects 232 | - not all types are supported in UE4 Blueprint (e.g. `double`): `get_types_cpp` does the check, however it is currently checking against a list of unsupported types that have been encountered (and there's more that are not checked against, so if the code fails compilation due to this problem, the type in question should be currently be added to the list). The alternative, and better implementation, would check for supported types (but must be careful with various aliases, like `int` and `int32` 233 | - fixed size array comes with TArray<>. User should reserve array with proper size by themselves. 234 | 235 | ### Todo 236 | - use object oriented python 237 | - the script iterates multiple times over the files - this can be avoided if performance is a real issue (messages shouldn't change often however, so clarity should be prioritized over performance 238 | - add automated testing: minimum should be to include all of the generated files and try to compile 239 | - Use TStaticArray instead of TArray for fixed size array. 240 | 241 | ### Maintainer 242 | yu.okamoto@rapyuta-robotics.com 243 | -------------------------------------------------------------------------------- /CodeGen/gen_ue_from_ros.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021-2024 Rapyuta Robotics Co., Ltd. 2 | ''' 3 | Gen UE wrapper of ros cpp generated by `colcon build` from [.action, .msg, .src] 4 | ''' 5 | import struct 6 | from jinja2 import Environment, FileSystemLoader 7 | import sys 8 | import os 9 | import glob 10 | import re 11 | import argparse 12 | from pathlib import Path 13 | 14 | import logging 15 | 16 | 17 | logging.basicConfig( 18 | level=logging.INFO, 19 | handlers=[ 20 | logging.FileHandler("codegen_debug.log"), 21 | logging.StreamHandler() 22 | ] 23 | ) 24 | logger = logging.getLogger(__name__) 25 | 26 | # default dependency pkgs. Commonly used pkgs 27 | # https://github.com/ros2/common_interfaces + alpha 28 | # BASE_ROS_INSTALL_PATH = '/opt/ros/foxy/share' 29 | DEFAULT_DEPENDENCY_PKGS = { 30 | # 'action_msgs': '', 31 | 'actionlib_msgs': '', 32 | 'builtin_interfaces': '', 33 | # 'unique_identifier_msgs': '', 34 | # 'diagnostic_msgs': '', 35 | # 'rosgraph_msgs': '', 36 | # 'geometry_msgs': '', 37 | # 'nav_msgs': '', 38 | # 'sensor_msgs': '', 39 | # 'shape_msgs': '', 40 | 'std_msgs': '', 41 | # 'std_srvs': 'StdSrv', 42 | # 'stereo_msgs': '', 43 | # 'trajectory_msgs': '', 44 | # 'visualization_msgs': '', 45 | # 'tf2_msgs': '', 46 | # 'pcl_msgs': '', 47 | #'ackermann_msgs': '', 48 | # 'example_interfaces': '', 49 | # 'ue_msgs': '' 50 | } 51 | # DEFAULT_DEPENDENCY_PKGS = {os.path.join(BASE_ROS_INSTALL_PATH, k):v for (k, v) in DEFAULT_DEPENDENCY_PKGS.items()} 52 | 53 | # for shorten the class name to meet the UE limitation, maximum 32 charcters for class name 54 | DEFAULT_NAME_MAPPING = { 55 | 'Covariance': 'Cov', 56 | 'Accel': 'Acc', 57 | 'Vector': 'Vec', 58 | 'Quaternion': 'Quat', 59 | 'Trajectory': 'Traj', 60 | 'Transform': 'TF', 61 | 'Compressed': 'Comp', 62 | 'Error': 'Err', 63 | 'Diagnostics': 'Diag', 64 | 'MultiArray': 'MA', 65 | 'Dimension': 'Dim', 66 | 'With': '', 67 | 'Destination': 'Dest', 68 | 'Reservation': 'Reserv', 69 | 'Exception': 'Except', 70 | 'Config': 'Conf', 71 | 'InteractiveMarker': 'IM', 72 | 'Control': 'Ctrl', 73 | 'Request': 'Req', 74 | 'Response': 'Res', 75 | 'Message': 'Msg', 76 | 'String': 'Str', 77 | 'Image': 'Img', 78 | 'Odometry': 'Odom' 79 | } 80 | 81 | # constants 82 | UE_SPECIFIC_TYPES = [ 83 | 'geometry_msgs/Vector3', 84 | 'geometry_msgs/Point', 85 | # 'geometry_msgs/Point32', 86 | 'geometry_msgs/Quaternion', 87 | 'geometry_msgs/Transform' 88 | ] 89 | 90 | # ros build in types 91 | # https://docs.ros.org/en/foxy/Concepts/About-ROS-Interfaces.html 92 | ROS_BUILDIN_TYPES = [ 93 | 'bool', 94 | 'byte', 95 | 'char', 96 | 'float32', 97 | 'float64', 98 | 'int8', 99 | 'uint8', 100 | 'int16', 101 | 'uint16', 102 | 'int32', 103 | 'uint32', 104 | 'int64', 105 | 'uint64', 106 | 'string', 107 | 'wstring' 108 | ] 109 | 110 | # ros_type: ue_type 111 | TYPE_CONVERSION = { 112 | 'bool' : 'bool', 113 | 'int32': 'int', 114 | 'uint32': 'unsigned int', 115 | 'byte': 'uint8', 116 | 'char': 'uint8', 117 | 'float32': 'float', 118 | 'float64': 'double', 119 | 'string': 'FString', 120 | 'wstring': 'FString', 121 | 'geometry_msgs/Vector3':'FVector', 122 | 'geometry_msgs/Point': 'FVector', 123 | 'geometry_msgs/Quaternion': 'FQuat', 124 | 'geometry_msgs/Transform': 'FTransform' 125 | } 126 | 127 | # used in set UPROPERTY to allow BP access 128 | BP_UNSUPPORTED_TYPES = [ 129 | 'unsigned int', 130 | # 'double', 131 | 'int8', 132 | 'int16', 133 | 'uint16', 134 | 'uint64', 135 | 'std::u16string' 136 | ] 137 | 138 | # UE BP-supported types only? 139 | def convert_to_ue_type(t, pkgs_name_mapping, name_mapping): 140 | 141 | #temp 142 | t = re.sub(r'string<=\d+', 'string', t) 143 | 144 | size = 0 145 | if '[]' in t: 146 | t = t.replace('[]','') 147 | t = f'TArray<{convert_to_ue_type(t, pkgs_name_mapping, name_mapping)[0]}>' 148 | elif ('[' in t) and (']' in t): 149 | tmp = re.split(r'\[|\]', t) 150 | size = tmp[1].replace('<=','') 151 | t = f'TArray<{convert_to_ue_type(tmp[0], pkgs_name_mapping, name_mapping)[0]}>' 152 | elif t in TYPE_CONVERSION : 153 | t = TYPE_CONVERSION[t] 154 | elif t in ROS_BUILDIN_TYPES: 155 | pass 156 | else : #compound msg type 157 | t = 'FROS' + update_msg_name_full(t, pkgs_name_mapping, name_mapping) 158 | 159 | #elif t == 'geometry_msgs/Pose': -> {FRRDoubleVector, FQuat} 160 | #elif t == 'geometry_msgs/Twist': -> {FVector TwistLinear, FVector TwistAngular} 161 | 162 | return (t, size) 163 | 164 | def convert_to_ue_name(n, var_type): 165 | var_name = snake_to_pascal(n) 166 | if ('bool' == var_type): 167 | var_name = f'b{var_name}' 168 | 169 | return var_name 170 | 171 | # todo use dict same as type conversion 172 | def get_type_default_value_str(t): 173 | res = None 174 | if 'FVector' == t: 175 | res = 'FVector::ZeroVector' 176 | elif 'FQuat' == t: 177 | res = 'FQuat::Identity' 178 | elif 'FTransform' == t: 179 | res = 'FTransform::Identity' 180 | elif t.startswith('F'): 181 | res = None 182 | elif 'TArray' in t: 183 | res = None 184 | elif 'bool' == t: 185 | res = 'false' 186 | elif 'int' in t: 187 | res = '0' 188 | elif 'float' in t: 189 | res = '0.f' 190 | elif 'double' == t: 191 | res = '0.f' 192 | 193 | return res 194 | 195 | # change pkg name 196 | def update_msg_name_full(in_type, pkgs_name_mapping, name_mapping): 197 | type_name = in_type 198 | pkg_name = '' 199 | if '/' in type_name: 200 | texts = in_type.split('/') 201 | pkg_name = texts[0] 202 | type_name = texts[1] 203 | return update_msg_name(pkg_name, type_name, pkgs_name_mapping, name_mapping) 204 | 205 | # change prefix 206 | def update_msg_name(in_pkg, in_type, pkgs_name_mapping, name_mapping): 207 | # pkg_name update 208 | if in_pkg in pkgs_name_mapping: 209 | pkg_name = pkgs_name_mapping[in_pkg] 210 | else: 211 | pkg_name = snake_to_pascal(in_pkg) 212 | 213 | # type_name update 214 | type_name = snake_to_pascal(in_type) 215 | for k, v in name_mapping.items(): 216 | if k in type_name: 217 | type_name = type_name.replace(k, v) 218 | 219 | return pkg_name + type_name 220 | 221 | def snake_to_pascal(in_text): 222 | return ''.join([c[0].upper()+c[1:] for c in in_text.replace('_', ' ').replace('/', ' ').split()]) 223 | 224 | def camel_to_snake(s): 225 | arr = [] 226 | for i in range(len(s)): 227 | temp = s[i].lower() 228 | if s[i].isupper() and \ 229 | ( # add _ if before or after charactor is lowercase or digit 230 | (i > 0 and (s[i-1].islower() or s[i-1].isdigit())) or 231 | (i != len(s)-1 and (s[i+1].islower() or s[i+1].isdigit())) 232 | ): # add _ if before or after charactor is lowercase 233 | temp = '_' + temp 234 | arr.append(temp) 235 | 236 | return ''.join(arr).lstrip('_') 237 | 238 | def remove_underscore(in_text): 239 | return in_text.replace("_", "") 240 | 241 | # generate variable name for UE C++ 242 | # original_names: member names defined in .msg, .srv, .action 243 | # return [(type, size), var_name] 244 | def get_ue_var_name(original_names, pkgs_name_mapping, name_mapping): 245 | if len(original_names) == 2: 246 | return [ 247 | convert_to_ue_type(original_names[0], pkgs_name_mapping, name_mapping), 248 | convert_to_ue_name(original_names[1], original_names[0]) 249 | ] 250 | else: 251 | logger.error('ERROR with ' + str(original_names) + ' (get_ue_var_name)') 252 | return '','' 253 | 254 | # generate msg variable access for ROS msg 255 | # original_names: member names defined in .msg, .srv, .action 256 | def get_ros_var_name(original_names={}): 257 | if len(original_names) == 2: 258 | vartype = original_names[0] 259 | varname = original_names[1] 260 | if re.search(r'\[\d+\]', original_names[0]) is not None: 261 | varname += '[i]' 262 | elif '[]' in original_names[0] or '[<=' in original_names[0]: 263 | varname += '.data[i]' 264 | return [str(vartype), str(varname)] 265 | else: 266 | logger.error('ERROR with ' + str(original_names) + ' (get_ros_var_name)') 267 | return '','' 268 | 269 | 270 | def check_ros_deprecated(file_path): 271 | content = [] 272 | with open(file_path) as f: 273 | content = f.readlines() 274 | for line in content: 275 | if 'deprecated as of' in line: 276 | logger.info('{} is deprecated'.format(file_path)) 277 | return True 278 | return False 279 | 280 | # generate code for setter (SetFromROS2) 281 | def setter(r, v_ue, v_ros, size, ros_msg_type): 282 | ros_msg_type = re.sub(r'\[\d*\]', '', ros_msg_type) 283 | 284 | v_ros_str = 'in_ros_data.' + v_ros 285 | func_name_str = 'ROSToUE' 286 | 287 | if r.startswith('TArray'): 288 | r2 = r[len('TArray<'):-len('>')] 289 | 290 | func_name_str = 'Sequence' + func_name_str + 'Array' 291 | if size > 0: 292 | size_str = str(size) 293 | v_ros_str = v_ros_str.replace('[i]', '') 294 | else: 295 | size_str = v_ros_str.split('.data[i]', 1)[0] + '.size' 296 | v_ros_str = v_ros_str.replace('data[i]', 'data') 297 | 298 | body = '(' + v_ros_str + ', ' + v_ue + ', ' + size_str + ')' 299 | tail = '' 300 | if r2 in ['FVector', 'FString' ]: 301 | head = r2[1:] + func_name_str + '<' + ros_msg_type + '>' 302 | elif r2 in ['FQuat', 'FTransform']: 303 | head = r2[1:] + func_name_str 304 | else: # compound 305 | head = func_name_str + '<' + ros_msg_type + ', ' + r2 + '>' 306 | head = 'UROS2Utils::' + head 307 | 308 | else: 309 | head = v_ue 310 | body = ' = UROS2Utils::' + r[1:] + func_name_str 311 | tail = '(' + v_ros_str + ')' 312 | if r in ['FVector', 'FString' ]: 313 | body += '<' + ros_msg_type + '>' 314 | elif r in ['FQuat', 'FTransform']: 315 | pass 316 | elif r in ROS_BUILDIN_TYPES or r in TYPE_CONVERSION.values(): 317 | body = ' = ' 318 | tail = v_ros_str 319 | else: #compound type 320 | body = '.SetFromROS2' 321 | 322 | return head + body + tail + ';\n\n\t\t' 323 | 324 | 325 | def cpp2ros_vector(v_ros, v_ue, comp, is_array=False, is_fixed_size=False): 326 | iterator = '' 327 | iterator_ros = '' 328 | component = '' 329 | if comp != '': 330 | component = '.' + comp 331 | if is_array and is_fixed_size: 332 | # iterator_ros = '[i]' 333 | pass 334 | if is_array: 335 | iterator = '[i]' 336 | return 'out_ros_data.' + v_ros + iterator_ros + component.lower() + ' = ' + v_ue + iterator + component.upper() + ';' 337 | 338 | 339 | def free_and_malloc(v_ros, v_ue, type, Free=False): 340 | alloc_type = 'decltype(*out_ros_data.' + v_ros + '.data)' 341 | alloc_type_cast = 'decltype(out_ros_data.' + v_ros + '.data)' 342 | size = '(' + v_ue + '.Num())' 343 | # if type == 'FString': 344 | # size = '(strLength+1)' 345 | if type == 'FVector': 346 | size = '(' + v_ue + '.Num() * 3)' 347 | elif type == 'FQuat': 348 | size = '(' + v_ue + '.Num() * 4)' 349 | free_mem = '' 350 | if Free: 351 | free_mem = 'if (out_ros_data.' + v_ros + '.data != nullptr)\n\t\t{\n\t\t\t'\ 352 | + 'free(out_ros_data.' + v_ros + '.data);\n\t\t}\n\t\t' 353 | return free_mem \ 354 | + 'out_ros_data.' + v_ros + \ 355 | '.data = (' + alloc_type_cast + ')malloc(' + size + \ 356 | '*sizeof(' + alloc_type + '));\n\t\t' 357 | 358 | # generate code for getter_AoS - Array-of-Structures (SetROS2) 359 | def getter_AoS(r, v_ue, v_ros, size, ros_msg_type, ros_msg_sequence_type): 360 | ros_msg_type = re.sub(r'\[\d*\]', '', ros_msg_type) 361 | 362 | v_ros_str = 'out_ros_data.' + v_ros 363 | func_name_str = 'UEToROS' 364 | closing_str = ';\n\n\t\t' 365 | 366 | if r.startswith('TArray'): 367 | r2 = r[len('TArray<'):-len('>')] 368 | func_name_str = 'Array' + func_name_str + 'Sequence' 369 | head = 'UROS2Utils::' 370 | tail = '' 371 | if size > 0: 372 | size_str = str(size) 373 | v_ros_str = v_ros_str.replace('[i]', '') 374 | else: 375 | v_ros_size = v_ros_str.replace('.data[i]', '') 376 | size_str = v_ue + '.Num()' 377 | v_ros_str = v_ros_str.replace('data[i]', 'data') 378 | 379 | # ref https://github.com/ros2/rosidl_typesupport_opensplice/blob/master/rosidl_typesupport_opensplice_c/resource/msg__type_support_c.cpp.em 380 | head = 'if (' + v_ros_size + '.data) {' + '\n\t\t' + \ 381 | ros_msg_sequence_type + '__fini(&' + v_ros_size + ');' + '\n\t\t' + \ 382 | '}' + '\n\t\t' + \ 383 | 'if (!' + ros_msg_sequence_type + '__init(&' + v_ros_size + ', ' + size_str + ')) {' + \ 384 | 'UE_LOG_WITH_INFO(LogTemp, Error, TEXT(\"failed to create array for field ' + v_ros_size + ' \"));' + \ 385 | '}' + '\n\t\t' + head 386 | # head = ros_msg_sequence_type + '__fini(&' + v_ros_size + ');' + '\n\t\t' + \ 387 | # ros_msg_sequence_type + '__init(&' + v_ros_size + ', ' + size_str + ');' + \ 388 | # '\n\t\t' + head 389 | 390 | body = '(' + v_ue + ', ' + v_ros_str + ', ' + size_str + ')' 391 | if r2 in ['FVector']: 392 | head += r2[1:] + func_name_str + '<' + ros_msg_type + '>' 393 | elif r2 in ['FString']: 394 | t = r2[1:] 395 | if ros_msg_type == 'rosidl_runtime_c__U16String': 396 | t = 'U16String' 397 | head += t + func_name_str 398 | elif r2 in ['FQuat', 'FTransform']: 399 | head += r2[1:] + func_name_str 400 | else: # compound 401 | head += func_name_str + '<' + ros_msg_type + ', ' + r2 + '>' 402 | 403 | else: 404 | head = v_ros_str 405 | body = 'UROS2Utils::' + r[1:] + func_name_str 406 | tail = '(' + v_ue + ')' 407 | if r == 'FVector': 408 | body = ' = ' + body + '<' + ros_msg_type + '>' 409 | elif r == 'FString': 410 | head = '' 411 | t = r[1:] 412 | if ros_msg_type == 'rosidl_runtime_c__U16String': 413 | t = 'U16String' 414 | body = 'UROS2Utils::' + t + func_name_str 415 | tail = '(' + v_ue + ', ' + v_ros_str + ')' 416 | elif r in ['FQuat', 'FTransform']: 417 | body = ' = ' + body 418 | elif r in ROS_BUILDIN_TYPES or r in TYPE_CONVERSION.values(): 419 | body = ' = ' 420 | tail = v_ue 421 | else: #compound type 422 | head = v_ue 423 | tail = '(' + v_ros_str + ')' 424 | body = '.SetROS2' 425 | 426 | return head + body + tail + closing_str 427 | 428 | def free_and_malloc_SoA(v_ros, v_ue, type): 429 | alloc_type = 'decltype(*out_ros_data.' + v_ros + '.data)' 430 | alloc_type_cast = 'decltype(out_ros_data.' + v_ros + '.data)' 431 | size = '(' + v_ue + '.Num())' 432 | if type == 'FString': 433 | size = '(strLength+1)' 434 | elif type == 'FVector': 435 | size = '(' + v_ue + '.Num() * 3)' 436 | elif type == 'FQuat': 437 | size = '(' + v_ue + '.Num() * 4)' 438 | return 'if (out_ros_data.' + v_ros + '.data != nullptr)\n\t\t{\n\t\t\t' \ 439 | + 'free(out_ros_data.' + v_ros + '.data);\n\t\t}\n\t\t' \ 440 | + 'out_ros_data.' + v_ros + \ 441 | '.data = (' + alloc_type_cast + ')malloc(' + size + \ 442 | '*sizeof(' + alloc_type + '));\n\t\t' 443 | 444 | # generate code for getter_SoA - Structure-of-Arrays (SetROS2) 445 | def getter_SoA(r_array, v_ue_array, v_ros_array, size_array): 446 | # WARNING: there could be multiple groups of SoA - need to go by matching substrings in v_ros_array 447 | SoAs_ros = {} 448 | SoAs_types = {} 449 | SoAs_r = {} 450 | for e in range(len(v_ros_array)): 451 | if v_ros_array[e].split('.data[i].')[0] in SoAs_ros: 452 | SoAs_ros[v_ros_array[e].split( 453 | '.data[i].')[0]].append(v_ros_array[e]) 454 | SoAs_types[v_ros_array[e].split( 455 | '.data[i].')[0]].append(v_ue_array[e]) 456 | SoAs_r[v_ros_array[e].split('.data[i].')[0]].append(r_array[e]) 457 | else: 458 | SoAs_ros[v_ros_array[e].split('.data[i].')[0]] = [v_ros_array[e]] 459 | SoAs_types[v_ros_array[e].split('.data[i].')[0]] = [v_ue_array[e]] 460 | SoAs_r[v_ros_array[e].split('.data[i].')[0]] = [r_array[e]] 461 | 462 | malloc_size = {} 463 | for t in SoAs_types: 464 | if t not in malloc_size: 465 | malloc_size[t] = '' 466 | malloc_size[t] += SoAs_types[t][0] + '.Num() * ' 467 | malloc_size[t] += '(' 468 | for e in SoAs_types[t]: 469 | malloc_size[t] += 'sizeof(' + e + ') + ' 470 | malloc_size[t] = malloc_size[t][:-3] 471 | malloc_size[t] += ')' 472 | 473 | getter_SoA_result = '' 474 | for t in SoAs_types: 475 | # free_and_malloc 476 | getter_SoA_result += 'if (out_ros_data.' + t + '.data != nullptr)\n\t\t{\n\t\t\t' \ 477 | + 'free(out_ros_data.' + t + '.data);\n\t\t}\n\t\t' \ 478 | + 'out_ros_data.' + t + '.data = (decltype(out_ros_data.' + t + '.data))malloc(' + malloc_size[t] + ');\n\t\t' \ 479 | + 'out_ros_data.' + t + '.size = ' + SoAs_types[t][0] + '.Num();\n\t\t' \ 480 | + 'out_ros_data.' + t + '.capacity = ' + \ 481 | SoAs_types[t][0] + '.Num();\n\t\t' 482 | # fill 483 | getter_SoA_result += 'for (auto i = 0; i < ' + \ 484 | SoAs_types[t][0] + '.Num(); ++i)\n\t\t{\n\t\t\t' 485 | for i in range(len(SoAs_types[t])): 486 | v_ue = SoAs_types[t][i] 487 | v_ros = SoAs_ros[t][i] 488 | r = SoAs_r[t][i] 489 | if 'TArray' in r: 490 | r = r.split('<', 1)[1].split('>')[0] 491 | if r == 'FVector': 492 | getter_SoA_result += cpp2ros_vector(v_ros, v_ue, 'x', True, False) + '\n\t\t\t' \ 493 | + cpp2ros_vector(v_ros, v_ue, 'y', True, False) + '\n\t\t\t' \ 494 | + cpp2ros_vector(v_ros, v_ue, 'z', 495 | True, False) + '\n\n\t\t\t' 496 | elif r == 'FQuat': 497 | getter_SoA_result += cpp2ros_vector(v_ros, v_ue, 'x', True, False) + '\n\t\t\t' \ 498 | + cpp2ros_vector(v_ros, v_ue, 'y', True, False) + '\n\t\t\t' \ 499 | + cpp2ros_vector(v_ros, v_ue, 'z', True, False) + '\n\t\t\t' \ 500 | + cpp2ros_vector(v_ros, v_ue, 'w', 501 | True, False) + '\n\n\t\t\t' 502 | elif r == 'FString': 503 | getter_SoA_result += '{\n\t\t\t\t' \ 504 | + 'FTCHARToUTF8 strUtf8( *' + v_ue + '[i] );\n\t\t\tint32 strLength = strUtf8.Length();\n\t\t\t\t' \ 505 | + free_and_malloc(v_ros, v_ue, r, False) \ 506 | + 'memcpy(out_ros_data.' + v_ros + '.data, TCHAR_TO_UTF8(*' + v_ue + '[i]), (strLength+1)*sizeof(char));\n\t\t\t\t' \ 507 | + 'out_ros_data.' + v_ros + '.size = strLength;\n\t\t\t\t' \ 508 | + 'out_ros_data.' + v_ros + '.capacity = strLength + 1;\n\t\t\t' \ 509 | + '}\n\n\t\t\t' 510 | elif 'TArray' in r: 511 | getter_SoA_result += '\t\t\tUE_LOG(LogTemp, Error, TEXT("Not Implemented Yet!"));\n\n' 512 | else: 513 | getter_SoA_result += 'out_ros_data.' + \ 514 | v_ros + ' = ' + v_ue + '[i];\n\n\t\t\t' 515 | getter_SoA_result += '}\n\t' 516 | 517 | return getter_SoA_result 518 | 519 | # extract type name from array type 520 | def msg_cleanup(type_ros): 521 | r = type_ros.replace('<=','') 522 | r = re.sub(r'\[\d*\]', '', r) 523 | return r 524 | 525 | # return msg, srv or action 526 | def get_msg_type(type_ros, types_dict): 527 | msg_type = None 528 | r = msg_cleanup(type_ros) 529 | for t in types_dict: 530 | if r in types_dict[t]: 531 | msg_type = t 532 | break 533 | if msg_type is None: 534 | logger.error('get headers: can\'t find include target for ros:{}'.format(r)) 535 | 536 | return msg_type 537 | 538 | def get_headers(type_ue, type_ros, types_dict): 539 | res = '' 540 | u = type_ue 541 | if u.startswith('TArray'): 542 | u = type_ue[len('TArray<'):-len('>')] 543 | 544 | r = msg_cleanup(type_ros) 545 | if r not in ROS_BUILDIN_TYPES and r not in UE_SPECIFIC_TYPES: 546 | msg_type = get_msg_type(type_ros, types_dict) 547 | file_name = 'ROS2' + u[4:] + '.h' #len(FROS2)=4 548 | dir_name = str.capitalize(msg_type) + 's' 549 | res = '#include \"' + os.path.join(dir_name, file_name) + '\"\n' 550 | 551 | # headers for sequences 552 | if type_ue.startswith('TArray'): 553 | res += '#include \"' 554 | if '/' in type_ros: 555 | msg_type = get_msg_type(type_ros, types_dict) 556 | r = ('__' + msg_type + '__').join(r.split('/')) 557 | seq_type_arr = [camel_to_snake(v) for v in r.split('__')] 558 | seq_type_arr.insert(2, 'detail') 559 | res += '/'.join(seq_type_arr) + '__functions.h' 560 | else: 561 | if r == 'string': 562 | res += 'rosidl_runtime_c/string_functions.h' 563 | elif r == 'wstring': 564 | res += 'rosidl_runtime_c/u16string_functions.h' 565 | else: 566 | res += 'rosidl_runtime_c/primitives_sequence_functions.h' 567 | 568 | res += '\"\n' 569 | 570 | return res 571 | 572 | # input: k=struct_name, v=[type, name, value] 573 | # return: definition_str, getter_str 574 | def get_constants(k, v): 575 | type_name = convert_to_ue_type(v[0], {}, {})[0] 576 | struct_name = 'FROS' + k + '::' 577 | constant_name = v[2].strip() 578 | cdef = '' 579 | if type_name == 'FString': 580 | head = 'TEXT(' 581 | tail = ')' 582 | if not constant_name.startswith("\""): 583 | head += "\"" 584 | tail = "\"" + tail 585 | constant_name = head + constant_name + tail 586 | cdec = '\tstatic const FString ' + v[1] + ';\n' 587 | cdef = '\tconst FString ' + struct_name + v[1] + ' = ' + constant_name + ';\n' 588 | else: 589 | cdec = '\tstatic constexpr ' + type_name + ' ' + v[1] + ' = ' + constant_name + ';\n' 590 | 591 | # UFUNCTION(BlueprintCallable) 592 | # static int sample() 593 | # { 594 | # return FROSTFStamped::sample; 595 | # } 596 | cgetter = '' 597 | if type_name not in BP_UNSUPPORTED_TYPES: 598 | cgetter += 'UFUNCTION(BlueprintCallable)\n\t' 599 | cgetter += \ 600 | 'static ' + type_name + ' CONST_' + v[1] + '()\n\t' + \ 601 | '{\n\t\t' + \ 602 | 'return FROS' + k + '::' + v[1] + ';\n\t' + \ 603 | '}\n\t' 604 | return cdec, cgetter, cdef 605 | 606 | def get_constructors(r, v_ue, size): 607 | 608 | # Initialize fixed size array 609 | if size <= 0: 610 | return '' 611 | 612 | if not r.startswith('TArray'): 613 | return '' 614 | 615 | return v_ue + '.SetNumZeroed(' + str(size) + ');\n\n\t\t' 616 | 617 | # scan msg, srv and action files to find all types present in the given target_paths 618 | def get_types(target_paths): 619 | logger.debug('\n\n*************************** \n get types \n***************************') 620 | types = set(ROS_BUILDIN_TYPES) 621 | for target_path in target_paths: 622 | for subdir, dirs, files in os.walk(target_path): 623 | files = [fi for fi in files if fi.endswith( 624 | ('.msg', '.srv', '.action'))] 625 | # compose filename with path 626 | filepaths = [os.path.join(subdir, filename) for filename in files] 627 | for fp in filepaths: 628 | logger.debug('get types: get type from {}'.format(fp)) 629 | content = [] 630 | # load all data of the file into content, line by line 631 | with open(fp) as f: 632 | content = f.readlines() 633 | 634 | # remove leading and trailing spaces, including new lines '\n' 635 | content = [x.strip() for x in content] 636 | 637 | # ignore empty lines '' 638 | content = [c for c in content if c != ''] 639 | 640 | # ignore comment lines '#' 641 | content = [c for c in content if not c.startswith('#')] 642 | 643 | # ignore separator lines '---' 644 | content = [c for c in content if c != '---'] 645 | 646 | # Get type 647 | content = [c.split()[0] for c in content] 648 | 649 | # remove array sizes 650 | content = [re.sub(r'<=', '', c) for c in content] 651 | 652 | # remove array markers '[]' 653 | content = [re.sub(r'\[\d*\]', '', c) for c in content] 654 | 655 | 656 | # get pkg name 657 | el = fp.split('/') 658 | pkg_name = el[len(el)-3] + '/' 659 | 660 | # include complex types (self) 661 | for c in content: 662 | if c not in types: 663 | # make full type name 664 | type_name = c 665 | if type_name not in ROS_BUILDIN_TYPES and '/' not in type_name: 666 | type_name = pkg_name + type_name 667 | logger.debug('get types: add types: {}'.format(type_name)) 668 | types.add(type_name) 669 | 670 | el = fp.split('/') 671 | struct_type = pkg_name + remove_underscore(os.path.splitext(os.path.basename(fp))[0]) 672 | if struct_type not in types: 673 | logger.debug('get types: add types: {}'.format(struct_type)) 674 | types.add(struct_type) 675 | 676 | # logger.debug('get types: {}'.format(types)) 677 | return types 678 | 679 | 680 | # create a dictionary matching types with the corresponding expanded contents expressed with basic types 681 | def get_types_dict(target_paths): 682 | types_dict = {'msg': {}, 'srv': {}, 'action': {}} 683 | types = get_types(target_paths) 684 | logger.debug('\n\n*************************** \n get_types_dict \n***************************') 685 | 686 | def print_duplication_error(in_text): 687 | logger.error('Type name ' + in_text + ' is duplicated. Please check definition overlapping.') 688 | sys.exit(1) 689 | 690 | # for every folder to scan 691 | for target_path in target_paths: 692 | logger.info('get_types_dict: target path: {}'.format(target_path)) 693 | pkg_name = os.path.basename(target_path) + '/' 694 | 695 | # iterate all subfolders 696 | for subdir, dirs, files in os.walk(target_path): 697 | # iterate over all msg, srv and action files 698 | files = [fi for fi in files if fi.endswith(('.msg', '.srv', '.action'))] 699 | for fi in files: 700 | 701 | logger.debug('get_types_dict: target file {}'.format(fi)) 702 | content = [] 703 | 704 | # if the file corresponds to the type t being processed 705 | fi_name = os.path.splitext(fi)[0] 706 | t = pkg_name + remove_underscore(fi_name) 707 | tRequest = f'{t}Req' 708 | tResponse = f'{t}Res' 709 | tSendGoal = f'{t}SGReq' 710 | tGetResult = f'{t}GRRes' 711 | tFeedback = f'{t}FB' 712 | 713 | # if struct_type == t: 714 | # logger.debug('get_types_dict: type and file matches. filename: {}'.format(t)) 715 | 716 | # load all data of the file into content, line by line 717 | with open(os.path.join(subdir, fi)) as f: 718 | content = f.readlines() 719 | 720 | # remove leading and trailing spaces, including new lines '\n' 721 | content = [x.strip() for x in content] 722 | 723 | content = [ 724 | c for c in content if not c.startswith('#') and c != ''] 725 | content = [c.split('#')[0] for c in content] 726 | 727 | constants = [c for c in content if '=' in c and ('<=' not in c)] 728 | 729 | # remove constants - these will eventually need to be used 730 | content = [c for c in content if '=' not in c or ('<=' in c)] 731 | 732 | # sprit type and name 733 | content = [c.split()[0:2] if c != '---' else c for c in content ] 734 | 735 | 736 | # make full type name 737 | for c in content: 738 | if c == '---': 739 | continue 740 | 741 | # temp remove string length limit 742 | c[0] = re.sub(r'string<=\d+', 'string', c[0]) 743 | 744 | temp_name = re.sub(r'<=', '', c[0]) 745 | temp_name = re.sub(r'\[\d*\]', '', temp_name) 746 | if temp_name not in ROS_BUILDIN_TYPES and '/' not in temp_name: 747 | c[0] = pkg_name + c[0] 748 | 749 | # parse constants to [type name value] 750 | for c in constants: 751 | c = c.split() # split type, name and value 752 | const_v= c[1:] # [typename=value] or [typename, =, value] 753 | const_v = ''.join(const_v).split('=') # [typename, value] 754 | c = [c[0], const_v[0], const_v[1]] #[typename, name, value] 755 | content.append(c) 756 | 757 | if fi.endswith('.msg'): 758 | # remove comments, empty and separator lines; keep only variable type and name 759 | # content = [ c for c in content if '=' not in c[1] ] # ignore constants 760 | if t in types_dict['msg']: 761 | print_duplication_error(t) 762 | types_dict['msg'][t] = content 763 | logger.debug('get_types_dict: msg: parsed result: {}: {}'.format(t, content)) 764 | elif fi.endswith('.srv'): 765 | counter = 0 766 | logger.debug('get_types_dict: srv: parsed result') 767 | 768 | if tRequest in types_dict['srv']: 769 | print_duplication_error(tRequest) 770 | if tResponse in types_dict['srv']: 771 | print_duplication_error(tResponse) 772 | 773 | for c in content: 774 | if not c == '---': 775 | if counter == 0: 776 | if (tRequest in types_dict['srv']): # and (c not in types_dict['srv'][tRequest]): 777 | types_dict['srv'][tRequest].append(c) 778 | else: 779 | types_dict['srv'][tRequest] = [c] 780 | logger.debug('get_types_dict: srv: parsed request: {}'.format(c)) 781 | elif counter == 1: 782 | if (tResponse in types_dict['srv']): # and (c not in types_dict['srv'][tResponse]): 783 | types_dict['srv'][tResponse].append(c) 784 | else: 785 | types_dict['srv'][tResponse] = [c] 786 | logger.debug('get_types_dict: srv: parsed response: {}'.format(c)) 787 | else: 788 | counter += 1 789 | elif fi.endswith('.action'): 790 | # remove comments and empty lines; keep only variable type and name 791 | counter = 0 792 | logger.debug('get_types_dict: action: parsed result') 793 | 794 | if tSendGoal in types_dict['action']: 795 | print_duplication_error(tSendGoal) 796 | if tGetResult in types_dict['action']: 797 | print_duplication_error(tGetResult) 798 | if tFeedback in types_dict['action']: 799 | print_duplication_error(tFeedback) 800 | 801 | for c in content: 802 | if not c == '---': 803 | if '=' not in c[1]: 804 | if counter == 0: 805 | if (tSendGoal in types_dict['action']): # and (c not in types_dict['action'][tSendGoal]): 806 | types_dict['action'][tSendGoal].append(c) 807 | else: 808 | types_dict['action'][tSendGoal] = [c] 809 | logger.debug('get_types_dict: action send goal: parsed goal: {}'.format(c)) 810 | elif counter == 1: 811 | if (tGetResult in types_dict['action']): # and (c not in types_dict['action'][tGetResult]): 812 | types_dict['action'][tGetResult].append(c) 813 | else: 814 | types_dict['action'][tGetResult] = [c] 815 | logger.debug('get_types_dict: action get result: parsed result: {}'.format(c)) 816 | elif counter == 2: 817 | if (tFeedback in types_dict['action']): # and (c not in types_dict['action'][tFeedback]): 818 | types_dict['action'][tFeedback].append(c) 819 | else: 820 | types_dict['action'][tFeedback] = [c] 821 | logger.debug('get_types_dict: action feedback: parsed feedback: {}'.format(c)) 822 | else: 823 | counter += 1 824 | logger.debug('\n') 825 | 826 | # remove complex types that have a corresponding UE type 827 | # for ue_type in UE_SPECIFIC_TYPES: 828 | # types_dict.pop(ue_type, None) 829 | 830 | # traverse and add complex type breakdown 831 | # for key, value in types_dict.items(): 832 | # for index, c in enumerate(value): 833 | # print(index, c) 834 | # v = c[0].replace('[]', '') 835 | # if v in types_dict: 836 | # value[index] = [c[0], c[1], types_dict[v]] 837 | for type_name in types_dict: 838 | for key, value in types_dict[type_name].items(): 839 | logger.debug('get_types_dict: ' + str(key) + ' -> ' + str(value)) 840 | 841 | return types_dict 842 | 843 | 844 | # generate UE & ROS C++ code snippets to be inserted in their respective placeholders in the templates 845 | # element 0: type 846 | # element 1: set from ros2 847 | # element 2: set ros2 848 | def get_types_cpp(target_paths, pkgs_name_mapping, name_mapping): 849 | types_dict = get_types_dict(target_paths) 850 | types_cpp = {} 851 | 852 | logger.debug('\n\n*************************** \n get_types_cpp \n***************************') 853 | for type_name in types_dict: 854 | for key, value in types_dict[type_name].items(): 855 | cpp_type = '' 856 | set_ros2 = '' 857 | set_from_ros2 = '' 858 | constructor = '' 859 | headers = '' 860 | constants = {'dec': '', 'getter': '', 'def': ''} 861 | logger.debug('get_types_cpp: parse input {} {}'.format(key, value)) 862 | new_key = update_msg_name_full(key, pkgs_name_mapping, name_mapping) 863 | for v in value: 864 | logger.debug("input: {}".format(v)) 865 | 866 | # parse constants 867 | if len(v) >= 3: 868 | logger.debug("parse constant: {}".format(v)) 869 | cdec, cgetter, cdef = get_constants(new_key, v) 870 | constants['dec'] += cdec 871 | constants['getter'] += cgetter 872 | constants['def'] += cdef 873 | continue 874 | 875 | res_ue = get_ue_var_name(v, pkgs_name_mapping, name_mapping) 876 | logger.debug("res_ue: {}".format(res_ue)) 877 | res_ros = get_ros_var_name(v) 878 | logger.debug("res_ros: {}".format(res_ros)) 879 | 880 | t_ue_array = [] 881 | v_ue_array = [] 882 | v_ros_array = [] 883 | size_array = [] 884 | 885 | it_ros = iter(res_ros) 886 | 887 | 888 | for i in range(0, len(res_ue), 2): 889 | t_ue = res_ue[i] 890 | v_ue = res_ue[i + 1] 891 | type_ros = res_ros[i] 892 | 893 | # used as input to template 894 | ros_msg_type = msg_cleanup(type_ros) 895 | ros_msg_sequence_type = '' 896 | if '/' in type_ros: 897 | msg_type = get_msg_type(type_ros, types_dict) 898 | ros_msg_type = ('__' + msg_type + '__').join(ros_msg_type.split('/')) 899 | ros_msg_sequence_type = ros_msg_type + '__Sequence' 900 | else: 901 | if ros_msg_type == 'string': 902 | ros_msg_type = 'rosidl_runtime_c__String' 903 | ros_msg_sequence_type = ros_msg_type + '__Sequence' 904 | elif ros_msg_type == 'wstring': 905 | ros_msg_type = 'rosidl_runtime_c__U16String' 906 | ros_msg_sequence_type = ros_msg_type + '__Sequence' 907 | else: 908 | ros_msg_sequence_type = 'rosidl_runtime_c__' + ros_msg_type + '__Sequence' 909 | if ros_msg_type in ['int64', 'uint64']: #why this is required? 910 | ros_msg_type += '_t' 911 | elif ros_msg_type in TYPE_CONVERSION.keys(): 912 | ros_msg_type = TYPE_CONVERSION[ros_msg_type] 913 | 914 | type_ue = t_ue[0] 915 | size = int(t_ue[1]) 916 | 917 | # ros_type 918 | next(it_ros) 919 | # ros_var 920 | v_ros = next(it_ros) 921 | 922 | # BP does not support these types 923 | dft_val = get_type_default_value_str(type_ue) 924 | var_initialization = f' = {dft_val}' if (dft_val != None) else '' 925 | var_declaration = f'{type_ue} {v_ue}{var_initialization};\n\n\t' 926 | 927 | # check bp_supported or not 928 | bp_supported = True 929 | if type_ue in BP_UNSUPPORTED_TYPES: 930 | bp_supported = False 931 | elif 'TArray' in type_ue: 932 | for msg_type in BP_UNSUPPORTED_TYPES: 933 | if msg_type in type_ue: 934 | bp_supported = False 935 | break 936 | if bp_supported: 937 | cpp_type += 'UPROPERTY(EditAnywhere, BlueprintReadWrite)\n\t' + var_declaration 938 | else: 939 | cpp_type += 'UPROPERTY(EditAnywhere)\n\t' + var_declaration 940 | 941 | # Headers 942 | headers += get_headers(type_ue, type_ros, types_dict) 943 | 944 | # Constructors 945 | constructor += get_constructors(type_ue, v_ue, size) 946 | 947 | # SetFromROS 948 | set_from_ros2 += setter(type_ue, v_ue, v_ros, size, ros_msg_type) 949 | 950 | # SetROS2 951 | if '.data[i].' not in v_ros: 952 | set_ros2 += getter_AoS(type_ue, v_ue, v_ros, size, ros_msg_type, ros_msg_sequence_type) 953 | else: 954 | t_ue_array.append(type_ue) 955 | v_ue_array.append(v_ue) 956 | v_ros_array.append(v_ros) 957 | size_array.append(size) 958 | 959 | # if any('.data[i].' in vr for vr in v_ros_array) and any('fields_' in vt for vt in v_ue_array) and not any('_fields_' in vt for vt in v_ue_array): 960 | # print('getter with:\n' + str(t_ue_array) + '\n' + str(v_ue_array) + '\n' + str(v_ros_array) + '\n' + str(size_array)) 961 | 962 | # this may not happen any more? 963 | if len(t_ue_array) > 0: 964 | set_ros2 += getter_SoA(t_ue_array, v_ue_array, 965 | v_ros_array, size_array) 966 | 967 | logger.debug('get_types_cpp: parsed result {} {}'.format(new_key, [cpp_type, set_from_ros2, set_ros2, constructor, headers, constants])) 968 | types_cpp[new_key] = [cpp_type, set_from_ros2, set_ros2, constructor, headers, constants] 969 | 970 | # for key, value in types_cpp.items(): 971 | # print(str(key) + ' -> ' + str(value[2])) 972 | # print(str(key) + ' -> ' + str(value[0])) 973 | 974 | return types_cpp 975 | 976 | ############################################ 977 | # main process 978 | ############################################ 979 | 980 | def codegen(module, dependency, target, name_mapping, ros_ws = os.path.join(os.getcwd(), '../ros2_ws'), ignore_deprecated_msg = True): 981 | file_loader = FileSystemLoader('templates') 982 | env = Environment(loader=file_loader) 983 | current_dir = os.getcwd() 984 | 985 | # print(module, dependency, target, name_mapping) 986 | # sys.exit(1) 987 | 988 | # cleanup 989 | os.system('rm -r Msgs Srvs Actions') 990 | os.system('mkdir Msgs Srvs Actions') 991 | 992 | module_api = module.upper() + '_API' 993 | ros_install_path = os.path.join(ros_ws, 'install') 994 | ros_paths = [os.path.join(ros_install_path, pkg) for pkg in dependency.keys()] 995 | groups = [] 996 | 997 | # defautl is same as dependency 998 | if target is None: 999 | ue_paths = ros_paths 1000 | else: 1001 | ue_paths = [os.path.join(ros_install_path, pkg) for pkg in target.keys()] 1002 | 1003 | for path in dependency: 1004 | path = path.rstrip('/') #removing trailing slash 1005 | if path not in ros_paths: 1006 | ros_paths.append(path) 1007 | 1008 | for path in ue_paths: 1009 | path = path.rstrip('/') #removing trailing slash 1010 | if path not in ros_paths: 1011 | ros_paths.append(path) 1012 | 1013 | for path in ue_paths: 1014 | groups.append(os.path.basename(path)) 1015 | 1016 | logger.debug('ros_paths: {}'.format(ros_paths)) 1017 | logger.debug('ue_paths: {}'.format(ue_paths)) 1018 | logger.debug('groups: {}'.format(groups)) 1019 | 1020 | pkgs_name_mapping = {**dependency, **target} 1021 | types_cpp = get_types_cpp(ros_paths, pkgs_name_mapping, name_mapping) 1022 | def is_valid_group_name(in_group_name): 1023 | return ((in_group_name in types_cpp) and len(types_cpp[in_group_name]) >= 3) 1024 | 1025 | def print_group_name_info(in_group_name): 1026 | logger.warning(f"Error in parsing msg or msg is empty. types_cpp[{in_group_name}] \ 1027 | size:{len(types_cpp[in_group_name]) if (in_group_name in types_cpp) else 0}") 1028 | 1029 | # generate code 1030 | SUB_DIRS =[*['action', 'srv', 'msg']] 1031 | for p in range(len(ue_paths)): 1032 | subdir_path = f'{ue_paths[p]}' 1033 | pkg_name = os.path.basename(subdir_path) 1034 | if not os.path.exists(subdir_path): 1035 | continue 1036 | logger.debug('[ROS2 PKG SHARE PATH]: {}'.format(subdir_path)) 1037 | 1038 | for subdir in SUB_DIRS: 1039 | file_type = os.path.dirname(subdir) if r'\/' in subdir else subdir 1040 | logger.info('INPUT ROS2 DIR:' + subdir_path + ' -> Output DIR:' + current_dir) 1041 | for file_path in Path(subdir_path).rglob(f'*.{file_type}'): 1042 | 1043 | name = Path(file_path).stem 1044 | # Not accept ROS file name having '_' 1045 | if('_' in name): 1046 | continue 1047 | ue_name = update_msg_name(pkg_name, name, pkgs_name_mapping, name_mapping) 1048 | print(ue_name) 1049 | name_cap = snake_to_pascal(name) 1050 | 1051 | logger.debug(' INPUT ROS2 FILE:' + os.path.basename(file_path)) 1052 | if ignore_deprecated_msg and check_ros_deprecated(file_path): 1053 | continue 1054 | 1055 | info = {} 1056 | file_path_parts = file_path.parts 1057 | info['Filename'] = os.path.join(*list(file_path_parts[len(file_path_parts)-3:len(file_path_parts)])) #get last 3 parts of msg such as nav_msgs/msg/Odometry.msg 1058 | info['Group'] = groups[p] 1059 | 1060 | # PascalCase to snake_case; correctly handles acronyms: TLA -> tla instead of t_l_a 1061 | name_lower = re.sub( 1062 | r'([a-z0-9])([A-Z])', r'\1_\2', re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name)).lower() 1063 | info['Name'] = name_lower 1064 | info['NameCap'] = name_cap 1065 | info['UEName'] = ue_name 1066 | info['ModuleAPI'] = module_api 1067 | 1068 | # p_group_name = f"{groups[p]}/{name}" 1069 | # group_name = p_group_name 1070 | p_group_name = ue_name 1071 | if file_type == 'msg': 1072 | group_name = p_group_name 1073 | if(is_valid_group_name(group_name)): 1074 | info['Types'] = types_cpp[group_name][0] 1075 | info['SetFromROS2'] = types_cpp[group_name][1] 1076 | info['SetROS2'] = types_cpp[group_name][2] 1077 | info['Constructor'] = types_cpp[group_name][3] 1078 | info['Headers'] = types_cpp[group_name][4] 1079 | info['ConstantsDec'] = types_cpp[group_name][5]['dec'] 1080 | info['ConstantsGetter'] = types_cpp[group_name][5]['getter'] 1081 | info['ConstantsDef'] = types_cpp[group_name][5]['def'] 1082 | else: 1083 | print_group_name_info(group_name) 1084 | 1085 | elif file_type == 'srv': 1086 | group_name = f"{p_group_name}Req" 1087 | if(is_valid_group_name(group_name)): 1088 | info['ReqTypes'] = types_cpp[group_name][0] 1089 | info['ReqSetFromROS2'] = types_cpp[group_name][1] 1090 | info['ReqSetROS2'] = types_cpp[group_name][2] 1091 | info['ReqConstructor'] = types_cpp[group_name][3] 1092 | info['ReqHeaders'] = types_cpp[group_name][4] 1093 | info['ReqConstantsDec'] = types_cpp[group_name][5]['dec'] 1094 | info['ReqConstantsGetter'] = types_cpp[group_name][5]['getter'] 1095 | info['ReqConstantsDef'] = types_cpp[group_name][5]['def'] 1096 | else: 1097 | print_group_name_info(group_name) 1098 | 1099 | group_name = f"{p_group_name}Res" 1100 | if(is_valid_group_name(group_name)): 1101 | info['ResTypes'] = types_cpp[group_name][0] 1102 | info['ResSetFromROS2'] = types_cpp[group_name][1] 1103 | info['ResSetROS2'] = types_cpp[group_name][2] 1104 | info['ResConstructor'] = types_cpp[group_name][3] 1105 | info['ResHeaders'] = types_cpp[group_name][4] 1106 | info['ResConstantsDec'] = types_cpp[group_name][5]['dec'] 1107 | info['ResConstantsGetter'] = types_cpp[group_name][5]['getter'] 1108 | info['ResConstantsDef'] = types_cpp[group_name][5]['def'] 1109 | else: 1110 | print_group_name_info(group_name) 1111 | 1112 | elif file_type == 'action': 1113 | group_name = f"{p_group_name}SGReq" 1114 | if(is_valid_group_name(group_name)): 1115 | info['GoalTypes'] = types_cpp[group_name][0] 1116 | info['GoalSetFromROS2'] = types_cpp[group_name][1].replace( 1117 | 'in_ros_data.', 'in_ros_data.goal.') 1118 | info['GoalSetROS2'] = types_cpp[group_name][2].replace( 1119 | 'out_ros_data.', 'out_ros_data.goal.') 1120 | info['GoalConstructor'] = types_cpp[group_name][3] 1121 | info['GoalHeaders'] = types_cpp[group_name][4] 1122 | info['GoalConstantsDec'] = types_cpp[group_name][5]['dec'] 1123 | info['GoalConstantsGetter'] = types_cpp[group_name][5]['getter'] 1124 | info['GoalConstantsDef'] = types_cpp[group_name][5]['def'] 1125 | else: 1126 | print_group_name_info(group_name) 1127 | 1128 | group_name = f"{p_group_name}GRRes" 1129 | if(is_valid_group_name(group_name)): 1130 | info['ResultTypes'] = types_cpp[group_name][0] 1131 | info['ResultSetFromROS2'] = types_cpp[group_name][1].replace( 1132 | 'in_ros_data.', 'in_ros_data.result.') 1133 | info['ResultSetROS2'] = types_cpp[group_name][2].replace( 1134 | 'out_ros_data.', 'out_ros_data.result.') 1135 | info['ResultConstructor'] = types_cpp[group_name][3] 1136 | info['ResultHeaders'] = types_cpp[group_name][4] 1137 | info['ResultConstantsDec'] = types_cpp[group_name][5]['dec'] 1138 | info['ResultConstantsGetter'] = types_cpp[group_name][5]['getter'] 1139 | info['ResultConstantsDef'] = types_cpp[group_name][5]['def'] 1140 | else: 1141 | print_group_name_info(group_name) 1142 | 1143 | group_name = f"{p_group_name}FB" 1144 | if(is_valid_group_name(group_name)): 1145 | info['FeedbackTypes'] = types_cpp[group_name][0] 1146 | info['FeedbackSetFromROS2'] = types_cpp[group_name][1].replace( 1147 | 'in_ros_data.', 'in_ros_data.feedback.') 1148 | info['FeedbackSetROS2'] = types_cpp[group_name][2].replace( 1149 | 'out_ros_data.', 'out_ros_data.feedback.') 1150 | info['FeedbackConstructor'] = types_cpp[group_name][3] 1151 | info['FeedbackHeaders'] = types_cpp[group_name][4] 1152 | info['FeedbackConstantsDec'] = types_cpp[group_name][5]['dec'] 1153 | info['FeedbackConstantsGetter'] = types_cpp[group_name][5]['getter'] 1154 | info['FeedbackConstantsDef'] = types_cpp[group_name][5]['def'] 1155 | else: 1156 | print_group_name_info(group_name) 1157 | 1158 | os.chdir(current_dir) 1159 | 1160 | output_h = '' 1161 | output_cpp = '' 1162 | if file_type == 'msg': 1163 | output_h = env.get_template('Msg.h').render(data=info) 1164 | output_cpp = env.get_template( 1165 | 'Msg.cpp').render(data=info) 1166 | elif file_type == 'srv': 1167 | output_h = env.get_template('Srv.h').render(data=info) 1168 | output_cpp = env.get_template( 1169 | 'Srv.cpp').render(data=info) 1170 | elif file_type == 'action': 1171 | output_h = env.get_template( 1172 | 'Action.h').render(data=info) 1173 | output_cpp = env.get_template( 1174 | 'Action.cpp').render(data=info) 1175 | else: 1176 | logger.error('type not found: {}'.format(subdir)) 1177 | 1178 | # this should only happen if the file does not exist 1179 | output_filename = f'{current_dir}/{file_type.capitalize()}s/ROS2{ue_name}' 1180 | logger.debug(' OUTPUT UE FILE:' + os.path.basename(output_filename)) 1181 | logger.info(' Generate: ' + os.path.basename(file_path) + ' -> ' + os.path.basename(output_filename) ) 1182 | file_h = open(f'{output_filename}.h', 'w') 1183 | file_cpp = open(f'{output_filename}.cpp', 'w') 1184 | 1185 | file_h.write(output_h) 1186 | file_cpp.write(output_cpp) 1187 | 1188 | file_h.close() 1189 | file_cpp.close() 1190 | 1191 | # this seems not work property. Temporary relay on pre-commit hook on each repo. 1192 | os.system('./format.sh') #apply clang-format 1193 | 1194 | if __name__ == "__main__": 1195 | 1196 | parser = argparse.ArgumentParser(description='Generate C++ files for rclUE from ROS2 msgs.') 1197 | parser.add_argument('--module', default='RCLUE', help='UE module name used in class/struct definition.') 1198 | parser.add_argument('--dependency', nargs='*', default=DEFAULT_DEPENDENCY_PKGS, help='path to directory which include \ 1199 | dependency of target. You can specify UE_tools/ros2_ws to include all build \ 1200 | pkgs as dependency, but it take longer to process.') 1201 | parser.add_argument('--target', nargs='*', help='path to directory which has target msg files') 1202 | parser.add_argument('--name_mapping', nargs='*', default=DEFAULT_NAME_MAPPING, help='name mapping to replace specific words.') 1203 | parser.add_argument('--ue_target_ros_wrapper_path', default='', help='path to directory which has header files') 1204 | parser.add_argument('--ignore_deprecated_msg', default=True, help='Ignore deprecated msg or not') 1205 | args = parser.parse_args() 1206 | 1207 | target = args.target 1208 | if target is None: 1209 | target = args.dependency 1210 | 1211 | codegen( 1212 | module = args.module, 1213 | dependency = args.dependency, 1214 | target = target, 1215 | name_mapping = args.name_mapping, 1216 | ignore_deprecated_msg = args.ignore_deprecated_msg 1217 | ) --------------------------------------------------------------------------------