이번 강의에서는 ROS 2 Humble 환경에서 Python 패키지를 사용하여 모바일 로봇의 URDF/Xacro 모델을 작성하고 RViz에서 확인합니다.
ROS 2에서 모바일 로봇을 시뮬레이션하거나 RViz로 시각화하려면 로봇의 구조를 컴퓨터가 이해할 수 있는 형식으로 작성해야 합니다. 이때 가장 기본이 되는 파일 형식이 URDF입니다.
하지만 URDF만으로 로봇을 작성하면 같은 코드가 많이 반복됩니다.
예를 들어 4륜 모바일 로봇을 만든다고 하면 바퀴 link와 joint를 거의 같은 구조로 네 번 작성해야 합니다. 센서도 LiDAR, 카메라, IMU 등을 추가할 때마다 비슷한 구조가 반복됩니다.
이 문제를 해결하기 위해 사용하는 것이 Xacro입니다.
Xacro는 XML 기반의 매크로 언어입니다. URDF 파일을 더 짧고, 읽기 쉽고, 재사용 가능하게 만들기 위해 사용합니다.
이번 글에서는 다음 내용을 실습합니다.
1. Xacro 기본 구조 2. Xacro Property 사용 3. Xacro Macro 사용 4. Wheel Macro 작성 5. Sensor Macro 작성 6. Xacro Include 사용 7. Python 패키지에서 launch 파일 작성 8. RViz에서 모바일 로봇 확인
1. 필요한 패키지 설치
ROS 2 Humble 기준으로 다음 패키지를 설치합니다.
만약 아래의 패키지를 설치하셨으면 설치를 반복하지 않으셔도 됩니다.
sudo apt update sudo apt install ros-humble-xacro sudo apt install ros-humble-robot-state-publisher sudo apt install ros-humble-joint-state-publisher-gui sudo apt install ros-humble-rviz2 sudo apt install ros-humble-urdfdom
설치 확인:
ros2 run xacro xacro --help
또는:
xacro --help
정상적으로 도움말이 출력되면 설치가 완료된 것입니다.


2. Python 패키지 구조 준비
이미 사용 중인 Python 패키지 my_first_package를 사용합니다.
작업 위치로 이동합니다.
cd ~/ros2_study/src/my_first_package
다음 폴더를 추가합니다.
mkdir -p urdf launch rviz
최종 구조는 다음과 같습니다.
my_first_package/ ├── package.xml ├── setup.py ├── setup.cfg ├── resource/ │ └── my_first_package ├── my_first_package/ │ ├── __init__.py │ └── ... ├── urdf/ │ ├── mobile_robot.urdf.xacro │ ├── wheel.xacro │ └── sensor.xacro ├── launch/ │ └── display_mobile_robot.launch.py └── rviz/
이번 강의에서 작성할 파일들은 다음과 같습니다.
| mobile_robot.urdf.xacro | 모바일 로봇 전체 조립 파일 |
| wheel.xacro | 바퀴 매크로 파일 |
| sensor.xacro | 센서 매크로 파일 |
| display_mobile_robot.launch.py | RViz 실행 launch 파일 |
| setup.py | urdf, launch, rviz 폴더 설치 설정 |
| package.xml | 실행 의존성 설정 |
3. Xacro가 필요한 이유
1) URDF만 사용할 때의 문제
URDF는 로봇의 구조를 XML 형식으로 작성합니다.
URDF에서는 보통 다음 정보를 작성합니다.
| link | 로봇의 몸체, 바퀴, 센서 같은 부품 |
| joint | link와 link 사이의 연결 관계 |
| visual | RViz에서 보이는 모양 |
| collision | 충돌 계산에 사용하는 모양 |
| inertial | 질량과 관성 정보 |
예를 들어 왼쪽 바퀴와 오른쪽 바퀴를 URDF로 직접 작성하면 다음과 같은 구조가 반복됩니다.
<link name="left_wheel_link"> ... </link> <joint name="left_wheel_joint" type="continuous"> ... </joint> <link name="right_wheel_link"> ... </link> <joint name="right_wheel_joint" type="continuous"> ... </joint>
왼쪽 바퀴와 오른쪽 바퀴는 이름과 위치만 다르고 구조는 거의 같습니다.
4륜 모바일 로봇이면 이 구조가 네 번 반복됩니다.
센서가 많아지면 반복은 더 심해집니다.
2) URDF만 사용할 때 생기는 문제
| 코드 반복 | 바퀴, 센서, 관절 구조가 계속 반복됨 |
| 수정 어려움 | 바퀴 반지름을 바꾸려면 여러 곳을 수정해야 함 |
| 실수 가능성 증가 | 한쪽 바퀴의 축 방향만 틀리는 문제가 생길 수 있음 |
| 재사용 어려움 | 다른 로봇 모델에 그대로 가져다 쓰기 어려움 |
| 유지보수 불편 | 로봇 크기, 센서 위치 변경이 번거로움 |
이 문제를 해결하기 위해 Xacro를 사용합니다.
Xacro를 사용하면 다음 기능을 쓸 수 있습니다.
Property : 변수처럼 값을 저장
Macro : 반복되는 구조를 함수처럼 재사용
Include : 파일을 나누어 관리
수식 계산 : ${base_width / 2} 같은 계산 가능
4. Xacro 기본 구조
먼저 메인 Xacro 파일을 만듭니다.
cd ~/ros2_study/src/my_first_package touch urdf/mobile_robot.urdf.xacro

