[ROS2] URDF 작성하여 가상환경에 로봇 나타내기

YJ·2024년 6월 14일

ROS2

목록 보기
7/9

visualize robot


ROS에서는 visualization tool인 RVIZ를 제공한다. RVIZ를 이용하면, robot과 주변 환경의 state를 visualize할 수 있다. 이 때 robot model이라는 이름의 토픽을 이용하여 가상환경에서 robot을 나타내게 되는데, 이러한 robot model을 작성하는 파일 형식이 바로 URDF이다. URDF를 사용하여 로봇을 설계하면 각 link의 TF를 알아서 설정해주기 때문에 아주 편리하다.
URDF(Unified Robot Description Format)는 다시 말해 가상 환경에서 로봇의 규격을 정의하는 규칙이다. XML 형식(태그 안의 태그..)으로 작성되며 ROS 및 Gazebo(가상 환경)와 연동해서 사용할 수 있다. 오늘은 이러한 URDF를 작성하고, RVIZ 상에서 robot model topic을 받아오는 방법을 알아보고자 한다.



how to write URDF

URDF 파일은 기본적으로 tag 형식으로 되어있다. 그리고 URDF가 나타낼 수 있는 도형은 직육면체, 원기둥, 구 3가지로 한정되어 있다. 즉, 단순히 URDF로 로봇을 만드려면 이 3가지 도형의 크기와 위치를 조정하는 hard coding을 해야한다는 뜻이다. 우선 이 포스팅에서는 hard coding을 해본 뒤에, 반복되는 부분을 생략할 수 있는 방법을 소개하고, 더 쉽게 URDF를 작성하는 방법을 소개하도록 하겠다.

우선 urdf를 위한 pkg를 만들어준다.

ros2 pkg create --build-type ament_python urdf_tutorial
cd urdf_tutorial

urdf_tutorial 폴더 아래에 launch 폴더와 urdf 폴더를 만들어 준 뒤,
setup.py의 data_files에 다음을 추가한다.

