diff --git a/copier.yml b/copier.yml
index 887d50c..5e92e79 100644
--- a/copier.yml
+++ b/copier.yml
@@ -12,12 +12,12 @@ template:
help: Template
type: str
default: ros2_cpp_pkg
- choices: [ros2_cpp_pkg, ros2_interfaces_pkg]
+ choices: [ros2_cpp_pkg, ros2_interfaces_pkg, ros2_python_pkg]
package_name:
help: Package name
type: str
- placeholder: ros2_cpp_pkg
+ placeholder: "{{ template }}"
validator: "{% if not package_name %}Package name is required{% endif %}"
description:
@@ -53,13 +53,13 @@ license:
node_name:
help: Node name
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: str
default: "{{ package_name }}"
node_class_name:
help: Class name of node
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: str
default: "{{ node_name | to_camel }}"
@@ -77,56 +77,56 @@ is_lifecycle:
has_launch_file:
help: Add a launch file?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: true
launch_file_type:
help: Type of launch file
- when: "{{ template == 'ros2_cpp_pkg' and has_launch_file }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: str
default: py
choices: [py, xml, yml]
has_params:
help: Add parameter loading?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: true
has_subscriber:
help: Add a subscriber?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: true
has_publisher:
help: Add a publisher?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: true
has_service_server:
help: Add a service server?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: false
has_action_server:
help: Add an action server?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: false
has_timer:
help: Add a timer callback?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
type: bool
default: false
auto_shutdown:
help: Automatically shutdown the node after launch (useful in CI/CD)?
- when: "{{ template == 'ros2_cpp_pkg' }}"
+ when: "{{ template == 'ros2_cpp_pkg' or template == 'ros2_python_pkg' }}"
when: false
type: bool
default: false
diff --git a/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/CMakeLists.txt.jinja b/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/CMakeLists.txt.jinja
new file mode 100644
index 0000000..d66679b
--- /dev/null
+++ b/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/CMakeLists.txt.jinja
@@ -0,0 +1,12 @@
+cmake_minimum_required(VERSION 3.8)
+project({{ package_name }}_interfaces)
+
+find_package(rosidl_default_generators REQUIRED)
+
+rosidl_generate_interfaces(${PROJECT_NAME}
+ action/Fibonacci.action
+)
+
+ament_export_dependencies(rosidl_default_runtime)
+
+ament_package()
diff --git a/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/action/Fibonacci.action b/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/action/Fibonacci.action
new file mode 100644
index 0000000..32b18f2
--- /dev/null
+++ b/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/action/Fibonacci.action
@@ -0,0 +1,5 @@
+int32 order
+---
+int32[] sequence
+---
+int32[] partial_sequence
\ No newline at end of file
diff --git a/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/package.xml.jinja b/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/package.xml.jinja
new file mode 100644
index 0000000..f7d17ba
--- /dev/null
+++ b/templates/ros2_python_pkg/{% if has_action_server %}{{ package_name }}_interfaces{% endif %}/package.xml.jinja
@@ -0,0 +1,24 @@
+
+
+
+
+ {{ package_name }}_interfaces
+ 0.0.0
+ ROS interface definitions for {{ package_name }}
+
+ {{ maintainer }}
+ {{ author }}
+
+ {{ license }}
+
+ ament_cmake
+ rosidl_default_generators
+ rosidl_default_runtime
+
+ rosidl_interface_packages
+
+
+ ament_cmake
+
+
+
diff --git a/templates/ros2_python_pkg/{{ package_name }}/package.xml.jinja b/templates/ros2_python_pkg/{{ package_name }}/package.xml.jinja
new file mode 100644
index 0000000..234e93f
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/package.xml.jinja
@@ -0,0 +1,22 @@
+
+
+
+
+ {{ package_name }}
+ 0.0.0
+ {{ description }}
+
+ {{ maintainer }}
+ {{ author }}
+
+ {{ license }}
+
+ ament_python
+
+ rclpy
+
+
+ ament_python
+
+
+
diff --git a/templates/ros2_python_pkg/{{ package_name }}/resource/{{ package_name }}.jinja b/templates/ros2_python_pkg/{{ package_name }}/resource/{{ package_name }}.jinja
new file mode 100644
index 0000000..e69de29
diff --git a/templates/ros2_python_pkg/{{ package_name }}/setup.cfg.jinja b/templates/ros2_python_pkg/{{ package_name }}/setup.cfg.jinja
new file mode 100644
index 0000000..e35a7b9
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/setup.cfg.jinja
@@ -0,0 +1,4 @@
+[develop]
+script_dir=$base/lib/{{ package_name }}
+[install]
+install_scripts=$base/lib/{{ package_name }}
diff --git a/templates/ros2_python_pkg/{{ package_name }}/setup.py.jinja b/templates/ros2_python_pkg/{{ package_name }}/setup.py.jinja
new file mode 100644
index 0000000..6f65446
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/setup.py.jinja
@@ -0,0 +1,29 @@
+import os
+from glob import glob
+from setuptools import setup
+
+package_name = '{{ package_name }}'
+
+setup(
+ name=package_name,
+ version='0.0.0',
+ packages=[package_name],
+ data_files=[('share/ament_index/resource_index/packages',
+ ['resource/' + package_name]),
+ (os.path.join('share', package_name), ['package.xml']),
+ (os.path.join('share', package_name,
+ 'launch'), glob('launch/*launch.[pxy][yma]*')),
+ (os.path.join('share', package_name,
+ 'config'), glob('config/*.yaml'))],
+ install_requires=['setuptools'],
+ zip_safe=True,
+ maintainer='root',
+ maintainer_email='root@todo.todo',
+ description='TODO: Package description',
+ license='TODO: License declaration',
+ tests_require=['pytest'],
+ entry_points={
+ 'console_scripts':
+ ['{{ node_name }} = {{ package_name }}.{{ node_name }}:main'],
+ },
+)
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{% if has_docker_ros %}.gitlab-ci.yml{% endif %}.jinja b/templates/ros2_python_pkg/{{ package_name }}/{% if has_docker_ros %}.gitlab-ci.yml{% endif %}.jinja
new file mode 100644
index 0000000..c01306a
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/{% if has_docker_ros %}.gitlab-ci.yml{% endif %}.jinja
@@ -0,0 +1,8 @@
+include:
+ - remote: https://raw.githubusercontent.com/ika-rwth-aachen/docker-ros/main/.gitlab-ci/docker-ros.yml
+
+variables:
+ PLATFORM: amd64,arm64
+ TARGET: dev,run
+ BASE_IMAGE: rwthika/ros2:latest
+ COMMAND: ros2 run {{ package_name }} {{ node_name }}
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja b/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja
new file mode 100644
index 0000000..8fbd350
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'py' %}{{ node_name }}_launch.py{% endif %}.jinja
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+import os
+
+from ament_index_python import get_package_share_directory
+from launch import LaunchDescription
+{% if is_lifecycle %}
+from launch.actions import DeclareLaunchArgument, GroupAction
+from launch.conditions import LaunchConfigurationNotEquals
+from launch.substitutions import LaunchConfiguration
+from launch_ros.actions import LifecycleNode, SetParameter
+{% else %}
+from launch.actions import DeclareLaunchArgument
+from launch.substitutions import LaunchConfiguration
+from launch_ros.actions import Node
+{% endif %}
+
+
+def generate_launch_description():
+
+ arg_name = DeclareLaunchArgument("name", default_value="{{ node_name }}", description="node name")
+ arg_namespace = DeclareLaunchArgument("namespace", default_value="", description="node namespace")
+
+ node = Node(
+ package="{{ package_name }}",
+ executable="{{ node_name }}",
+ namespace=LaunchConfiguration("namespace"),
+ name=LaunchConfiguration("name"),
+{% if has_params %}
+ parameters=[
+ os.path.join(get_package_share_directory("{{ package_name }}"), "config", "params.yml"),
+ ],
+{% endif %}
+ output="screen",
+ emulate_tty=True,
+ )
+
+ return LaunchDescription([
+ arg_name,
+ arg_namespace,
+ node
+ ])
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja b/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja
new file mode 100644
index 0000000..745aea4
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'xml' %}{{ node_name }}_launch.xml{% endif %}.jinja
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+{% if has_params %}
+
+{% endif %}
+
+
+
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja b/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja
new file mode 100644
index 0000000..3216122
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/{% if has_launch_file %}launch{% endif %}/{% if launch_file_type == 'yml' %}{{ node_name }}_launch.yml{% endif %}.jinja
@@ -0,0 +1,20 @@
+launch:
+ - arg:
+ name: name
+ default: {{ node_name }}
+ description: node name
+ - arg:
+ name: namespace
+ default: ""
+ description: node namespace
+ - node:
+ pkg: {{ package_name }}
+ exec: {{ node_name }}
+ namespace: "$(var namespace)"
+ name: "$(var name)"
+ output: screen
+{% if has_params %}
+ param:
+ - name: param
+ value: 1.0
+{% endif %}
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{% if has_params %}config{% endif %}/params.yml.jinja b/templates/ros2_python_pkg/{{ package_name }}/{% if has_params %}config{% endif %}/params.yml.jinja
new file mode 100644
index 0000000..809c91b
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/{% if has_params %}config{% endif %}/params.yml.jinja
@@ -0,0 +1,3 @@
+/**/*:
+ ros__parameters:
+ param: 1.0
\ No newline at end of file
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/__init__.py b/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/{{ node_name }}.py.jinja b/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/{{ node_name }}.py.jinja
new file mode 100644
index 0000000..c52c018
--- /dev/null
+++ b/templates/ros2_python_pkg/{{ package_name }}/{{ package_name }}/{{ node_name }}.py.jinja
@@ -0,0 +1,19 @@
+import rclpy
+
+from rclpy import Node
+
+
+class {{ node_class_name }}(Node):
+ def __init__(self):
+ pass
+
+
+def main():
+
+ rclpy.init()
+ rclpy.spin({{ node_class_name }}())
+ rclpy.shutdown()
+
+
+if __name__ == "__main__":
+ main()