기본 구조는 다음과 같습니다.
<?xml version="1.0"?> <robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="simple_mobile_robot"> <!-- 여기에 property, macro, link, joint를 작성합니다 --> </robot>
1) 핵심 구조 설명
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="simple_mobile_robot">
| robot | URDF의 최상위 태그 |
| xmlns:xacro | xacro 문법을 사용하겠다는 선언 |
| name | 로봇 모델 이름 |
Xacro 파일은 최종적으로 URDF로 변환됩니다.
우리가 직접 관리하는 파일은 다음입니다.
mobile_robot.urdf.xacro
ROS에서 실제로 사용하는 형태는 변환된 URDF입니다.
mobile_robot.urdf
변환 테스트는 다음 명령으로 할 수 있습니다.
cd ~/ros2_study/src/my_first_package xacro urdf/mobile_robot.urdf.xacro
파일로 저장하려면 다음과 같이 합니다.
xacro urdf/mobile_robot.urdf.xacro > /tmp/mobile_robot.urdf
5. Xacro Property 사용
1) Property란?
Xacro property는 프로그래밍의 변수와 비슷합니다.
예를 들어 모바일 로봇의 바디 크기를 다음과 같이 정의할 수 있습니다.
<xacro:property name="base_length" value="0.6"/> <xacro:property name="base_width" value="0.4"/> <xacro:property name="base_height" value="0.2"/>
이후에는 다음처럼 사용할 수 있습니다.
${base_length}
${base_width}
${base_height}
예를 들어 박스 크기를 만들 때 다음처럼 사용합니다.
<box size="${base_length} ${base_width} ${base_height}"/>
이 코드는 실제로는 다음과 같은 의미입니다.
<box size="0.6 0.4 0.2"/>
하지만 숫자를 직접 쓰는 것보다 property를 쓰는 것이 좋습니다.
| 수정 쉬움 | base_width 값만 바꾸면 전체 구조에 반영됨 |
| 가독성 좋음 | 0.4보다 base_width가 의미가 명확함 |
| 재사용 가능 | 바퀴 위치, 센서 위치 계산에 재사용 가능 |
| 실수 감소 | 같은 값을 여러 곳에 직접 입력하지 않아도 됨 |
6. 모바일 로봇 기본 바디 작성
mobile_robot.urdf.xacro 파일을 다음처럼 작성합니다.
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="simple_mobile_robot">
<!-- ========================= -->
<!-- Xacro Properties -->
<!-- ========================= -->
<xacro:property name="base_length" value="0.6"/>
<xacro:property name="base_width" value="0.4"/>
<xacro:property name="base_height" value="0.2"/>
<xacro:property name="wheel_radius" value="0.08"/>
<xacro:property name="wheel_width" value="0.04"/>
<!-- ========================= -->
<!-- Materials -->
<!-- ========================= -->
<material name="blue">
<color rgba="0.1 0.2 0.8 1.0"/>
</material>
<material name="black">
<color rgba="0.01 0.01 0.01 1.0"/>
</material>
<material name="gray">
<color rgba="0.5 0.5 0.5 1.0"/>
</material>
<!-- ========================= -->
<!-- Base Link -->
<!-- ========================= -->
<link name="base_link">
<visual>
<origin xyz="0 0 ${base_height / 2}" rpy="0 0 0"/>
<geometry>
<box size="${base_length} ${base_width} ${base_height}"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<origin xyz="0 0 ${base_height / 2}" rpy="0 0 0"/>
<geometry>
<box size="${base_length} ${base_width} ${base_height}"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 ${base_height / 2}" rpy="0 0 0"/>
<mass value="5.0"/>
<inertia
ixx="0.1" ixy="0.0" ixz="0.0"
iyy="0.1" iyz="0.0"
izz="0.1"/>
</inertial>
</link>
</robot>

