AEGLOSLAB ROS 2 Jazzy 대전

제5부 좌표·모델

5.3 URDF 심층

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

URDF(Unified Robot Description Format)는 로봇의 물리적 구조를 XML로 기술하는 형식이다. tf 트리(5.1)가 어디서 오는지, 시뮬레이터가 무엇을 그리고 무엇에 힘을 가하는지, MoveIt 2가 무엇과 충돌을 검사하는지 — 전부 이 파일에서 온다.

그래서 URDF는 로봇 시스템의 단일 출처가 되어야 하는데, 실무에서는 부정확하게 만들어져 나중에 문제를 일으키는 경우가 많다. 관성이 엉터리라 시뮬이 발산하고, 충돌 기하가 메시라 계획이 느리고, 축 방향이 틀려 관절이 반대로 회전한다.

배우는 것:

  • 구조 — 링크와 관절, 그리고 트리 제약.
  • 관절 종류 여섯 가지 — 각각의 자유도와 용도.
  • <visual>·<collision>·<inertial> — 세 기하가 왜 따로인가.
  • 관성 — 왜 중요하고, 어떻게 계산하고, 무엇을 틀리는가.
  • 메시와 package:// — 자원 참조 방식.
  • 확장 태그 — Gazebo·ros2_control이 URDF에 추가하는 것.
  • URDF가 표현하지 못하는 것 — 폐쇄 사슬·센서·세계.

