AEGLOSLAB ROS 2 Jazzy 대전

제5부 좌표·모델

5.6 SDF와 URDF — 기술 형식의 이중성

5.6.1 이 장에서 배우는 것 / 선수 지식

5.3.8에서 URDF가 표현하지 못하는 것들을 봤다 — 폐쇄 사슬, 센서, 세계, 다중 로봇. 그 한계를 메우는 형식이 SDF(Simulation Description Format) 이며, Gazebo의 기본 형식이다.

여기서 실무적 난제가 시작된다. ROS 생태계는 URDF를 쓰고 시뮬레이터는 SDF를 쓴다. 같은 로봇을 두 형식으로 유지해야 하는가? 변환은 어디까지 되는가? 어느 것을 단일 출처로 삼을 것인가?

이 장은 그 판단을 다룬다. 결론을 먼저 말하면 — 대부분의 경우 URDF(xacro)를 단일 출처로 두고 시뮬 확장을 조건부로 추가하는 것이 실용적이며, SDF가 필요한 경우는 명확히 구분된다.

배우는 것:

  • SDF의 정체 — 무엇을 더 표현하는가.
  • 구조적 차이 — 월드·중첩 모델·센서·플러그인·폐쇄 사슬.
  • 변환 — URDF → SDF가 어디까지 되는가.
  • 판단 기준 — 어느 것을 단일 출처로.
  • 실무 구성 — xacro 조건부 vs SDF 병행.
  • 버전 문제 — sdformat과 Gazebo 세대.

선수 지식: 5.3(URDF), 5.4(xacro 조건부 블록), 5.1(tf). Gazebo 자체는 10.2에서 본격적으로 다루며, 여기서는 형식의 선택에 집중한다.

정직: SDF·sdformat 명세와 Gazebo 세대(Gazebo Classic vs 신 Gazebo)의 관계는 복잡하고 변해 왔다. BK-05에 따라 이 책은 Jazzy와 짝을 이루는 Gazebo Harmonic을 기준으로 하되, 변환 도구의 지원 범위·태그 이름은 버전 의존이므로 (확인 필요)를 남겨 두었다. 다만 <mimic>의 소비자별 지원과 URDF 검사 도구는 Jazzy에서 실행 확인했다(5.6.11). Gazebo 자체는 이 환경에 없어 SDF 변환 결과는 검증하지 못했다(목차 3).

5.6.2 SDF는 무엇을 더 표현하는가

URDF 로봇 하나 링크 · 관절 (트리) SDF 월드 지형 · 조명 · 물리 모델 여럿 중첩 가능 센서 1급 개념 플러그인 폐쇄 사슬 상태 · 액터 · 관절 축 2개
기능URDFSDF
로봇 구조(링크·관절)✅✅
폐쇄 사슬❌ 트리만✅
센서 정의❌ 확장 태그로만✅ 1급 개념
월드(지형·조명·중력)❌✅
다중 모델❌✅
중첩 모델(모델 안의 모델)❌✅
플러그인확장 태그✅
2축 관절(universal 등)❌✅
물리 속성(마찰·반발)확장 태그✅
조명·재질 상세제한적✅

SDF 예:

<?xml version="1.0"?>
<sdf version="1.10">
  <world name="factory">
    <physics name="default" type="ode">
      <max_step_size>0.001</max_step_size>
      <real_time_factor>1.0</real_time_factor>
    </physics>

    <light type="directional" name="sun">
      <direction>-0.5 0.1 -0.9</direction>
    </light>

    <model name="ground">
      <static>true</static>
      <link name="surface">
        <collision name="c">
          <geometry><plane><normal>0 0 1</normal></plane></geometry>
          <surface><friction><ode><mu>0.8</mu></ode></friction></surface>
        </collision>
      </link>
    </model>

    <model name="my_robot">
      <link name="base_link">
        <sensor name="lidar" type="gpu_lidar">
          <update_rate>10</update_rate>
          <topic>scan</topic>
          <lidar>
            <scan><horizontal><samples>360</samples></horizontal></scan>
            <range><min>0.1</min><max>10.0</max></range>
          </lidar>
        </sensor>
      </link>
    </model>
  </world>
</sdf>