7. Xacro Macro 사용
1) Macro란?
Macro는 반복되는 XML 구조를 함수처럼 재사용하는 기능입니다.
바퀴 하나를 만들려면 보통 다음 두 가지가 필요합니다.
wheel_link wheel_joint
왼쪽 바퀴, 오른쪽 바퀴, 앞바퀴, 뒷바퀴는 이름과 위치만 다르고 구조는 거의 같습니다.
그래서 바퀴 구조를 하나의 매크로로 만들면 좋습니다.
2) 기본 Macro 문법
<xacro:macro name="매크로이름" params="파라미터1 파라미터2"> ... </xacro:macro>
사용할 때는 다음처럼 호출합니다.
<xacro:매크로이름 파라미터1="값1" 파라미터2="값2"/>
예:
<xacro:macro name="test_box" params="name x y z">
<link name="${name}">
<visual>
<origin xyz="${x} ${y} ${z}" rpy="0 0 0"/>
<geometry>
<box size="0.1 0.1 0.1"/>
</geometry>
</visual>
</link>
</xacro:macro>
<xacro:test_box name="box1" x="1.0" y="0.0" z="0.1"/>
8. 모바일 로봇 바퀴 좌표 기준
이번 실습 로봇은 기본적인 2륜 차동구동 모바일 로봇입니다.

ROS에서 일반적으로 사용하는 모바일 로봇 좌표계는 다음과 같습니다.
| X | 로봇 전방 |
| Y | 로봇 왼쪽 |
| Z | 위쪽 |
따라서 왼쪽 바퀴는 y가 양수입니다.
오른쪽 바퀴는 y가 음수입니다.
left_wheel : x = 0, y = +base_width/2, z = wheel_radius right_wheel : x = 0, y = -base_width/2, z = wheel_radius
9. Wheel Macro 작성
새 파일을 만듭니다.
cd ~/ros2_study/src/my_first_package touch urdf/wheel.xacro

내용:
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="wheel" params="prefix parent x y z">
<!-- Wheel Link -->
<link name="${prefix}_wheel_link">
<visual>
<!-- cylinder 기본 축은 z축이므로 바퀴 회전축을 y축으로 맞추기 위해 회전 -->
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_width}"/>
</geometry>
<material name="black"/>
</visual>
<collision>
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
<geometry>
<cylinder radius="${wheel_radius}" length="${wheel_width}"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.5"/>
<inertia
ixx="0.001" ixy="0.0" ixz="0.0"
iyy="0.001" iyz="0.0"
izz="0.001"/>
</inertial>
</link>
<!-- Wheel Joint -->
<joint name="${prefix}_wheel_joint" type="continuous">
<parent link="${parent}"/>
<child link="${prefix}_wheel_link"/>
<origin xyz="${x} ${y} ${z}" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
</xacro:macro>
</robot>

