링크와 조인트를 XML로 적어 RViz에 띄우기까지
— 매니퓰레이터 한 대를 처음부터 만들어 본다
로봇의 생김새를 기계가 읽을 수 있는 형식으로 적어두는 파일. 비슷한 형식이 여럿 있는데 무엇이 어디에 쓰이는지, 그리고 URDF가 표현하지 못하는 것은 무엇인지부터 정리한다.
<link>, 움직임은 <joint>로 기술한다<?xml version="1.0" ?> <robot name="testbot"> <link name="base"/> <link name="link1"> ... </link> <joint name="base_joint" type="fixed"> <parent link="base"/> <child link="link1"/> </joint> </robot>
| 형식 | 무엇을 기술하나 | 주로 쓰는 도구 |
|---|---|---|
URDF | 링크·조인트 — 로봇의 형상과 움직임 | RViz |
SDF | 물리와 환경까지 포함한 시뮬레이션 정보 | Gazebo |
SRDF | 그룹·경로 계획·충돌 검사 | MoveIt |
gz sdf -p model.urdf > model.sdf 로 SDF를, Setup Assistant로 SRDF를 만들어 낸다.
링크 하나에 부모는 하나뿐이다. 고리처럼 닫히는 폐루프 구조 — 델타 로봇 같은 병렬 기구 — 는 URDF로 표현할 수 없다.
check_urdf가 트리를 출력하는 것도 이 때문
조인트로 이어진 링크가 휘거나 늘어나지 않는다고 본다. 케이블·스프링 같은 유연한 요소는 지원하지 않는다.
필요하면 Gazebo 플러그인 쪽에서 해결
사람의 팔과 비슷하게 움직이는 로봇을 매니퓰레이터라 부른다. XML을 쓰기 전에 이 로봇이 어떤 부분으로 나뉘는지, 그중 URDF가 신경 쓰는 것은 무엇인지 본다.
화살표는 부모 → 자식 방향이다. 기저에 가까운 쪽이 항상 부모이고, 링크와 조인트가 번갈아 반복되며 팔이 길어진다.
이름 · 외형 · 무게(kg) · 관성 모멘트(kg·m²)를 적는다.
외형은 원통·원뿔·직육면체 같은 단순 도형이 기본. 복잡한 구조는 stl·dae 메시 파일로.
이름 · 종류 · 운동의 기준 축 · 최소/최대 값 · 힘과 속도를 적는다.
언제나 앞뒤 링크의 관계를 기술한다. 조인트 홀로는 의미가 없다.
ROS에서 로봇 모델은 로봇명_description 패키지에 담는 것이 관례다.
testbot_description을 만들고, 그 안에 URDF의 가장 바깥 뼈대를 세운다.
로봇명_description<robot>은 링크 목록과 조인트 목록이다<?xml version="1.0" ?> <robot name="testbot"> <!-- 부품: 무엇이 있는가 --> <link name="base"/> <link name="link1"> ... </link> <link name="link2"> ... </link> <!-- 연결: 어떻게 붙어 움직이는가 --> <joint name="base_joint" type="fixed"> ... </joint> <joint name="link1_link2" type="revolute"> ... </joint> </robot>
<robot>이 문서의 root 요소로 제일 먼저 선언된다. 링크끼리는 조인트를 통해서만 연결된다.
링크 하나를 제대로 적는 법. 보이는 모양, 부딪히는 모양, 물리 계산에 쓰는 질량과 관성 — 이 세 가지가 각각 무엇이고 왜 나뉘어 있는지 본다.
<visual> — 화면에 보이는 모양<collision> — 충돌 판정에 쓰는 간섭 범위<inertial> — 질량과 관성 텐서, 물리 계산용<origin>과 <geometry>를 따로 가진다<link name="link1"> <visual> <origin xyz="0 0 0.25" rpy="0 0 0"/> <geometry><box size="0.1 0.1 0.5"/></geometry> <material name="green"/> </visual> <collision> ... </collision> <inertial> ... </inertial> </link>
<visual>만 있어도 된다.
box·cylinder·sphere가 기본. 표현하기 어려운 형상은 STL·DAE 같은 CAD 파일을 불러온다.
예쁘게 보이는 것이 목적
태그 구성은 같지만 목적이 다르다. 표시 범위보다 크게 잡아 안전 여유를 두기도 한다.
빠르고 안전하게 계산하는 것이 목적
<visual>에, 단순한 박스·원통은 <collision>에. 보기도 좋고 충돌 계산도 가벼워진다.
<!-- 길이 0.5 박스를 z로 절반만큼 올린다 --> <origin xyz="0.0 0.0 0.25" rpy="0.0 0.0 0.0"/> <geometry> <box size="0.1 0.1 0.5"/> </geometry> <!-- yaw 90도 = pi/2 = 1.5708 rad --> <origin xyz="0 0 0" rpy="0 0 1.5708"/>
| 3×3 관성 행렬 | x | y | z |
|---|---|---|---|
| x | ixx | ixy | ixz |
| y | ixy | iyy | iyz |
| z | ixz | iyz | izz |
ixy = iyx이므로 ixx · ixy · ixz · iyy · iyz · izz 여섯 개면 충분하다. <mass>는 kg, <inertia>는 kg·m².
<!-- 로봇 상단에 한 번 정의하고 --> <material name="green"> <color rgba="0 0.6 0 1" /> </material> <material name="orange"> <color rgba="1.0 0.4 0.0 1.0"/> </material> <!-- 링크에서는 이름으로 참조한다 --> <visual> ... <material name="green"/> </visual>
<texture>로 png 이미지를 입힐 수도 있다.
조인트는 두 링크를 잇고, 그 연결이 어떻게 움직일 수 있는지를 정한다. 여섯 가지 종류와, 실제 동작을 결정하는 축·제한 값을 본다.
| type | 움직임 | 비유 |
|---|---|---|
fixed | 움직이지 않는다 | 기저와 첫 링크의 결합 |
revolute | 각도 제한이 있는 회전 | 선풍기의 좌우 회전 |
continuous | 제한 없는 연속 회전 | 자동차 바퀴 |
prismatic | 한 축을 따라 직선 이동 | 미끄러지는 서랍 |
floating | 6자유도 이동 + 회전 | 자유 부유체 |
planar | 한 평면 위 이동 + 회전 | 평면 위를 도는 물체 |
<parent>·<child> — 기저에 가까운 쪽이 부모<origin> — 부모 링크 기준 조인트의 상대 위치type이 이 연결의 움직임 방식을 정한다<joint name="link1_link2" type="revolute"> <origin xyz="0.0 0.0 0.5" rpy="0.0 0.0 0.0"/> <parent link="link1"/> <child link="link2"/> <axis xyz="0 0 1"/> <limit lower="-2.617" upper="2.617" effort="30.0" velocity="1.571"/> </joint>
link1_link2처럼 잇는 두 링크를 이름에 담아 두면 트리를 눈으로 읽을 수 있다.
<axis>와 <limit>이 실제 동작을 정한다axis xyz="0 0 1" — z축 회전. 선풍기 목처럼 좌우로 돈다axis xyz="0 1 0" — y축 회전. 팔꿈치처럼 위아래로 꺾인다lower·upper — 회전 범위, 단위는 라디안effort — 조인트에 주는 힘(N), velocity — 속도(rad/s)<!-- 목처럼 도는 관절 --> <axis xyz="0.0 0.0 1.0"/> <!-- 팔꿈치처럼 꺾이는 관절 --> <axis xyz="0.0 1.0 0.0"/> <limit lower="-2.617" upper="2.617" effort="30.0" velocity="1.571"/>
파일만 봐서는 알 수 없다. 런치 파일로 노드 세 개를 띄우면 화면에 로봇이 나타나고 슬라이더로 관절을 직접 돌려볼 수 있다.
화살표는 토픽이 흐르는 방향이다. URDF 자체는 robot_description 파라미터로 robot_state_publisher에 미리 넣어 둔다.
def generate_launch_description(): urdf_file = os.path.join( get_package_share_directory('testbot_description'), 'urdf', 'testbot.urdf') with open(urdf_file, 'r') as infp: robot_description_file = infp.read() robot_state_publisher = Node( package='robot_state_publisher', executable='robot_state_publisher', parameters=[{'robot_description': robot_description_file}]) joint_state_publisher_gui = Node( package='joint_state_publisher_gui', executable='joint_state_publisher_gui') rviz2 = Node( package='rviz2', executable='rviz2', arguments=['-d', rviz_display_config_file]) return LaunchDescription([robot_state_publisher, joint_state_publisher_gui, rviz2])
robot_description 파라미터로 넘기는 것. 나머지는 노드를 띄우는 상용구다.
| 노드 | 구독 | 발행 |
|---|---|---|
joint_state_publisher | — | /joint_states |
robot_state_publisher | /joint_states | /tf · /tf_static · /robot_description |
rviz2 | /tf · /robot_description | — |
alias cba='colcon build --symlink-install' 처럼 줄여두면 이 반복이 훨씬 가벼워진다.
# urdf_tutorial 패키지를 설치하고 $ sudo apt install ros-humble-urdf-tutorial # 모델 경로만 넘기면 RViz가 뜬다 $ ros2 launch urdf_tutorial display.launch.py \ model:=/home/parallels/robot_ws/src/testbot_description/urdf/testbot.urdf
RViz를 띄우기 전에 문법과 트리를 확인하는 명령어들,
그리고 URDF가 길어졌을 때 반복을 줄여주는 xacro를 본다.
check_urdf — 문법과 링크 트리를 한 번에| 도구 | 무엇을 보여주나 |
|---|---|
check_urdf | 문법 오류와 링크 트리 |
urdf_to_graphiz | 링크·조인트 관계와 상대 좌표 변환을 PDF 다이어그램으로 |
ros2 run tf2_tools view_frames | 실행 중인 TF 트리를 PDF로 |
rqt | 노드와 토픽이 실제로 어떻게 연결됐는지 |
| URDF for VSCode | .urdf·.xacro 문법 강조와 스니펫 |
rviz2 | 최종 렌더링 — 결국 여기서 확인한다 |
xacro는 반복되는 XML을 매크로로 줄인다<xacro:property> — 값을 이름으로 관리pi·radians(x)·sqrt(x) 같은 수식을 쓸 수 있다<xacro:macro> — 인자를 받는 링크·조인트 템플릿<xacro:if> — 조건에 따라 다른 블록을 넣는다<xacro:property name="the_radius" value="2.1" /> <geometry type="cylinder" radius="${the_radius}" /> <!-- 라디안을 직접 계산하지 않아도 된다 --> <xacro:property name="upper" value="${radians(150)}"/> <xacro:macro name="link_box" params="suffix"> <link name="link_${suffix}"> ... </link> </xacro:macro> <xacro:link_box suffix="1" />
<link>로 부품을, <joint>로 연결과 움직임을 적는다.
visual·collision·inertial·origin·axis·limit은 전부 이 둘의 세부 사항
값을 바꾸고 다시 띄워보는 반복이 URDF를 이해하는 가장 빠른 길이다.
그 전에 check_urdf로 트리부터 확인
/joint_states를 구독하고, URDF와 합쳐 좌표 변환을 계산해 /tf·/tf_static으로 발행한다. 관절 값 자체를 만드는 건 joint_state_publisher다. → 슬라이드 23 · 25check_urdf로 문법과 트리를 보고, ros2 launch urdf_tutorial display.launch.py model:=... 로 RViz에 바로 띄운다. → 슬라이드 27 · 29