센서가 1급 개념이라는 점이 URDF와의 큰 차이다. URDF에서는 <gazebo> 확장 태그로 밀어 넣어야 하고(5.3.7), 그것은 URDF 파서가 무시하는 영역이라 오타가 조용히 지나간다.

5.6.3 왜 두 형식이 존재하는가

역사적 이유다. URDF는 ROS의 로봇 기술 형식으로 먼저 만들어졌고, 시뮬레이션에 필요한 것들(월드·센서·물리)이 부족했다. Gazebo는 그 확장으로 SDF를 만들었고, 이후 독립적으로 발전했다.

형식설계 목적주 소비자
URDFROS의 로봇 운동학·시각 기술tf(5.1), MoveIt 2(9.1), RViz2(11.4)
SDF물리 시뮬레이션 전반Gazebo(10.2)

결과: 생태계가 갈라져 있다.

도구URDFSDF
robot_state_publisher(5.5)✅❌
MoveIt 2(9.1)✅❌ — SDF 파서가 없다(실행 확인: moveit_core는 URDF 모델만 만든다)
RViz2✅❌
ros2_control(6.1)✅(확장 태그)—
Gazebo변환해서✅ 네이티브
다른 시뮬레이터(Isaac 등 — 10.5)각자 다름각자 다름

이것이 URDF를 단일 출처로 삼는 실무적 근거다. ROS 생태계의 핵심 도구들이 URDF만 이해하므로, SDF를 원본으로 하면 그 도구들을 위해 URDF를 별도로 만들어야 한다.

5.6.4 URDF → SDF 변환

Gazebo는 URDF를 받으면 내부적으로 SDF로 변환한다.

# 변환 결과 확인 (신 Gazebo. Classic 은 gz2sdf 계열이었다)
gz sdf -p my_robot.urdf > my_robot.sdf
# 주의: gz 명령은 Gazebo 를 설치해야 생긴다 — ros-jazzy-ros-gz 로는 안 온다(10.2)

# xacro → urdf → sdf
xacro robot.urdf.xacro | gz sdf -p /dev/stdin > robot.sdf

변환에서 일어나는 일:

URDF 요소SDF 변환 결과
<link>·<joint>대응 요소로
<inertial>그대로(5.3.5의 값이 여기서 쓰인다)
<gazebo> 확장 태그SDF 요소로 삽입
고정 관절⚠️ 링크가 병합될 수 있다
<mimic>URDF 쪽 소비자는 모두 지원(실행 확인 — 5.6.11). SDF 변환 시의 보존 여부는 미확인

"고정 관절로 이어진 링크가 병합된다"가 중요한 함정이다.

URDF: base_link --fixed--> laser_link SDF 변환 laser_link가 base_link에 병합될 수 있음 결과: laser_link 프레임이 시뮬에서 사라짐

왜 병합하는가: 물리 엔진 입장에서 고정 관절로 이어진 두 강체는 하나의 강체다. 성능을 위해 합치는 것이 합리적이다.

결과로 생기는 문제: 그 링크에 센서를 달았거나 그 프레임을 참조하면 찾을 수 없게 된다.

회피:

<!-- 병합을 막는 태그 -->
<gazebo reference="laser_link">
  <preserveFixedJoint>true</preserveFixedJoint>
</gazebo>

(확인 필요: 태그 이름과 지원은 Gazebo 세대에 따라 다르다. 신 Gazebo에서는 다른 방식일 수 있다.)

함정: 시뮬에서만 센서 프레임을 못 찾는 문제의 흔한 원인이 이것이다. 실기에서는 RSP가 /tf_static으로 발행하니 정상이고, 시뮬에서는 병합되어 없다. 증상이 환경에 따라 달라 진단이 혼동을 일으킨다.

변환의 한계:

한계설명
폐쇄 사슬URDF에 없으니 변환도 불가
SDF 고유 기능URDF에서 표현 못 하는 것은 <gazebo> 태그로만
월드URDF는 로봇만 — 월드는 별도 SDF 필요
정확도변환기 구현에 의존

5.6.5 판단 — 어느 것을 단일 출처로

세 가지 접근이 있다.

① URDF(xacro) 단일 출처 + <gazebo> 확장