1) 코드 설명
매크로 선언:
<xacro:macro name="wheel" params="prefix parent x y z">
| prefix | 바퀴 이름 접두어 |
| parent | 바퀴가 붙을 부모 link |
| x | 바퀴 x 위치 |
| y | 바퀴 y 위치 |
| z | 바퀴 z 위치 |
link 이름 생성:
<link name="${prefix}_wheel_link">
prefix=”left”이면 다음으로 변환됩니다.
<link name="left_wheel_link">
prefix=”right”이면 다음으로 변환됩니다.
<link name="right_wheel_link">
바퀴 모양:
<cylinder radius="${wheel_radius}" length="${wheel_width}"/>
바퀴는 원통이므로 cylinder를 사용합니다.
바퀴 회전:
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
URDF에서 cylinder는 기본적으로 z축 방향으로 길이가 놓입니다.
하지만 모바일 로봇 바퀴는 보통 y축을 회전축으로 둡니다.
그래서 cylinder를 x축 기준으로 90도 회전시킵니다.
1.5708 rad ≒ 90도
joint 타입:
<joint name="${prefix}_wheel_joint" type="continuous">
바퀴는 계속 회전할 수 있으므로 continuous joint를 사용합니다.
회전축:
<axis xyz="0 1 0"/>
바퀴가 y축을 기준으로 회전한다는 뜻입니다.
10. Sensor Macro 작성
모바일 로봇에서 자주 사용하는 센서는 다음과 같습니다.
LiDAR Camera IMU
이번 강의에서는 Gazebo 플러그인까지는 넣지 않고, RViz에서 보이는 URDF/Xacro 구조에 집중합니다.
새 파일을 만듭니다.
cd ~/ros2_study/src/my_first_package touch urdf/sensor.xacro

내용:
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- ========================= -->
<!-- LiDAR Macro -->
<!-- ========================= -->
<xacro:macro name="lidar_sensor" params="name parent x y z">
<link name="${name}_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.07" length="0.05"/>
</geometry>
<material name="gray"/>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.07" length="0.05"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.2"/>
<inertia
ixx="0.0005" ixy="0.0" ixz="0.0"
iyy="0.0005" iyz="0.0"
izz="0.0005"/>
</inertial>
</link>
<joint name="${name}_joint" type="fixed">
<parent link="${parent}"/>
<child link="${name}_link"/>
<origin xyz="${x} ${y} ${z}" rpy="0 0 0"/>
</joint>
</xacro:macro>
<!-- ========================= -->
<!-- Camera Macro -->
<!-- ========================= -->
<xacro:macro name="camera_sensor" params="name parent x y z">
<link name="${name}_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.08 0.04 0.04"/>
</geometry>
<material name="gray"/>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.08 0.04 0.04"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.1"/>
<inertia
ixx="0.0002" ixy="0.0" ixz="0.0"
iyy="0.0002" iyz="0.0"
izz="0.0002"/>
</inertial>
</link>
<joint name="${name}_joint" type="fixed">
<parent link="${parent}"/>
<child link="${name}_link"/>
<origin xyz="${x} ${y} ${z}" rpy="0 0 0"/>
</joint>
</xacro:macro>
<!-- ========================= -->
<!-- IMU Macro -->
<!-- ========================= -->
<xacro:macro name="imu_sensor" params="name parent x y z">
<link name="${name}_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.05 0.05 0.02"/>
</geometry>
<material name="gray"/>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="0.05 0.05 0.02"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.05"/>
<inertia
ixx="0.0001" ixy="0.0" ixz="0.0"
iyy="0.0001" iyz="0.0"
izz="0.0001"/>
</inertial>
</link>
<joint name="${name}_joint" type="fixed">
<parent link="${parent}"/>
<child link="${name}_link"/>
<origin xyz="${x} ${y} ${z}" rpy="0 0 0"/>
</joint>
</xacro:macro>
</robot>