선수 지식: 5.1(tf 트리·REP 103 좌표 규약), 4.1.4(package:// 경로와 설치). 관성·강체 동역학 이론은 자매서 robotics(RB-63·RB-66)가 다루며, 여기서는 URDF에 무엇을 어떻게 적는가에 집중한다(BK-04).

정직: URDF 스펙은 오랫동안 안정적이지만, 파서 구현마다 관대함이 다르다(어떤 것은 경고, 어떤 것은 오류). 아래 서술은 URDF 명세와 urdfdom 동작에 근거하며 이 환경에서 검증하지 않았다(목차 3). 시뮬레이터별 확장 태그는 해당 프로젝트 문서가 정본이다.(→ 165차 확인: check_urdf(urdfdom)·mimic·transmission 관련 핵심 서술을 Jazzy에서 실제로 검증했다 — 5.3.3·5.3.7·5.3.9 실행 확인(165차) 참고. 관성 계산·xacro·시뮬레이터별 확장은 여전히 검증하지 않았다.)

5.3.2 구조 — 링크와 관절

URDF는 링크(강체)를 관절로 이은 트리다.

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

  <link name="base_link">
    <visual>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <geometry><box size="0.4 0.3 0.1"/></geometry>
      <material name="grey"><color rgba="0.5 0.5 0.5 1"/></material>
    </visual>
    <collision>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <geometry><box size="0.4 0.3 0.1"/></geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0.05" rpy="0 0 0"/>
      <mass value="5.0"/>
      <inertia ixx="0.05" ixy="0" ixz="0" iyy="0.08" iyz="0" izz="0.10"/>
    </inertial>
  </link>

  <link name="laser_link">
    <visual>
      <geometry><cylinder radius="0.04" length="0.05"/></geometry>
    </visual>
  </link>

  <joint name="laser_joint" type="fixed">
    <origin xyz="0.15 0 0.12" rpy="0 0 0"/>
    <parent link="base_link"/>
    <child link="laser_link"/>
  </joint>

</robot>

핵심 규칙:

규칙설명
트리여야 한다링크마다 부모 관절 하나. 순환 금지(5.1.4와 같은 제약)
루트 링크부모가 없는 링크가 정확히 하나
관절이 관계를 정의<origin>이 부모→자식 변환
링크는 강체변형하지 않는다

<origin>의 의미가 관절과 기하에서 다르다:

위치의미
<joint><origin>부모 링크 원점 → 자식 링크 원점의 변환(관절이 0일 때)
<visual><origin>링크 원점 → 시각 기하의 변환
<collision><origin>링크 원점 → 충돌 기하의 변환
<inertial><origin>링크 원점 → 질량중심의 변환

rpy는 고정축 roll-pitch-yaw(x-y-z 순)이며 라디안이다(REP 103).

함정: 링크의 원점 위치를 어디로 설정할지가 나중의 편의를 좌우한다. 관절 축 위에 두면 관절 회전이 직관적이고, 기하 중심에 두면 시각·충돌 정의가 간단하다. 관절 축 기준을 권한다 — 관절 값의 의미가 명확해지고, <visual>·<collision>의 <origin>으로 기하를 옮기면 된다.

5.3.3 관절 종류

종류자유도설명용도
fixed0고정센서 장착, 구조 분할
revolute1회전, 한계 있음대부분의 관절
continuous1회전, 한계 없음바퀴
prismatic1직선 이동리니어 액추에이터
floating6자유거의 안 씀
planar2평면 이동거의 안 씀
<joint name="shoulder_pan" type="revolute">
  <origin xyz="0 0 0.15" rpy="0 0 0"/>
  <parent link="base_link"/>
  <child link="shoulder_link"/>
  <axis xyz="0 0 1"/>                    <!-- 회전축 (자식 프레임 기준) -->
  <limit lower="-3.14" upper="3.14"
         effort="100.0" velocity="2.0"/>  <!-- 필수 (revolute·prismatic) -->
  <dynamics damping="0.1" friction="0.05"/>
</joint>

<limit>의 네 값:

속성단위의미
lower·upperrad(revolute) / m(prismatic)위치 한계
effortN·m / N최대 토크·힘
velocityrad/s / m/s최대 속도

revolute에는 <limit>이 필수다. 없으면 파서가 거부하거나 기본값(0)을 써서 관절이 안 움직인다.

함정: continuous와 revolute를 혼동하면 문제가 생긴다. 바퀴를 revolute로 하고 한계를 ±π로 주면, 한 바퀴 회전할 때 한계에 걸린다. 반대로 팔 관절을 continuous로 하면 케이블이 꼬이는 것을 모델이 막지 못한다.

함정: effort·velocity는 시뮬레이션과 ros2_control에서 실제로 쓰인다(6.5·10.4). 임의의 큰 값을 넣으면 시뮬에서 비현실적으로 강한 로봇이 되고, 실기와 다르게 동작한다. 실제 사양을 넣으라.

<axis>는 자식 링크 프레임 기준이다. 이것을 부모 기준으로 착각하는 실수가 흔하고, 결과는 관절이 잘못된 방향으로 회전하는 것이다.

<mimic> — 종속 관절:

<joint name="right_finger" type="prismatic">
  ...
  <mimic joint="left_finger" multiplier="-1.0" offset="0.0"/>
</joint>

그리퍼처럼 한 액추에이터가 두 관절을 움직일 때 쓴다. robot_state_publisher가 이를 반영해 tf를 계산한다(5.5). 다만 시뮬레이터·ros2_control이 mimic을 어떻게 처리하는지는 구현마다 다르다(확인 필요)(→ 165차 확인: robot_state_publisher의 tf 계산은 실측으로 확정했고, ros2_control은 소스 레벨의 1급 구조적 지원을 확인했다 — 아래 실행 확인(165차) 참고. 시뮬레이터별 처리는 여전히 미확인)(→ 166차 확인: ros2_control은 소스 확인에 그치지 않고 mock_components/GenericSystem으로 실제 런타임까지 확정했다 — 아래 실행 확인(165차) 블록의 166차 추가분 참고).

실행 확인(165차, Jazzy) — <mimic> 관절 처리를 두 층위에서 확인했다.

  • robot_state_publisher의 tf 계산 — 실측으로 확정. left_finger_joint(구동)·right_finger_joint(mimic, multiplier="-1.0" offset="0.0")를 가진 URDF로 robot_state_publisher를 실행하고, left_finger_joint의 JointState만 0.03으로 계속 발행했다(right_finger_joint는 JointState를 한 번도 발행하지 않음). tf2_echo로 확인한 right_finger의 x축 변위는 정확히 -0.030($-1.0\times0.03+0.0$, mimic 공식과 완전히 일치) — mimic된 관절은 자신의 JointState 항목이 없어도 계산됨을 함께 확인했다.
  • ros2_control — 소스 레벨의 1급 구조적 지원, 166차에 런타임까지 확정. Jazzy에 설치된 hardware_interface/hardware_info.hpp에 MimicJoint 구조체(joint_index·mimicked_joint_index·multiplier·offset)와 HardwareInfo::mimic_joints 벡터가 실재한다. 공식 테스트 픽스처(ros2_control_test_assets/descriptions.hpp)에는 <ros2_control><joint mimic="true"> 형태의 전용 XML 속성 예제가 여러 개 있다. 166차에 mock_components/GenericSystem으로 실제 controller_manager를 기동해 런타임까지 확인했다 — right_finger_joint(구동, position 커맨드)·left_finger_joint(mimic, multiplier=2 offset=0.01)로 실제 하드웨어를 활성화하고 joint_state_broadcaster+forward_position_controller를 스폰한 뒤, right_finger_joint에 0.1을 커맨드하자 /joint_states의 left_finger_joint가 정확히 0.21000000000000002($2\times0.1+0.01$, 부동소수 오차만)로 반영됐다 — GenericSystem이 mimic의 multiplier·offset을 하드웨어 인터페이스 레벨에서 자동 적용함을 실측으로 확정했다. 원본: tools/gap_harness/gap166/r2c_test/(gripper.urdf·controllers.yaml·joint_states_before.log·joint_states_after.log).

컨테이너 gap165j, ROS_DOMAIN_ID=70. 원본: tools/gap_harness/gap165/urdf_test/(mimic.urdf·rsp_mimic.log·tf_right_finger.log·mimic_include_search.log).

5.3.4 세 가지 기하 — 왜 따로인가

링크 하나 visual 보기 위한 것 collision 충돌 판정용 inertial 동역학 계산용 상세 메시 OK RViz2 · 시뮬 렌더 단순 도형 권장 계획 · 물리 엔진 질량 · 관성 텐서 시뮬 · 동역학
기하소비자요구
<visual>RViz2(11.4), 시뮬 렌더정확한 외형. 성능 여유 있음
<collision>MoveIt 2(9.3), 물리 엔진, 코스트맵속도. 단순할수록 좋다
<inertial>물리 엔진, 동역학 제어물리적 타당성

이 분리가 URDF의 중요한 설계다. 충돌 기하에 상세 메시를 쓰면 계획·시뮬이 급격히 느려진다.

충돌 기하판정 비용정확도
<box>·<sphere>·<cylinder>매우 낮음낮음
볼록 메시(convex hull)낮음중간
비볼록 상세 메시매우 높음높음

견해: 충돌 기하는 원시 도형으로 근사하는 것을 기본으로 하라. 팔 링크는 실린더, 몸체는 박스로 충분한 경우가 많다. 정밀한 충돌이 필요한 부분(그리퍼 손가락·좁은 통과)만 메시를 쓰고, 그때도 볼록 분해(convex decomposition) 를 거친다. MoveIt 2의 계획 속도가 충돌 검사에 지배되는 경우가 많다(9.3).

함정: 충돌 기하를 시각 기하보다 약간 크게 설정하는 것이 안전 여유가 된다. 반대로 작게 설정하면 실제로 부딪히는데 계획은 통과시킨다.

<visual>·<collision>이 없어도 된다:

생략결과
<visual> 없음RViz2에 안 보임. tf는 정상
<collision> 없음충돌 검사에서 무시됨
<inertial> 없음시뮬레이터가 기본값 적용 — 대개 문제를 일으킴

5.3.5 관성 — 가장 자주 틀리는 부분

<inertial>
  <origin xyz="0 0 0.05" rpy="0 0 0"/>     <!-- 질량중심 위치 -->
  <mass value="5.0"/>                       <!-- kg -->
  <inertia ixx="0.05" ixy="0.0" ixz="0.0"
                      iyy="0.08" iyz="0.0"
                                 izz="0.10"/>
</inertial>

관성 텐서는 질량중심을 원점으로, <origin>의 회전 기준으로 표현한다. 단위는 kg·m².

기본 도형의 관성 모멘트:

도형$I_{xx}$$I_{yy}$$I_{zz}$
직육면체 ($w \times d \times h$)$\frac{m}{12}(d^2+h^2)$$\frac{m}{12}(w^2+h^2)$$\frac{m}{12}(w^2+d^2)$
원통 (반지름 $r$, 높이 $h$, z축)$\frac{m}{12}(3r^2+h^2)$같음$\frac{m}{2}r^2$
구 (반지름 $r$)$\frac{2}{5}mr^2$같음같음

흔한 오류 다섯 가지:

오류증상
질량이 0 또는 누락시뮬레이터가 링크를 무시하거나 발산
관성이 0물리 엔진 수치 불안정
관성 값이 비현실적으로 작음시뮬에서 링크가 진동·발산
질량중심이 링크 밖비정상적인 동역학
단위 혼동(g vs kg)1000배 오차

함정: "관성은 시뮬레이션용이니까 대략적으로"이 가장 비용이 큰 실수 중 하나다. 시뮬이 발산하거나(로봇이 튀어 날아감), 제어 게인을 시뮬에서 튜닝했는데 실기에서 전혀 안 맞는다. CAD에서 산출하거나, 최소한 기본 도형 근사로 계산하라.

관성 계산 방법:

방법정확도비용
CAD에서 추출높음CAD 모델 필요
기본 도형 근사중간낮음 — 대부분 충분
메시에서 자동 계산중간도구 필요
실측(진자 실험 등)높음시간

타당성 검사(삼각 부등식): 관성 텐서의 주모멘트는 다음을 만족해야 한다.

$$I_{xx} + I_{yy} \geq I_{zz}, \quad I_{yy} + I_{zz} \geq I_{xx}, \quad I_{zz} + I_{xx} \geq I_{yy}$$

이를 위반하면 물리적으로 불가능한 강체다. 자동 검사로 걸러낼 수 있으며, 5.7에서 검증 도구로 다룬다.

견해: 개발 초기에는 기본 도형 근사로 충분하고, 시뮬 기반 제어 튜닝을 시작하는 시점에 정확도를 높이라. 처음부터 CAD 추출을 요구하면 진도가 안 나가고, 끝까지 부정확하게 두면 Sim2Real 간극이 커진다(10.6).

5.3.6 메시와 자원 참조

<visual>
  <geometry>
    <mesh filename="package://my_robot_description/meshes/base.dae"
          scale="0.001 0.001 0.001"/>
  </geometry>
</visual>

package:// URI가 ROS의 자원 참조 방식이다. ament 인덱스로 패키지 위치를 찾아 해석한다(4.1.3).

형식지원비고
.dae(COLLADA)널리 지원재질·색상 포함
.stl널리 지원색상 없음. 단순
.obj도구에 따라(확인 필요)(→ 165차 확인: urdfdom/check_urdf 자체는 메시 확장자를 전혀 검사하지 않는다 — 아래 실행 확인(165차) 참고. "지원 여부"는 전적으로 소비 도구(RViz2·Gazebo의 assimp 로더)에 달렸다)

scale이 자주 문제가 된다. CAD는 대개 mm 단위인데 URDF는 m이므로 0.001 배율이 필요하다. 이것을 빠뜨리면 1000배 큰 로봇이 나온다.

함정: 메시 파일을 설치 규칙에 넣는 것을 잊으면 배포본에서 안 보인다(4.1.4).

install(DIRECTORY meshes urdf launch rviz
  DESTINATION share/${PROJECT_NAME})

로봇 기술 패키지를 분리하는 것이 관행이다:

패키지내용
my_robot_descriptionURDF·xacro·메시·RViz 설정
my_robot_bringuplaunch·파라미터
my_robot_driver하드웨어 인터페이스

분리 이유는 인터페이스 패키지 분리와 같다(1.2.5) — 기술 파일만 필요한 쪽(시뮬·MoveIt 설정·시각화)이 드라이버 의존을 끌어오지 않게.

5.3.7 확장 태그

URDF는 다른 도구가 자기 정보를 추가할 수 있게 열려 있다.

<!-- Gazebo 확장 (10.2) -->
<gazebo reference="base_link">
  <material>Gazebo/Grey</material>
  <mu1>0.8</mu1>
  <mu2>0.8</mu2>
</gazebo>

<!-- ros2_control 확장 (6.1) -->
<ros2_control name="MyRobotSystem" type="system">
  <hardware>
    <plugin>my_robot_driver/MyRobotHardware</plugin>
    <param name="device">/dev/ttyUSB0</param>
  </hardware>
  <joint name="shoulder_pan">
    <command_interface name="position">
      <param name="min">-3.14</param>
      <param name="max">3.14</param>
    </command_interface>
    <state_interface name="position"/>
    <state_interface name="velocity"/>
  </joint>
</ros2_control>
확장소비자관련 장
<gazebo>Gazebo10.2·10.3
<ros2_control>controller_manager6.1~6.5
<transmission>구형 방식(확인 필요: Jazzy에서의 위치)(→ 165차 정정: "폐기"가 아니라 "이전·재설계"다 — 아래 실행 확인(165차) 참고)6.1
MoveIt SRDF별도 파일9.1

URDF 파서는 모르는 태그를 무시한다. 그래서 확장이 가능하고, 동시에 오타가 조용히 무시되는 함정도 생긴다.

함정: <gazebo reference="base_lnk">처럼 링크 이름에 오타가 있어도 오류가 나지 않는다. 단지 적용이 안 될 뿐이다. 마찰 계수가 적용되지 않는 원인이 이런 오타인 경우가 있다.

견해: 확장 태그가 늘어나면 URDF가 거대해진다. xacro로 분리하는 것이 필수적이며(5.4), 시뮬 전용 설정과 실기 설정을 조건부로 나누는 구조가 흔하다.

실행 확인(165차, Jazzy) — <transmission>의 Jazzy 내 위치를 확인했다. ros-jazzy-transmission-interface(버전 4.45.2)가 실재·설치 가능함을 확인했고, hardware_interface/hardware_info.hpp에 TransmissionInfo(name·type·joints·actuators) 구조체가 있음을 확인했다. 공식 테스트 픽스처(ros2_control_test_assets/descriptions.hpp)에서 <transmission>이 <ros2_control> 블록 안에 중첩되어 <plugin>transmission_interface/SimpleTransmission</plugin>·DifferentialTransmission·FourBarLinkageTransmission 같은 플러그인 클래스와 <actuator>/<joint role="..."> 서브요소로 쓰이는 실제 예제를 확인했다 — 기어비·차동·4절 링크처럼 액추에이터↔관절 공간이 1:1이 아닌 경우를 위한 현역 기능이다. "구형 방식"이라는 표는 ROS 1 시절의 독립 최상위 <transmission> 태그(pr2_mechanism/ros_control)를 가리킨다면 맞지만, Jazzy에서는 <ros2_control> 블록 안으로 위치를 옮겨 스키마가 완전히 바뀐 채로 계속 쓰이고 있다 — "폐기"가 아니라 "이전·재설계"가 정확한 표현이다. 원본: tools/gap_harness/gap165/urdf_test/transmission_search.log·transmission_test_assets.log.

5.3.8 URDF가 표현하지 못하는 것

이 절이 URDF를 이해하는 데 중요하다.

표현 불가설명대안
폐쇄 사슬트리만 가능 — 4절 링크·병렬 로봇·델타 로봇SDF(5.6), 또는 근사
센서표준 URDF에 센서 개념 없음<gazebo> 확장, SDF
세계·환경로봇 하나만 기술SDF world
다중 로봇하나의 <robot>별도 파일 + 네임스페이스
변형체강체만—
동적 재구성정적 파일런타임 재발행(확인 필요)(→ 165차: 실제로 시도했으나 결론 내지 못함 — 아래 실행 확인(165차) 참고, 미확인 유지)(→ 166차 확인: 원인을 분리해 지원됨을 확정했다 — 아래 실행 확인(165차) 블록의 166차 추가분 참고)

폐쇄 사슬 제약이 실무에서 가장 피해가 크다. 4절 링크 기구, 병렬 그리퍼, 차동 구동 이외의 서스펜션 — 전부 트리로 표현되지 않는다.

우회 방법:

방법설명대가
<mimic> 관절종속 관계로 근사(5.3.3)실제 역학과 다름
사슬을 끊어 트리로시각·tf 목적에는 충분물리적으로 부정확
SDF 사용폐쇄 사슬 지원URDF 생태계와 분리(5.6)
시뮬에서 조인트 추가Gazebo 플러그인으로 사슬 닫기시뮬 전용

견해: "tf와 시각화만 필요하면 사슬을 끊어 트리로 근사"하는 것이 실용적이다. 동역학이 정확할 필요가 없으면(운동학 계산·RViz 표시) 충분하고, 정밀 시뮬이 필요할 때만 SDF나 시뮬 전용 처리를 추가한다.

실행 확인(165차, Jazzy) — "동적 재구성"(런타임 robot_description 교체)을 시도했으나 결론을 못 냈다. 정직하게 미해결로 남긴다. 새 링크를 추가한 URDF로 ros2 param set /robot_state_publisher robot_description <새 URDF>를 실행했더니, robot_state_publisher가 재파싱 중 libsdformat_urdf_plugin.so 로드를 시도하다 libsdformat14.so.14를 찾을 수 없음 오류로 실패했다 — 파라미터 값 자체가 갱신되지 않았고(새 링크 이름이 조회 결과에 없음) tf에도 새 프레임이 나타나지 않았다. libsdformat14는 표준 ROS apt 저장소가 아니라 Gazebo 저장소 패키지라 이 최소 컨테이너(ros:jazzy-ros-base)에는 없었다. 이 실패가 "런타임 재구성 자체가 안 된다"를 뜻하는지, 단순히 이 컨테이너의 의존성 누락 때문에 재파싱 경로 전체가 막힌 것인지 분리하지 못했다 — 위 표의 미확인 표시는 닫지 않고 그대로 둔다. 원본: tools/gap_harness/gap165/urdf_test/param_set_result.log·rsp_mimic.log.

실행 확인(166차, Jazzy) — 원인을 분리해 "동적 재구성"이 지원됨을 확정했다. OSRF Gazebo apt 저장소를 추가해 libsdformat14(14.9.0-1~noble)를 설치한 뒤 재시도했다. 새 링크(new_link_v2)를 추가한 URDF로 ros2 param set /robot_state_publisher robot_description <새 URDF>를 실행하니 즉시 "Set parameter successful"을 반환했고, robot_state_publisher 로그에 "Robot initialized"가 두 번(최초 기동 + 파라미터 교체 후) 출력돼 실제로 재파싱이 일어났음을 확인했다. tf2_echo base_link new_link_v2로 새 프레임이 실제로 존재함을 확인했다(translation [0,0,0], 고정 관절의 기본 origin과 일치). 165차의 실패는 기능 자체의 한계가 아니라 최소 컨테이너(ros:jazzy-ros-base)에 libsdformat14가 빠져 있던 순수한 환경 문제였다 — "기능 미지원"과 "의존성 누락" 두 원인을 정확히 분리했다. 원본: tools/gap_harness/gap166/dynreconfig_test/(v1.urdf·v2.urdf·param_set_v2.log·tf_new_link_v2.log·rsp_v1.log).

5.3.9 검증

# 문법·구조 검사 (가장 기본)
check_urdf my_robot.urdf

# 트리 시각화
urdf_to_graphiz my_robot.urdf
# → my_robot.gv, my_robot.pdf 생성

# xacro 전개 후 검사 (5.4)
xacro my_robot.urdf.xacro > /tmp/robot.urdf && check_urdf /tmp/robot.urdf

# RViz2에서 시각 확인
ros2 launch my_robot_description display.launch.py

check_urdf가 확인하는 것:

검사내용
XML 문법파싱 가능한가
트리 구조루트가 하나인가, 순환이 없는가
참조 무결성관절이 가리키는 링크가 존재하는가
필수 속성revolute의 <limit> 등

check_urdf가 확인하지 못하는 것 — 이것이 중요하다:

미검사결과
관성의 물리적 타당성시뮬 발산
질량·치수의 현실성잘못된 동역학
축 방향의 의도 일치관절이 반대로
메시 파일 존재런타임에 안 보임
확장 태그 오타조용히 무시
충돌 기하 복잡도성능 저하

그래서 별도 검증이 필요하며, 5.7에서 체계적으로 다룬다.

실행 확인(165차, Jazzy) — check_urdf가 확인하는 것/못하는 것을 7개의 의도적으로 깨진 URDF로 결정적으로 확인했다. 정상 URDF가 통과함을 먼저 확인한 뒤:

변형무엇을 깨뜨렸나결과
revolute에 <limit> 누락필수 속성실패(EXIT 255, "does not specify limits")
관성 삼각부등식 대폭 위반(izz=100 vs ixx=iyy=0.001)물리적 타당성통과(EXIT 0, 조용히)
질량 음수(-5.0)질량 현실성통과(EXIT 0, 조용히)
존재하지 않는 메시 파일 참조메시 존재통과(EXIT 0, 조용히)
관절이 존재하지 않는 자식 링크 참조참조 무결성실패(EXIT 255, "child link ... not found")
루트 링크 둘(분리된 트리)트리 구조실패(EXIT 255, "Two root links found")
a→b→c→a 순환트리 구조실패(EXIT 255, "No root link found... not a valid tree")

위 두 표(확인하는 것/못하는 것)가 7항목 전부 정확히 실측과 일치했다. 부수 발견 — revolute의 <limit> 누락은 5.3.3이 "거부하거나 기본값(0)을 써서"라고 적은 것 중 이번 실측에서는 항상 거부만 관찰됐다(urdfdom 파서 레벨에서 "기본값 처리" 분기는 못 찾음 — 다른 소비자·구버전에 그런 동작이 있었을 가능성은 열어 둔다). 원본: tools/gap_harness/gap165/urdf_test/all_checks.log(7개 변형 전체 원시 출력), *.urdf(변형 파일 7종).

시각 확인용 launch:

def generate_launch_description():
    pkg = get_package_share_directory('my_robot_description')
    urdf = os.path.join(pkg, 'urdf', 'my_robot.urdf.xacro')
    robot_desc = xacro.process_file(urdf).toxml()

    return LaunchDescription([
        Node(package='robot_state_publisher', executable='robot_state_publisher',
             parameters=[{'robot_description': robot_desc}]),
        Node(package='joint_state_publisher_gui',
             executable='joint_state_publisher_gui'),
        Node(package='rviz2', executable='rviz2',
             arguments=['-d', os.path.join(pkg, 'rviz', 'display.rviz')]),
    ])

joint_state_publisher_gui로 관절을 움직여 보는 것이 가장 빠른 검증이다 — 축 방향·한계·기하가 의도대로인지 즉시 보인다(5.5).

5.3.10 함정 카탈로그

#함정증상대처
1<inertial> 누락·0시뮬 발산·링크 무시계산해서 넣기
2관성 삼각 부등식 위반물리적으로 불가능검증(5.3.5)
3단위 혼동(mm·g)1000배 오차m·kg·rad 확인
4메시 scale 누락거대한 로봇0.001 배율
5<axis>를 부모 기준으로 이해관절이 반대로자식 프레임 기준
6revolute에 <limit> 누락파싱 실패·안 움직임필수 속성
7바퀴를 revolute로한 바퀴에서 걸림continuous
8충돌 기하에 상세 메시계획·시뮬 급격히 느림원시 도형 근사
9충돌을 시각보다 작게실제 충돌 발생약간 크게
10메시 설치 규칙 누락배포본에서 안 보임install(DIRECTORY meshes)
11확장 태그 링크명 오타조용히 무시이름 대조
12effort·velocity를 임의 값으로시뮬과 실기 불일치실제 사양
13폐쇄 사슬을 URDF로 시도표현 불가근사 또는 SDF(5.6)
14check_urdf 통과를 완전 검증으로 오해물리 문제 잔존추가 검증(5.7)

5.3.11 체크포인트

  • URDF의 기본 규칙 네 가지를 말할 수 있는가. <origin>의 의미가 관절과 기하에서 어떻게 다른가.
  • 관절 종류 여섯 가지와 각각의 용도를 말할 수 있는가. continuous와 revolute를 혼동하면 무슨 일이 생기는가.
  • <axis>는 어느 프레임 기준인가. 잘못 이해하면 어떤 증상이 나타나는가.
  • 세 가지 기하가 분리된 이유와 각각의 요구를 설명할 수 있는가. 충돌 기하에 상세 메시를 쓰면 무엇이 악화되는가.
  • 직육면체와 원통의 관성 모멘트를 쓸 수 있는가. 삼각 부등식 조건은 무엇을 의미하는가.
  • 관성 관련 흔한 오류 다섯 가지와, "근사값으로 넣으면 안 되는" 이유를 말할 수 있는가.
  • package:// URI가 어떻게 해석되는가. 메시가 배포본에서 안 보이는 원인은?
  • 로봇 기술 패키지를 분리하는 이유는 무엇이며, 어떤 원칙과 같은가.
  • URDF 파서가 모르는 태그를 무시한다는 사실의 이점과 함정은?
  • URDF가 표현하지 못하는 것 여섯 가지를 들 수 있는가. 폐쇄 사슬의 우회 방법 네 가지는?
  • check_urdf가 확인하는 것과 확인하지 못하는 것을 구분할 수 있는가.
  • 로봇 모델을 가장 빠르게 시각 검증하는 방법은?

다음 장 5.4는 URDF의 실무적 한계 — 장황함과 반복을 푸는 도구인 xacro를 다룬다. 매크로·속성·조건부 블록으로 로봇 모델을 조립 가능한 모듈로 만드는 법.