<!-- robot.urdf.xacro -->
<xacro:if value="${use_sim}">
  <gazebo reference="laser_link">
    <sensor name="lidar" type="gpu_lidar">...</sensor>
  </gazebo>
</xacro:if>
축평가
ROS 도구 호환✅ 완전
시뮬 표현력⚠️ 확장 태그의 한계 안에서
유지보수✅ 한 파일
폐쇄 사슬❌
월드별도 SDF 필요

적합: 대부분의 이동 로봇·매니퓰레이터. 기본 선택.

② SDF 단일 출처 + URDF 생성

SDF를 원본으로 두고, ROS 도구용 URDF를 별도로 만든다(수동 또는 도구).

축평가
시뮬 표현력✅ 완전
ROS 도구 호환⚠️ 변환 필요
유지보수❌ SDF → URDF 자동 변환이 어렵다(확인 필요)
폐쇄 사슬✅

적합: 시뮬레이션이 주된 목적이고 ROS 도구 사용이 제한적인 경우. 드물다.

③ 병행 유지

URDF와 SDF를 각각 관리한다.

축평가
각 영역 표현력✅ 최대
일관성❌ 반드시 어긋난다
유지보수❌ 두 배

견해: ③은 피하라. 두 파일이 어긋나는 것은 시간 문제이고, 어긋난 뒤에는 "시뮬과 실기가 왜 다른가"를 며칠씩 쫓게 된다(10.6). 단일 출처 원칙(5.2.5·5.4.5·5.5.4에서 반복한 것)이 여기서 가장 중요하다.

견해: ①이 기본이고, 폐쇄 사슬이나 고급 시뮬 기능이 정말 필요할 때만 다시 생각하라. 그리고 그때도 "폐쇄 사슬을 시뮬에서만 닫는" 부분적 접근이 가능한지 검토한다 — Gazebo 플러그인이나 SDF 스니펫을 조건부로 삽입하는 방식이다.

5.6.6 실무 구성 예

단일 출처 xacro에서 세 가지 산출물을 만드는 구조:

robot.urdf.xacro (단일 출처) use_sim:=false → 실기 URDF use_sim:=true → 시뮬용 URDF (gazebo 태그 포함) Gazebo가 내부 변환 → SDF RSP · MoveIt 2 · RViz2
<!-- robot.urdf.xacro -->
<xacro:arg name="use_sim" default="false"/>
<xacro:property name="use_sim" value="$(arg use_sim)"/>

<!-- 공통 구조 -->
<xacro:include filename="base.xacro"/>
<xacro:include filename="sensors.xacro"/>

<!-- 시뮬 전용 -->
<xacro:if value="${use_sim}">
  <xacro:include filename="gazebo_sensors.xacro"/>
  <xacro:include filename="gazebo_physics.xacro"/>
</xacro:if>

<!-- ros2_control: 시뮬과 실기가 다른 플러그인 (10.4) -->
<ros2_control name="MyRobot" type="system">
  <hardware>
    <xacro:if value="${use_sim}">
      <plugin>gz_ros2_control/GazeboSimSystem</plugin>
    </xacro:if>
    <xacro:unless value="${use_sim}">
      <plugin>my_robot_driver/MyRobotHardware</plugin>
      <param name="device">/dev/ttyUSB0</param>
    </xacro:unless>
  </hardware>
  <!-- 관절 정의는 공통 -->
</ros2_control>

이 구조의 핵심 이점: 관절 이름·치수·관성이 시뮬과 실기에서 자동으로 동일하다. Sim2Real 간극(10.6)의 상당 부분이 "모델이 미묘하게 다른 것"에서 오는데, 그것이 구조적으로 차단된다.

월드는 별도 SDF로 유지한다:

파일내용
worlds/factory.sdf지형·장애물·조명·물리 설정
urdf/robot.urdf.xacro로봇

로봇을 월드에 스폰하는 것은 실행 시점에 한다(10.2) — 월드 파일에 로봇을 고정 포함하지 않는다. 그래야 같은 월드에 여러 로봇을 스폰하거나 로봇만 교체할 수 있다.

5.6.7 버전 문제

sdformat 버전과 Gazebo 세대가 얽혀 있다.