센서는 보통 로봇 바디에 고정되어 있으므로 joint 타입은 fixed를 사용합니다.
<joint name="${name}_joint" type="fixed">
| LiDAR | 로봇 상단 중앙 | 주변 장애물 감지 |
| Camera | 로봇 전방 | 전방 영상 인식 |
| IMU | 로봇 중심 근처 | 자세, 각속도 측정 |
11. Xacro Include 사용
wheel.xacro와 sensor.xacro를 만들었지만 아직 메인 파일에서 사용하지 않았습니다.
메인 파일에서 include 해야 합니다.
<xacro:include filename="$(find my_first_package)/urdf/wheel.xacro"/> <xacro:include filename="$(find my_first_package)/urdf/sensor.xacro"/>
중요한 점은 패키지 이름입니다.
이번 강의에서는 Python 패키지 이름이 my_first_package입니다.
따라서 다음처럼 작성해야 합니다.
$(find my_first_package)
12. 메인 Xacro 파일 완성
urdf/mobile_robot.urdf.xacro 파일을 최종적으로 다음처럼 작성합니다.
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="simple_mobile_robot">
<!-- ========================= -->
<!-- Include Files -->
<!-- ========================= -->
<xacro:include filename="$(find my_first_package)/urdf/wheel.xacro"/>
<xacro:include filename="$(find my_first_package)/urdf/sensor.xacro"/>
<!-- ========================= -->
<!-- Xacro Properties -->
<!-- ========================= -->
<xacro:property name="base_length" value="0.6"/>
<xacro:property name="base_width" value="0.4"/>
<xacro:property name="base_height" value="0.2"/>
<xacro:property name="wheel_radius" value="0.08"/>
<xacro:property name="wheel_width" value="0.04"/>
<!-- ========================= -->
<!-- Materials -->
<!-- ========================= -->
<material name="blue">
<color rgba="0.1 0.2 0.8 1.0"/>
</material>
<material name="black">
<color rgba="0.01 0.01 0.01 1.0"/>
</material>
<material name="gray">
<color rgba="0.5 0.5 0.5 1.0"/>
</material>
<!-- ========================= -->
<!-- Base Link -->
<!-- ========================= -->
<link name="base_link">
<visual>
<origin xyz="0 0 ${base_height / 2}" rpy="0 0 0"/>
<geometry>
<box size="${base_length} ${base_width} ${base_height}"/>
</geometry>
<material name="blue"/>
</visual>
<collision>
<origin xyz="0 0 ${base_height / 2}" rpy="0 0 0"/>
<geometry>
<box size="${base_length} ${base_width} ${base_height}"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 ${base_height / 2}" rpy="0 0 0"/>
<mass value="5.0"/>
<inertia
ixx="0.1" ixy="0.0" ixz="0.0"
iyy="0.1" iyz="0.0"
izz="0.1"/>
</inertial>
</link>
<!-- ========================= -->
<!-- Wheels -->
<!-- ========================= -->
<xacro:wheel
prefix="left"
parent="base_link"
x="0"
y="${base_width / 2 + wheel_width / 2}"
z="${wheel_radius}"/>
<xacro:wheel
prefix="right"
parent="base_link"
x="0"
y="${-base_width / 2 - wheel_width / 2}"
z="${wheel_radius}"/>
<!-- ========================= -->
<!-- Sensors -->
<!-- ========================= -->
<xacro:lidar_sensor
name="lidar"
parent="base_link"
x="0"
y="0"
z="${base_height + 0.08}"/>
<xacro:camera_sensor
name="front_camera"
parent="base_link"
x="${base_length / 2 + 0.04}"
y="0"
z="${base_height * 0.75}"/>
<xacro:imu_sensor
name="imu"
parent="base_link"
x="0"
y="0"
z="${base_height / 2}"/>
</robot>
13. Xacro 변환 테스트
작성한 Xacro 파일이 정상인지 확인합니다.
cd ~/ros2_study/src/my_first_package xacro urdf/mobile_robot.urdf.xacro
출력이 길게 나오면 정상입니다.

URDF 파일로 저장해 봅니다.
xacro urdf/mobile_robot.urdf.xacro > /tmp/mobile_robot.urdf

URDF 구조를 확인합니다.
check_urdf /tmp/mobile_robot.urdf
정상이라면 link와 joint 트리 정보가 출력됩니다.


14. Python 패키지에서 launch 파일 작성
이번 강의에서는 Python 패키지 my_first_package 안에 launch 파일을 둡니다.
cd ~/ros2_study/src/my_first_package touch launch/display_mobile_robot.launch.py

내용:
import os
from launch import LaunchDescription
from launch.substitutions import Command
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
package_name = 'my_first_package'
pkg_path = get_package_share_directory(package_name)
xacro_file = os.path.join(pkg_path, 'urdf', 'mobile_robot.urdf.xacro')
robot_description = {
'robot_description': Command(['xacro ', xacro_file])
}
robot_state_publisher_node = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[robot_description],
output='screen'
)
joint_state_publisher_gui_node = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
output='screen'
)
rviz_node = Node(
package='rviz2',
executable='rviz2',
output='screen'
)
return LaunchDescription([
robot_state_publisher_node,
joint_state_publisher_gui_node,
rviz_node
])