(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
        (os.path.join('share', package_name, 'urdf'), glob('urdf/*.xacro')),

확장자에 대해 얘기하자면, urdf 확장자와 xacro 확장자 모두 xml 파일 확장자이다.
다만 urdf는 robot model을 정직하게 작성하기 위한 것이고, xacro는 xml macro의 줄임말로 macro라는 말에서도 알 수 있듯 shortcut을 제공하는 xml 파일 형식이다. xacro로 작성하는 게 일반적이고 더 편하므로 xacro라는 확장자를 작성하도록 하자.

그런 다음 아래의 아주 간단한 예제 파일을 urdf 폴더 아래에 robot.xacro 파일로 저장한다.

<?xml version="1.0"?>
<robot name = "my_first">

    <!-- BASE -->
    <link name="base_link">
    </link>

    <!-- BODY LINK -->
    <joint name="body_joint" type="fixed">
        <parent link="base_link"/>
        <child link="body"/>
    </joint>

    <link name="body">
        <visual>
                <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
        </visual>
    </link>

</robot>

파일을 해석하자면 이 로봇의 이름은 my_first로, base_link라는 가상의 link를 가지고 이 링크 위에 body라는 링크가 고정적으로 연결되어 있는데 이 링크는 길이가 0.6m이고 반지름이 0.2m 이다. 이렇듯 urdf 파일은 robot 태그 아래 로봇을 이루는 여러 link와 로봇을 연결하는 joint를 작성하여 만들어진다.

이제 이 파일을 실행해보자. 다음과 같은 robot.launch.py 파일을 launch 폴더 아래에 작성해주자.

import os
from ament_index_python.packages import get_package_share_directory
from launch import LaunchDescription
from launch.substitutions import LaunchConfiguration
from launch.actions import DeclareLaunchArgument
from launch_ros.actions import Node
import xacro

def generate_launch_description():
    use_sim_time = LaunchConfiguration("use_sim_time")

    pkg_path = os.path.join(get_package_share_directory("urdf_tutorial"))
    xacro_file = os.path.join(pkg_path, "urdf", "robot.xacro")
    robot_description = xacro.process_file(xacro_file)
    params = {"robot_description": robot_description.toxml(), "use_sim_time": use_sim_time}

    return LaunchDescription(
        [
            DeclareLaunchArgument(
                "use_sim_time", default_value="false", description="use sim time"
            ),
            Node(
                package="robot_state_publisher",
                executable="robot_state_publisher",
                output="screen",
                parameters=[params],
            ),
        ]
    )

간단히 말하자면 robot state publisher라는 node를 xacro 파일의 내용을 받아와 실행한다는 내용이다. robot model만 발행하는 package의 launch file로 그대로 이용해도 무방하다.(파일 이름만 수정하면 됨.)

그리고 workspace에서 colcon build를 한 뒤 ros launch를 이용해 실행해준다.

colcon build
ros2 launch urdf_tutorial robot.launch.py

다른 터미널을 열고 rviz2를 입력해주면 rviz2가 뜬다.

rviz의 사용 방법은 추후에 다시 포스팅하겠지만, 여기서 간단히 설명하도록 하겠다.

  • fixed frame > base link로 설정한다. (살펴볼 기준 frame을 설정하는 작업이다. map과 base_link 사이의 transform이 아직 정의되지 않았기 때문에, base_link를 기준으로 봐야 robot을 볼 수 있다.)
  • 왼쪽 아래의 add > by display type > robot model을 선택해주면 위와 같이 robot model topic이 displays 아래에 뜬다. 이는 visualize할 topic을 선택하는 작업이다.
  • description topic > /robot_description을 선택해야 비로소 로봇이 보인다.

이렇게 첫 번째 robot model(로봇이라고 볼 순 없지만..)을 만들어 보았다. 그런데 urdf 파일을 이렇게 매번 만들고 실행해서 확인하고 수정하기를 반복하기엔 번거롭다. (물론 xacro file만 수정하고 colcon build를 수행하면 되긴 하지만..)

http://www.mymodelrobot.appspot.com/5674976526991360

이 사이트를 이용하면 urdf 파일을 바로 model로 변경해주긴 한다. 근데 보안이 걱정됨
보안이 걱정된다면 colcon build를 매번 해주는 것 쯤은 감수하도록 하자.



develop - 복잡한 로봇 모델 만들기


robot.xacro 파일을 아래와 같이 수정하면 위와 같은 robot을 얻을 수 있다.

<?xml version="1.0"?>
<robot name="materials">

  <material name="blue">
    <color rgba="0 0 0.8 1"/>
  </material>

  <material name="white">
    <color rgba="1 1 1 1"/>
  </material>

  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
      <material name="blue"/>
    </visual>
  </link>

  <link name="right_leg">
    <visual>
      <geometry>
        <box size="0.6 0.1 0.2"/>
      </geometry>
      <origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
      <material name="white"/>
    </visual>
  </link>

  <joint name="base_to_right_leg" type="fixed">
    <parent link="base_link"/>
    <child link="right_leg"/>
    <origin xyz="0 -0.22 0.25"/>
  </joint>

</robot>

여기서 확인해야 하는 것은 다음의 4가지이다.

  1. 색상설정은 미리 정의한 뒤 link마다 해준다.
  2. link의 위치는 겹칠 수 있다.
  3. link와 link를 연결할 때는 우선 joint의 위치를 잡는다 -> 연결부를 나타낸다. 이 때 joint는 parent link의 기준 frame으로부터의 위치다. 여기서 xyz는 오른쪽 위로 joint를 이동시키는 역할을 한다.
  4. 그 뒤 link의 위치를 잡는다. link는 joint의 위치를 기준으로 움직인다. 팔이 세로로 길어야 하므로 90도 회전시키고, 팔이 너무 위에 있으면 안되므로 아래로 이동시킨 모양이다.

이를 바탕으로 훨씬 더 복잡한 로봇 모델을 만드려면 다음의 파일을 작성해주면 된다. 매우 길지만 단순하므로 차근차근 읽어보고, 결과물을 파악해보자.

<?xml version="1.0"?>
<robot name="visual">

  <material name="blue">
    <color rgba="0 0 0.8 1"/>
  </material>
  <material name="black">
    <color rgba="0 0 0 1"/>
  </material>
  <material name="white">
    <color rgba="1 1 1 1"/>
  </material>

  <link name="base_link">
    <visual>
      <geometry>
        <cylinder length="0.6" radius="0.2"/>
      </geometry>
      <material name="blue"/>
    </visual>
  </link>

  <link name="right_leg">
    <visual>
      <geometry>
        <box size="0.6 0.1 0.2"/>
      </geometry>
      <origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
      <material name="white"/>
    </visual>
  </link>

  <joint name="base_to_right_leg" type="fixed">
    <parent link="base_link"/>
    <child link="right_leg"/>
    <origin xyz="0 -0.22 0.25"/>
  </joint>

  <link name="right_base">
    <visual>
      <geometry>
        <box size="0.4 0.1 0.1"/>
      </geometry>
      <material name="white"/>
    </visual>
  </link>

  <joint name="right_base_joint" type="fixed">
    <parent link="right_leg"/>
    <child link="right_base"/>
    <origin xyz="0 0 -0.6"/>
  </joint>

  <link name="right_front_wheel">
    <visual>
      <origin rpy="1.57075 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.1" radius="0.035"/>
      </geometry>
      <material name="black"/>
    </visual>
  </link>
  
  <joint name="right_front_wheel_joint" type="fixed">
    <parent link="right_base"/>
    <child link="right_front_wheel"/>
    <origin rpy="0 0 0" xyz="0.133333333333 0 -0.085"/>
  </joint>

  <link name="right_back_wheel">
    <visual>
      <origin rpy="1.57075 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.1" radius="0.035"/>
      </geometry>
      <material name="black"/>
    </visual>
  </link>
  <joint name="right_back_wheel_joint" type="fixed">
    <parent link="right_base"/>
    <child link="right_back_wheel"/>
    <origin rpy="0 0 0" xyz="-0.133333333333 0 -0.085"/>
  </joint>

  <link name="left_leg">
    <visual>
      <geometry>
        <box size="0.6 0.1 0.2"/>
      </geometry>
      <origin rpy="0 1.57075 0" xyz="0 0 -0.3"/>
      <material name="white"/>
    </visual>
  </link>

  <joint name="base_to_left_leg" type="fixed">
    <parent link="base_link"/>
    <child link="left_leg"/>
    <origin xyz="0 0.22 0.25"/>
  </joint>

  <link name="left_base">
    <visual>
      <geometry>
        <box size="0.4 0.1 0.1"/>
      </geometry>
      <material name="white"/>
    </visual>
  </link>

  <joint name="left_base_joint" type="fixed">
    <parent link="left_leg"/>
    <child link="left_base"/>
    <origin xyz="0 0 -0.6"/>
  </joint>

  <link name="left_front_wheel">
    <visual>
      <origin rpy="1.57075 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.1" radius="0.035"/>
      </geometry>
      <material name="black"/>
    </visual>
  </link>
  <joint name="left_front_wheel_joint" type="fixed">
    <parent link="left_base"/>
    <child link="left_front_wheel"/>
    <origin rpy="0 0 0" xyz="0.133333333333 0 -0.085"/>
  </joint>

  <link name="left_back_wheel">
    <visual>
      <origin rpy="1.57075 0 0" xyz="0 0 0"/>
      <geometry>
        <cylinder length="0.1" radius="0.035"/>
      </geometry>
      <material name="black"/>
    </visual>
  </link>
  <joint name="left_back_wheel_joint" type="fixed">
    <parent link="left_base"/>
    <child link="left_back_wheel"/>
    <origin rpy="0 0 0" xyz="-0.133333333333 0 -0.085"/>
  </joint>

  <joint name="gripper_extension" type="fixed">
    <parent link="base_link"/>
    <child link="gripper_pole"/>
    <origin rpy="0 0 0" xyz="0.19 0 0.2"/>
  </joint>

  <link name="gripper_pole">
    <visual>
      <geometry>
        <cylinder length="0.2" radius="0.01"/>
      </geometry>
      <origin rpy="0 1.57075 0 " xyz="0.1 0 0"/>
    </visual>
  </link>

  <joint name="left_gripper_joint" type="fixed">
    <origin rpy="0 0 0" xyz="0.2 0.01 0"/>
    <parent link="gripper_pole"/>
    <child link="left_gripper"/>
  </joint>

  <link name="left_gripper">
    <visual>
      <origin rpy="0.0 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
      </geometry>
    </visual>
  </link>

  <joint name="left_tip_joint" type="fixed">
    <parent link="left_gripper"/>
    <child link="left_tip"/>
  </joint>

  <link name="left_tip">
    <visual>
      <origin rpy="0.0 0 0" xyz="0.09137 0.00495 0"/>
      <geometry>
        <mesh filename="package://urdf_tutorial/meshes/l_finger_tip.dae"/>
      </geometry>
    </visual>
  </link>
  <joint name="right_gripper_joint" type="fixed">
    <origin rpy="0 0 0" xyz="0.2 -0.01 0"/>
    <parent link="gripper_pole"/>
    <child link="right_gripper"/>
  </joint>

  <link name="right_gripper">
    <visual>
      <origin rpy="-3.1415 0 0" xyz="0 0 0"/>
      <geometry>
        <mesh filename="package://urdf_tutorial/meshes/l_finger.dae"/>
      </geometry>
    </visual>
  </link>

  <joint name="right_tip_joint" type="fixed">
    <parent link="right_gripper"/>
    <child link="right_tip"/>
  </joint>

  <link name="right_tip">
    <visual>
      <origin rpy="-3.1415 0 0" xyz="0.09137 0.00495 0"/>
      <geometry>
        <mesh filename="package://urdf_tutorial/meshes/l_finger_tip.dae"/>
      </geometry>
    </visual>
  </link>

  <link name="head">
    <visual>
      <geometry>
        <sphere radius="0.2"/>
      </geometry>
      <material name="white"/>
    </visual>
  </link>
  <joint name="head_swivel" type="fixed">
    <parent link="base_link"/>
    <child link="head"/>
    <origin xyz="0 0 0.3"/>
  </joint>

  <link name="box">
    <visual>
      <geometry>
        <box size="0.08 0.08 0.08"/>
      </geometry>
      <material name="blue"/>
    </visual>
  </link>

  <joint name="tobox" type="fixed">
    <parent link="head"/>
    <child link="box"/>
    <origin xyz="0.1814 0 0.1414"/>
  </joint>
</robot>

그러면 다음의 결과물을 확인할 수 있다.

mesh file이 있어야 gripper가 나타난다.



더 알아볼만한 것들

solidworks for urdf

https://wiki.ros.org/sw_urdf_exporter
solidworks로 만든 파일을 urdf로 변경하는 것을 도와준다. 이 방법을 가장 많이 쓸 듯 하다.
blender에서도 가능하다고 하는데, 편한 쪽으로 사용하면 된다.

joint state

https://docs.ros.org/en/humble/Tutorials/Intermediate/URDF/Using-URDF-with-Robot-State-Publisher.html
robot의 link와 link를 연결하는 joint는 fixed 말고도 revoloute, prismatic 등을 지원한다. robot의 움직임을 구현하고 싶으면 joint 설정을 이런식으로 한 뒤, joint state가 변함에 따라 이를 publish 해주는 파일을 작성해주면 된다.

tf publisher

로봇을 만들었다면 로봇이 가만히 있을리는 당연히 없다. 이러한 robot의 위치를 변화시켜주려면, robot의 base가 되는 link와 map의 transform을 publish해주는 node를 작성하면 된다. 이는 TF2에 대한 포스팅을 참고하기 바란다.



참고 사이트

https://with-rl.tistory.com/entry/URDF%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-%EB%A1%9C%EB%B4%87-%EB%A7%8C%EB%93%A4%EA%B8%B0-1
https://docs.ros.org/en/humble/Tutorials/Intermediate/URDF/Building-a-Visual-Robot-Model-with-URDF-from-Scratch.html

0개의 댓글