제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 관절 종류
| 종류 | 자유도 | 설명 | 용도 |
|---|---|---|---|
fixed | 0 | 고정 | 센서 장착, 구조 분할 |
revolute | 1 | 회전, 한계 있음 | 대부분의 관절 |
continuous | 1 | 회전, 한계 없음 | 바퀴 |
prismatic | 1 | 직선 이동 | 리니어 액추에이터 |
floating | 6 | 자유 | 거의 안 씀 |
planar | 2 | 평면 이동 | 거의 안 씀 |
<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·upper | rad(revolute) / m(prismatic) | 위치 한계 |
effort | N·m / N | 최대 토크·힘 |
velocity | rad/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> | 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_description | URDF·xacro·메시·RViz 설정 |
my_robot_bringup | launch·파라미터 |
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> | Gazebo | 10.2·10.3 |
<ros2_control> | controller_manager | 6.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=100vsixx=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>를 부모 기준으로 이해 | 관절이 반대로 | 자식 프레임 기준 |
| 6 | revolute에 <limit> 누락 | 파싱 실패·안 움직임 | 필수 속성 |
| 7 | 바퀴를 revolute로 | 한 바퀴에서 걸림 | continuous |
| 8 | 충돌 기하에 상세 메시 | 계획·시뮬 급격히 느림 | 원시 도형 근사 |
| 9 | 충돌을 시각보다 작게 | 실제 충돌 발생 | 약간 크게 |
| 10 | 메시 설치 규칙 누락 | 배포본에서 안 보임 | install(DIRECTORY meshes) |
| 11 | 확장 태그 링크명 오타 | 조용히 무시 | 이름 대조 |
| 12 | effort·velocity를 임의 값으로 | 시뮬과 실기 불일치 | 실제 사양 |
| 13 | 폐쇄 사슬을 URDF로 시도 | 표현 불가 | 근사 또는 SDF(5.6) |
| 14 | check_urdf 통과를 완전 검증으로 오해 | 물리 문제 잔존 | 추가 검증(5.7) |
5.3.11 체크포인트
- URDF의 기본 규칙 네 가지를 말할 수 있는가.
<origin>의 의미가 관절과 기하에서 어떻게 다른가. - 관절 종류 여섯 가지와 각각의 용도를 말할 수 있는가.
continuous와revolute를 혼동하면 무슨 일이 생기는가. <axis>는 어느 프레임 기준인가. 잘못 이해하면 어떤 증상이 나타나는가.- 세 가지 기하가 분리된 이유와 각각의 요구를 설명할 수 있는가. 충돌 기하에 상세 메시를 쓰면 무엇이 악화되는가.
- 직육면체와 원통의 관성 모멘트를 쓸 수 있는가. 삼각 부등식 조건은 무엇을 의미하는가.
- 관성 관련 흔한 오류 다섯 가지와, "근사값으로 넣으면 안 되는" 이유를 말할 수 있는가.
package://URI가 어떻게 해석되는가. 메시가 배포본에서 안 보이는 원인은?- 로봇 기술 패키지를 분리하는 이유는 무엇이며, 어떤 원칙과 같은가.
- URDF 파서가 모르는 태그를 무시한다는 사실의 이점과 함정은?
- URDF가 표현하지 못하는 것 여섯 가지를 들 수 있는가. 폐쇄 사슬의 우회 방법 네 가지는?
check_urdf가 확인하는 것과 확인하지 못하는 것을 구분할 수 있는가.- 로봇 모델을 가장 빠르게 시각 검증하는 방법은?
다음 장 5.4는 URDF의 실무적 한계 — 장황함과 반복을 푸는 도구인 xacro를 다룬다. 매크로·속성·조건부 블록으로 로봇 모델을 조립 가능한 모듈로 만드는 법.