이 launch 파일은 다음 일을 합니다.
1. my_first_package의 share 경로를 찾음 2. urdf/mobile_robot.urdf.xacro 파일 경로를 만듦 3. xacro 명령으로 URDF를 생성함 4. robot_state_publisher 실행 5. joint_state_publisher_gui 실행 6. rviz2 실행
15. setup.py 수정
Python 패키지에서는 urdf, launch, rviz 폴더가 설치되도록 setup.py를 수정해야 합니다.
예시는 다음과 같습니다.
from setuptools import setup
import os
from glob import glob
package_name = 'my_first_package'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
(
'share/ament_index/resource_index/packages',
['resource/' + package_name]
),
(
'share/' + package_name,
['package.xml']
),
(
'share/' + package_name + '/launch',
glob.glob(os.path.join('launch', '*.launch.py'))
),
(
'share/' + package_name + '/config',
glob.glob(os.path.join('config', '*.yaml'))
),
(
'share/' + package_name + '/urdf',
glob.glob(os.path.join('urdf', '*.urdf'))
),
(
'share/' + package_name + '/urdf',
glob.glob(os.path.join('urdf', '*.xacro'))
),
(
'share/' + package_name + '/rviz',
glob.glob(os.path.join('rviz', '*.rviz'))
),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='user',
maintainer_email='user@example.com',
description='ROS 2 Humble Python package for mobile robot xacro lecture',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)

중요한 부분은 이것입니다.
(
'share/' + package_name + '/urdf',
glob.glob(os.path.join('urdf', '*.xacro'))
),
이 설정이 있어야 빌드 후 다음 경로에 xacro 파일이 설치됩니다.
~/ros2_study/install/my_first_package/share/my_first_package/urdf/
launch 파일도 다음 설정이 있어야 설치됩니다.
(
'share/' + package_name + '/launch',
glob.glob(os.path.join('launch', '*.launch.py'))
),
이 부분이 빠지면 ros2 launch 명령으로 실행할 때 launch 파일을 찾지 못합니다.
16. package.xml 수정
package.xml에 실행 의존성을 추가합니다.
예시는 다음과 같습니다.
<?xml version="1.0"?>
<package format="3">
<name>my_first_package</name>
<version>0.0.0</version>
<description>ROS 2 Humble Python package for mobile robot xacro lecture</description>
<maintainer email="user@example.com">user</maintainer>
<license>Apache-2.0</license>
<buildtool_depend>ament_python</buildtool_depend>
<exec_depend>rclpy</exec_depend>
<exec_depend>robot_state_publisher</exec_depend>
<exec_depend>joint_state_publisher_gui</exec_depend>
<exec_depend>rviz2</exec_depend>
<exec_depend>xacro</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>

export는 다음이어야 합니다.
<export> <build_type>ament_python</build_type> </export>
Python 패키지에서는 CMakeLists.txt를 수정하지 않습니다. setup.py를 수정합니다.
17. 빌드 및 실행
워크스페이스 루트로 이동합니다.
cd ~/ros2_study
빌드합니다.
colcon build --packages-select my_first_package
환경 설정을 다시 적용합니다.
source install/setup.bash

launch 파일을 실행합니다.
ros2 launch my_first_package display_mobile_robot.launch.py

RViz가 실행되면 다음을 설정합니다.
1. Fixed Frame을 base_link로 설정 2. Add 클릭 3. RobotModel 추가 4. 모바일 로봇 모델 확인

정상적으로 작성했다면 다음 link 구조가 보입니다.
base_link ├── left_wheel_link ├── right_wheel_link ├── lidar_link ├── front_camera_link └── imu_link

18. 로봇 크기 변경하기
mobile_robot.urdf.xacro에서 property 값을 수정합니다.
<xacro:property name="base_length" value="0.8"/> <xacro:property name="base_width" value="0.5"/> <xacro:property name="base_height" value="0.25"/>

다시 빌드하고 실행합니다.
cd ~/ros2_study colcon build --packages-select my_first_package source install/setup.bash ros2 launch my_first_package display_mobile_robot.launch.py
확인할 내용은 다음과 같습니다.
| 바디 길이 | 기존보다 길어졌는가 |
| 바디 폭 | 바퀴 간격이 같이 넓어졌는가 |
| 센서 위치 | 바디 높이에 맞게 이동했는가 |

