🎯 이 챕터의 목표
지금까지는 소프트웨어(노드/토픽/메시지)만 다뤘어요. 이번 챕터부턴 로봇의 모양 — 어떤 부품이 어떻게 연결되고
어디서 회전하는지를 코드로 표현해봐요. ROS2에서는 이 표현을 URDF라고 불러요.
🧱 핵심 단어 2개 — Link & Joint
· Link = 뼈 (대퇴골, 정강이뼈)
· Joint = 관절 (무릎, 발목)
"관절"이 두 "뼈"를 연결하면서 움직임의 방향과 한계를 정해줘요.
📐 좌표계 (frame) 이해
모든 링크는 자기 자신의 좌표계를 가져요. 보통 이름은 base_link, wheel_left_link 같은 식.
한 링크의 위치는 부모 링크의 좌표계 기준으로 표현해요.
z (위쪽)
↑
|
| y (왼쪽)
| ↗
| ↗
o ─────→ x (앞)
base_link
ROS2의 기본 좌표계 규칙(REP 103):
- x = 앞 (전진 방향)
- y = 왼쪽
- z = 위쪽
- 각도 단위는 라디안 (radian), 거리 단위는 미터 (meter)
📄 첫 URDF — 막대 하나
아주 단순한 직육면체 로봇부터. ~/ros2_ws/src/에 새 패키지를 만들어요.
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 my_robot_description
cd my_robot_description
mkdir urdf
nano urdf/box_bot.urdf
<?xml version="1.0"?>
<robot name="box_bot">
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.4 0.9 1.0"/>
</material>
</visual>
</link>
</robot>
한 줄씩 풀어보기
<robot name="box_bot">— 로봇 이름. URDF의 최상위 태그.<link name="base_link">— 첫 링크. "base_link"는 관례적인 첫 링크 이름.<visual>— 시각화용 표현 (실제 충돌이나 물리는 따로).<box size="0.4 0.3 0.2"/>— 0.4m × 0.3m × 0.2m 직육면체 (x, y, z).<material> <color rgba="..."/>— 색깔. RGBA 0~1.
URDF가 받아주는 geometry 4종
<box size="x y z"/> <!-- 직육면체 -->
<sphere radius="r"/> <!-- 구 -->
<cylinder length="l" radius="r"/> <!-- 원기둥 -->
<mesh filename="package://my_pkg/meshes/x.dae"/> <!-- 3D 모델 -->
🔗 두 번째 — 관절 추가하기
본체 위에 회전하는 머리(센서 헤드)를 얹어봐요. 링크 1개 + 조인트 1개 추가.
<?xml version="1.0"?>
<robot name="box_bot">
<!-- 본체 -->
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
<material name="blue">
<color rgba="0.2 0.4 0.9 1.0"/>
</material>
</visual>
</link>
<!-- 머리 -->
<link name="head_link">
<visual>
<geometry>
<sphere radius="0.08"/>
</geometry>
<material name="red">
<color rgba="0.9 0.3 0.3 1.0"/>
</material>
</visual>
</link>
<!-- 본체 ↔ 머리 회전 조인트 -->
<joint name="head_joint" type="revolute">
<parent link="base_link"/>
<child link="head_link"/>
<origin xyz="0 0 0.15" rpy="0 0 0"/>
<axis xyz="0 0 1"/>
<limit lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>
</joint>
</robot>
joint 태그 풀이
type="revolute"— 회전 조인트 (한정된 각도 내). 다른 타입은 잠시 후.parent/child— 어느 링크에 어느 링크가 붙는지 (방향 있음).origin xyz="0 0 0.15"— 부모 좌표계 기준, 머리는 본체 위 0.15m 위치.origin rpy="0 0 0"— Roll/Pitch/Yaw (회전 자세). 0이면 정렬됨.axis xyz="0 0 1"— 회전축. z축 회전 = 좌우 둘러보기.limit lower upper— 라디안 단위 한계 (-π/2 ~ +π/2 = -90° ~ +90°).
⚙ 조인트 타입 6종
revolutecontinuousprismaticfixedfloatingplanarrevolute, continuous, prismatic, fixed.
🏃 빠른 실습 — urdf_tutorial 패키지 활용
내가 만든 URDF를 화면에 띄워보고 싶다면, ROS2에 이미 준비된 urdf_tutorial이 가장 빠른 길.
sudo apt install ros-jazzy-urdf-tutorial -y
이걸 깔면 URDF + RViz + 슬라이더 GUI를 한 번에 띄우는 launch가 들어와요.
ros2 launch urdf_tutorial display.launch.py \
model:=$HOME/ros2_ws/src/my_robot_description/urdf/box_bot.urdf
실행하면:
- RViz2 창이 뜨고 박스 + 빨간 구가 보임
joint_state_publisher_gui라는 슬라이더 창이 같이 뜸- 슬라이더 움직이면 머리가 좌우로 돌아감 🎮
base_link로.
(다음 챕터 Ch.09에서 RViz 자세히 다룸)
📦 URDF 패키지 구조
실제 프로젝트는 보통 이렇게 구성해요.
my_robot_description/
├── package.xml
├── CMakeLists.txt
├── urdf/ ← .urdf 또는 .xacro 파일
│ └── my_robot.urdf
├── meshes/ ← 3D 모델 (.stl, .dae)
│ ├── base.stl
│ └── wheel.stl
├── launch/ ← 디스플레이용 launch
│ └── display.launch.py
└── config/ ← RViz 설정, joint 매핑 등
└── view.rviz
이런 패키지를 보통 <로봇이름>_description이라고 부르는 게 ROS2 관례. TurtleBot의 경우 turtlebot4_description.
🧬 inertial — 물리 시뮬레이션에 필요
URDF는 시각화만 위한 게 아니라 물리 엔진(Gazebo)에도 쓰여요. 그러려면 각 링크의 질량과 관성을 적어줘야 해요.
<link name="base_link">
<visual>...</visual>
<collision>
<geometry>
<box size="0.4 0.3 0.2"/>
</geometry>
</collision>
<inertial>
<mass value="5.0"/>
<inertia ixx="0.1" ixy="0" ixz="0"
iyy="0.1" iyz="0" izz="0.1"/>
</inertial>
</link>
<visual>— 보이는 모양<collision>— 충돌 판정용 모양 (보통 visual보다 단순하게)<inertial>— 질량 + 관성 텐서 (Gazebo가 사용)
collision/inertial은 생략 가능. Gazebo 쓰려면 필수.
Ch.10에서 다시 만나요.
🧩 xacro — URDF의 매크로 확장
URDF는 XML이라 반복이 많아져요. 바퀴 4개면 거의 같은 코드 4번 복붙. 그래서 xacro가 만들어졌어요.
.xacro 또는 .urdf.xacro.
<?xml version="1.0"?>
<robot name="box_bot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 변수 정의 -->
<xacro:property name="wheel_radius" value="0.05"/>
<!-- 매크로 정의 -->
<xacro:macro name="wheel" params="prefix x y">
<link name="${prefix}_wheel">
<visual>
<geometry>
<cylinder length="0.04" radius="${wheel_radius}"/>
</geometry>
</visual>
</link>
<joint name="${prefix}_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="${prefix}_wheel"/>
<origin xyz="${x} ${y} 0" rpy="1.5708 0 0"/>
<axis xyz="0 0 1"/>
</joint>
</xacro:macro>
<!-- 매크로 호출 (바퀴 4개를 4번 호출) -->
<xacro:wheel prefix="fl" x="0.15" y="0.15"/>
<xacro:wheel prefix="fr" x="0.15" y="-0.15"/>
<xacro:wheel prefix="rl" x="-0.15" y="0.15"/>
<xacro:wheel prefix="rr" x="-0.15" y="-0.15"/>
</robot>
XML 200줄을 50줄로 줄여줘요. 실전 로봇 모델은 거의 다 xacro.
🔍 URDF 검증 도구
# URDF 문법 체크
check_urdf box_bot.urdf
# xacro → urdf 변환
xacro robot.urdf.xacro > robot.urdf
# URDF의 링크/조인트 트리 보기
urdf_to_graphiz robot.urdf # PDF 생성됨
check_urdf는 빌드 안 하고 즉시 문법 오류를 잡아줘요. URDF 수정 후 항상 한 번 돌려봐요.
URDF에서 "변형되지 않는 부품 하나"를 의미하는 단어는?
한 바퀴가 끝없이 회전해야 한다면 joint type은?
ROS2의 기본 좌표계 규칙(REP 103)에서 로봇의 "앞 방향"은?
xacro를 쓰는 가장 큰 이유는?
🎁 정리
- URDF = 로봇의 모양을 XML로 표현하는 표준
- 구성 요소: Link(부품) + Joint(연결)
- 좌표계: x=앞, y=왼, z=위 (REP 103) / 거리=m, 각도=rad
- geometry:
box/sphere/cylinder/mesh - joint type 4대 핵심:
revolute/continuous/prismatic/fixed - RPY = Roll/Pitch/Yaw (자세 표현)
- xacro로 반복 줄이기 (변수 + 매크로 + include)
- 패키지 관례:
<robot>_description - 실습은
urdf_tutorial+ RViz로 가장 빠름
다음 챕터는 이걸 실제로 화면에 띄우고 디버깅하는 RViz2입니다. 🎨