축설명
sdformat 버전<sdf version="1.10"> 같은 명세 버전
Gazebo 세대Gazebo Classic(구) vs 신 Gazebo(Harmonic 등)
ROS 연동 패키지gazebo_ros_pkgs(구) vs ros_gz(신)

정직: Gazebo Classic과 신 Gazebo는 태그·플러그인·토픽 구조가 상당히 다르다. 인터넷의 예제 상당수가 Classic 기준이며, 그대로 쓰면 동작하지 않는다. BK-05에 따라 이 책은 Jazzy와 짝을 이루는 Harmonic을 기준으로 하되, 구체 태그·플러그인 이름은 Gazebo 문서의 해당 버전을 확인해야 한다(확인 필요).

세대 구분 신호:

신호세대
gazebo_ros·libgazebo_ros_*.soClassic
ros_gz_sim·gz-sim 플러그인신 Gazebo
<sensor type="ray">Classic
<sensor type="gpu_lidar">신 Gazebo(확인 필요)
gazebo 명령Classic
gz sim 명령신 Gazebo

견해: 예제를 참고할 때 세대를 먼저 확인하라. 태그 하나가 다른 것이 아니라 구조 전체가 다르므로, Classic 예제를 조금씩 고쳐 쓰려다 시간을 버리는 경우가 많다. Harmonic 공식 문서와 ros_gz 저장소의 예제를 출발점으로 삼는 편이 빠르다.

5.6.8 SDF가 정말 필요한 경우

①을 기본으로 하되, 다음에서는 SDF를 직접 다루게 된다.

상황이유
월드 제작URDF로 표현 불가
폐쇄 사슬 로봇델타 로봇·병렬 기구·4절 링크
복잡한 물리 튜닝접촉·마찰·서스펜션 세부
다중 모델 상호작용컨베이어·문·리프트
액터(사람 등) 시뮬SDF 고유 기능
Gazebo 고유 기능확장 태그로 다 안 되는 것

폐쇄 사슬의 실무적 접근:

tf · 시각화만 운동학 정확도 동역학 시뮬 폐쇄 사슬 로봇 무엇이 필요한가? URDF에서 사슬을 끊어 트리로 5.3.8 URDF 트리 + 별도 IK 노드 SDF 또는 Gazebo 플러그인으로 사슬 닫기

세 번째 경우에도 URDF를 버릴 필요는 없다 — URDF는 트리로 두고, 시뮬에서만 추가 관절로 사슬을 닫는 구성이 가능하다. 조건부 블록(5.4.5)으로 시뮬 전용 SDF 스니펫을 삽입한다.

5.6.11 <mimic>은 누가 처리하나 — 실측

이 장과 5.5가 함께 (확인 필요)로 남긴 항목이다 — "mimic 관절의 처리는 소비자마다 다르다". URDF 쪽 소비자는 셋 다 지원한다는 것을 확인했다(실행 확인, Jazzy).

소비자지원근거
URDF 파서(urdfdom)✅<mimic joint multiplier offset>이 든 URDF를 check_urdf가 정상 파싱
ros2_control✅hardware_info.hpp에 MimicJoint 구조체(joint_index·mimicked_joint_index·multiplier·offset)와 MimicAttribute 열거형(NOT_SET/TRUE/FALSE)
MoveIt 2✅JointModel에 getMimic()·getMimicFactor()·getMimicOffset()·getMimicRequests()·setMimic()
robot_state_publisher✅5.5.6에서 이미 다룬다

즉 "소비자마다 다르다"는 우려는 URDF 경로에서는 해소된다. 평행 그리퍼처럼 한 모터가 두 손가락을 움직이는 구조를 URDF에 <mimic>으로 적으면, 상태 발행·계획·제어가 모두 그 관계를 안다.

ros2_control의 MimicAttribute에 NOT_SET이 있다는 점이 설계를 드러낸다. URDF의 <mimic> 유무만이 아니라 <ros2_control> 블록에서 명시적으로 켜고 끌 수 있다는 뜻이다 — URDF는 mimic인데 하드웨어는 두 축을 독립 제어하는 경우를 표현하기 위한 것으로 보인다.