base_width 값만 바꿔도 바퀴 위치 계산식이 자동으로 반영됩니다.
rviz 설정 내용을 rviz/display_mobile_robot.rviz 파일로 저장합니다.

다시 실행할 경우 File-> Open Config 메뉴를 선택하고 저장한 rviz 파일을 선택합니다.

19. 4륜 모바일 로봇으로 확장하기
기존 2륜 로봇의 바퀴 호출 부분을 다음처럼 수정합니다.
<xacro:wheel
prefix="front_left"
parent="base_link"
x="${base_length / 3}"
y="${base_width / 2 + wheel_width / 2}"
z="${wheel_radius}"/>
<xacro:wheel
prefix="front_right"
parent="base_link"
x="${base_length / 3}"
y="${-base_width / 2 - wheel_width / 2}"
z="${wheel_radius}"/>
<xacro:wheel
prefix="rear_left"
parent="base_link"
x="${-base_length / 3}"
y="${base_width / 2 + wheel_width / 2}"
z="${wheel_radius}"/>
<xacro:wheel
prefix="rear_right"
parent="base_link"
x="${-base_length / 3}"
y="${-base_width / 2 - wheel_width / 2}"
z="${wheel_radius}"/>

이렇게 하면 바퀴 매크로 하나로 4개의 바퀴를 만들 수 있습니다.

생성되는 link 이름은 다음과 같습니다.
front_left_wheel_link front_right_wheel_link rear_left_wheel_link rear_right_wheel_link

20. 센서 위치 바꾸기
LiDAR를 로봇 중앙 상단에 배치합니다.
<xacro:lidar_sensor
name="lidar"
parent="base_link"
x="0"
y="0"
z="${base_height + 0.08}"/>
카메라는 전방에 배치합니다.
<xacro:camera_sensor
name="front_camera"
parent="base_link"
x="${base_length / 2 + 0.04}"
y="0"
z="${base_height * 0.8}"/>
IMU는 로봇 중심에 가깝게 배치합니다.
<xacro:imu_sensor
name="imu"
parent="base_link"
x="0"
y="0"
z="${base_height / 2}"/>



21. 후방 카메라 추가하기
이미 만든 camera_sensor 매크로를 재사용해서 후방 카메라를 추가합니다.
<xacro:camera_sensor
name="rear_camera"
parent="base_link"
x="${-base_length / 2 - 0.04}"
y="0"
z="${base_height * 0.75}"/>

이 코드를 메인 xacro 파일의 센서 부분에 추가합니다.
추가 후 link 구조는 다음처럼 됩니다.
base_link ├── left_wheel_link ├── right_wheel_link ├── lidar_link ├── front_camera_link ├── rear_camera_link └── imu_link

22. 센서 매크로를 하나로 통합하기
현재는 LiDAR, Camera, IMU 매크로가 각각 따로 있습니다.
강의 후반부에서는 공통 센서 매크로를 만들어 코드를 더 줄일 수 있습니다.
예를 들어 박스 형태 센서는 다음처럼 공통화할 수 있습니다.
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="box_sensor" params="name parent x y z size_x size_y size_z">
<link name="${name}_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="${size_x} ${size_y} ${size_z}"/>
</geometry>
<material name="gray"/>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<box size="${size_x} ${size_y} ${size_z}"/>
</geometry>
</collision>
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="0.1"/>
<inertia
ixx="0.0002" ixy="0.0" ixz="0.0"
iyy="0.0002" iyz="0.0"
izz="0.0002"/>
</inertial>
</link>
<joint name="${name}_joint" type="fixed">
<parent link="${parent}"/>
<child link="${name}_link"/>
<origin xyz="${x} ${y} ${z}" rpy="0 0 0"/>
</joint>
</xacro:macro>
</robot>

호출 예시는 다음과 같습니다.
<xacro:box_sensor
name="front_camera"
parent="base_link"
x="${base_length / 2 + 0.04}"
y="0"
z="${base_height * 0.8}"
size_x="0.08"
size_y="0.04"
size_z="0.04"/>