함정: 확인하지 못한 것은 SDF 변환 경로다. URDF를 SDF로 바꿀 때 mimic 관계가 보존되는지는 이 환경에 Gazebo가 없어 검증하지 못했다. 시뮬레이터에서 그리퍼 한쪽만 움직인다면 이 지점을 의심하라 — URDF에서 되는 것이 SDF에서도 된다는 보장이 없다는 것이 이 장의 요지이기도 하다.

5.6.11.1 gz 명령은 별도 설치다

이 장의 변환 예시(gz sdf -p)를 그대로 따라 하려면 Gazebo가 설치되어 있어야 한다(실행 확인 — ROS 2만 설치된 환경에는 gz 명령이 없다). ros_gz 브리지 패키지를 설치해도 오지 않는다.

URDF 쪽 검사 도구는 ROS 2 설치에 포함된다:

도구용도
check_urdf <file>파싱 + 링크 트리 출력
urdf_to_graphiz <file>트리를 그래프로(.gv·.pdf)

check_urdf의 출력이 트리 구조를 그대로 보여 준다는 점이 유용하다:

robot name is: m
---------- Successfully Parsed XML ---------------
root Link: base has 1 child(ren)
    child(1):  a
        child(1):  b

견해: URDF를 고칠 때마다 check_urdf를 먼저 실행하라. RViz2나 시뮬레이터를 실행해 확인하는 것보다 훨씬 빠르고, 트리가 의도한 모양인지(5.3.3의 부모·자식 관계)를 한눈에 본다. 링크가 빠져 있거나 루트가 둘로 갈라진 경우가 여기서 바로 드러난다.

5.6.9 함정 카탈로그

#함정증상대처
1URDF·SDF 병행 유지반드시 어긋남단일 출처(5.6.5)
2고정 관절 링크 병합시뮬에서만 프레임 없음preserveFixedJoint 계열
3Classic 예제를 신 Gazebo에동작 안 함세대 확인(5.6.7)
4월드에 로봇을 고정 포함함재사용 불가실행 시 스폰
5<gazebo> 태그 링크명 오타조용히 무시(5.3.7)이름 대조
6시뮬·실기 관절 이름 불일치컨트롤러 실패단일 xacro
7SDF를 원본으로 두고 ROS 도구 사용URDF 수동 유지 부담URDF 원본
8변환 결과 미확인예상과 다른 SDFgz sdf -p로 확인
9폐쇄 사슬을 URDF로 시도표현 불가근사 또는 시뮬 전용
10sdformat 버전 불일치파싱 오류·무시버전 명시·확인
11시뮬에서 그리퍼 한쪽만 움직임SDF 변환에서 <mimic> 유실 의심5.6.11

5.6.10 체크포인트

  • SDF가 URDF보다 더 표현하는 것 일곱 가지를 들 수 있는가. "센서가 1급 개념"인 것의 실무적 차이는?
  • 두 형식이 존재하는 역사적 이유와, 생태계가 갈라진 결과를 설명할 수 있는가.
  • URDF를 단일 출처로 삼는 실무적 근거는 무엇인가.
  • URDF → SDF 변환에서 고정 관절에 무슨 일이 일어나는가. 왜 그렇게 하며, 어떤 문제를 만드는가.
  • 그 문제가 왜 진단하기 혼동되는가(환경별 차이).
  • 세 가지 단일 출처 접근의 장단을 비교하고, 권장되는 것과 피해야 하는 것을 말할 수 있는가.
  • 단일 xacro에서 시뮬·실기를 분기하는 구조가 Sim2Real 간극에 어떤 도움이 되는가.
  • 월드에 로봇을 고정 포함하지 않는 이유는?
  • Gazebo 세대를 구분하는 신호 세 가지 이상을 들 수 있는가. 예제 참고 시 왜 세대 확인이 먼저인가.
  • SDF를 직접 다뤄야 하는 상황 여섯 가지를 들 수 있는가.
  • 폐쇄 사슬 로봇에서 필요에 따른 세 가지 접근을 구분할 수 있는가.
  • <mimic> 관절을 URDF 쪽 소비자 셋은 각각 어떻게 처리하는가. 확인되지 않은 경로는 어디인가(5.6.11).

다음 장 5.7은 제5부의 마지막 — 로봇 모델 품질 검증이다. check_urdf가 포착하지 못하는 것들을 어떻게 체계적으로 걸러내고, 그 검증을 CI에 넣는가.