센서가 많아질수록 이런 공통 매크로 방식이 실용적입니다.

23. 자주 발생하는 오류와 해결법
1) unknown macro name
오류 예:
unknown macro name: xacro:wheel
원인:
wheel.xacro가 include되지 않았거나 경로가 틀렸습니다.
확인할 코드:
<xacro:include filename="$(find my_first_package)/urdf/wheel.xacro"/>
패키지가 정상적으로 설치되었는지 확인합니다.
ros2 pkg prefix my_first_package
2) name ‘wheel_radius’ is not defined
원인:
wheel.xacro에서 다음 값을 사용하는데 메인 파일에 property가 없을 때 발생합니다.
${wheel_radius}
${wheel_width}
해결:
메인 xacro 파일에 다음 property가 있어야 합니다.
<xacro:property name="wheel_radius" value="0.08"/> <xacro:property name="wheel_width" value="0.04"/>
3) RViz에서 로봇이 안 보임
확인할 것:
1. Fixed Frame이 base_link인지 확인 2. RobotModel이 추가되었는지 확인 3. robot_state_publisher가 실행 중인지 확인 4. xacro 변환이 정상인지 확인
xacro 변환 테스트:
cd ~/ros2_study/src/my_first_package xacro urdf/mobile_robot.urdf.xacro
4) 바퀴가 이상한 방향으로 보임
원인:
URDF에서 cylinder의 기본 축 방향과 실제 바퀴 축 방향이 다르기 때문입니다.
해결:
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
필요하면 rpy 값을 조정합니다.
5) link 이름 중복 오류
오류 예:
link 'left_wheel_link' is not unique
원인:
같은 이름의 link가 두 번 생성되었습니다.
잘못된 예:
<xacro:wheel prefix="left" .../> <xacro:wheel prefix="left" .../>
올바른 예:
<xacro:wheel prefix="front_left" .../> <xacro:wheel prefix="rear_left" .../>
6) launch 실행 시 파일을 못 찾는 경우
오류 예:
No such file or directory: mobile_robot.urdf.xacro
원인:
Python 패키지에서 urdf 폴더가 설치되지 않았을 가능성이 큽니다.
setup.py에 다음 설정이 있는지 확인합니다.
(os.path.join('share', package_name, 'urdf'),
glob('urdf/*.xacro')),
수정 후 다시 빌드합니다.
cd ~/ros2_study colcon build --packages-select my_first_package source install/setup.bash
24. 정리
1) Xacro 기본 구조
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="robot_name"> </robot>
Xacro를 쓰려면 xmlns:xacro 선언이 필요합니다.
2) Xacro Property
<xacro:property name="wheel_radius" value="0.08"/>
사용:
<cylinder radius="${wheel_radius}" length="${wheel_width}"/>
Property는 반복되는 숫자, 크기, 질량, 색상 이름 등을 관리할 때 사용합니다.
3) Xacro Macro
<xacro:macro name="wheel" params="prefix parent x y z"> ... </xacro:macro>
사용:
<xacro:wheel prefix="left" parent="base_link" x="0" y="0.22" z="0.08"/>
Macro는 반복되는 link와 joint 구조를 함수처럼 재사용할 때 사용합니다.
4) Wheel Macro
바퀴는 보통 다음 구조로 만듭니다.
wheel_link wheel_joint
바퀴 joint 타입은 일반적으로 다음을 사용합니다.
type="continuous"
5) Sensor Macro
센서는 보통 로봇 바디에 고정하므로 다음 joint 타입을 사용합니다.
type="fixed"
센서 종류에 따라 geometry를 다르게 줄 수 있습니다.
| LiDAR | cylinder |
| Camera | box |
| IMU | box |
| 안테나 | cylinder 또는 sphere |
6) Xacro Include
<xacro:include filename="$(find my_first_package)/urdf/wheel.xacro"/>
파일을 나누면 유지보수가 쉬워집니다.
7) Python 패키지에서 중요한 파일
Python 패키지에서는 다음 두 파일이 중요합니다.
setup.py package.xml
setup.py에서는 urdf, launch, rviz 폴더를 설치해야 합니다.
package.xml에서는 ament_python, xacro, robot_state_publisher, joint_state_publisher_gui, rviz2 의존성을 설정해야 합니다.