# Xacro를 이용한 반복 구조 간소화

#### Xacro란?

Xacro(XML Macros)는 ROS에서 URDF를 보다 유연하고 간결하게 작성하기 위해 사용되는 템플릿 시스템이다. URDF(Universal Robot Description Format)는 로봇 모델을 정의하기 위한 XML 기반의 표준 형식이지만, 복잡한 로봇일수록 수많은 반복 구문과 중복된 코드가 발생하기 쉽다. 이러한 중복을 최소화하고 유지보수를 쉽게 만들기 위해 Xacro가 도입되었다.

Xacro 파일은 일반적인 XML 문법을 유지하지만, 매크로나 변수, 조건문, 반복문 등을 지원한다. 이를 통해 유사한 구조를 여러 번 작성할 필요 없이 반복을 자동화하거나, 특정 조건에서만 로봇 요소를 생성하는 로직을 손쉽게 정의할 수 있다.

#### 반복 구조가 필요한 이유

복잡한 로봇 모델에서는 다음과 같은 이유로 반복 구조가 빈번히 등장한다.

* 동일한 형상과 조인트 특성을 갖는 링크가 여러 개 존재하는 경우(예: 로봇 바퀴나 로봇팔의 유사 조인트 구조).
* 일정 간격을 두고 같은 구조를 나열해야 하는 경우(예: 컨베이어 벨트 위의 롤러 구조).
* 여러 옵션(센서, 액추에이터 등)을 유사한 형태로 달아야 하는 경우.

만약 URDF에서 직접 이러한 반복 구조를 모두 기술한다면, 단순한 텍스트의 복사-붙여넣기 과정이 계속되어 파일이 매우 길어지고 오류가 발생할 가능성이 높다. 또한 부품별로 약간의 파라미터만 다른 경우(링크 길이, 질량, 조인트 제한 각도 등)에도 모든 항목을 일일이 수정해야 하므로 작업 부담이 커진다.

#### Xacro의 주요 기능 요약

* **매크로 정의**: `<xacro:macro>`를 활용하여 반복되는 코드를 템플릿 형태로 만들 수 있다. 매크로는 인자를 받을 수 있어 링크 길이, 질량, 조인트 제한 등 파라미터화가 가능하다.
* **변수 정의**: `<xacro:property>`를 통해 변수(상수)를 설정할 수 있다. 수식을 넣어 계산 결과를 변수로 담을 수도 있다.
* **조건문과 반복문**: `<xacro:if>`, `<xacro:unless>`, `<xacro:for each>`등을 통해 조건과 반복 구조를 쉽게 정의할 수 있다.
* **Include(포함)**: `<xacro:include>`를 통해 다른 Xacro 파일을 포함할 수 있어, 로봇 모델 구성을 모듈화할 수 있다.

#### Xacro 매크로를 통한 반복 구조 개념

Xacro 매크로는 특정 XML 구조를 템플릿처럼 묶어두고 인자로 변수를 받아 재사용할 수 있게 한다. 예를 들어 다음과 같은 조인트 6개가 반복되는 로봇팔을 생각해 보자.

* 각 조인트는 동일한 형상을 갖는다.
* 연결되는 링크 간 크기가 약간씩 다르지만, 기본 뼈대는 같다.
* 질량이나 관성 같은 값은 링크마다 다를 수 있다.

이 경우 URDF만 사용한다면 매번 `<link>`와 `<joint>`를 반복해서 작성해야 하지만, Xacro 매크로를 활용하면 매번 중복되는 구조를 간결하게 반복하여 사용할 수 있다.

**매크로 문법 예시**

```xml
<xacro:macro name="my_link" params="link_name length mass">
  <link name="${link_name}">
    <!-- 필요하다면 충돌(collision), 시각(visual), 관성(inertial) 요소를 정의 -->
    <inertial>
      <mass value="${mass}" />
      <!-- 관성 텐서 등은 파라미터나 고정값으로 지정 가능 -->
      <origin xyz="0 0 ${length/2}" rpy="0 0 0"/>
      <inertia 
        ixx="1" ixy="0" ixz="0"
        iyy="1" iyz="0"
        izz="1"/>
    </inertial>
  </link>
</xacro:macro>
```

위의 Xacro 매크로는 링크 이름, 길이, 질량 등을 인자로 받아 링크를 생성한다. 이제 이 매크로를 여러 번 호출함으로써 반복되는 링크 구조를 간단히 정의할 수 있다.

#### 반복문 사용

Xacro는 `<xacro:for each="..." in="...">` 구문을 지원하여 반복 구조를 단순화할 수 있다. 예를 들어, 3개의 바퀴를 자동으로 생성하고 싶다면 다음과 같이 할 수 있다.

```xml
<xacro:property name="wheel_names" value="${['wheel_left','wheel_right','wheel_middle']}"/>
<xacro:property name="wheel_radius" value="0.1"/>
<xacro:property name="wheel_width" value="0.05"/>

<xacro:for each="w" in="${wheel_names}">
  <link name="${w}">
    <!-- 바퀴의 visual, collision, inertial 요소 등을 정의 -->
    <visual>
      <geometry>
        <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
      </geometry>
    </visual>
  </link>
  <!-- joint 정의 등 추가 가능 -->
</xacro:for>
```

이렇게 정의하면 `wheel_left`, `wheel_right`, `wheel_middle`라는 세 바퀴 링크가 자동으로 생성되며, 바퀴 형상 역시 동일한 문법을 반복해서 넣을 필요가 없다.

#### 반복 구조에서의 좌표계 변환

로봇 모델에서는 반복 구조가 생성될 때, 매번 링크의 좌표 변환이나 조인트 원점(Origin)이 어떻게 설정되는지도 중요하다. 예를 들어 $\mathbf{T}\_{i}$가 $i$번째 링크를 기준 좌표계에서 나타내는 강체 변환 행렬이라고 할 때, 멀티 조인트 로봇에서 $i+1$번째 링크로의 좌표 변환은 일반적으로 아래와 같은 형태를 갖는다.

$$
\mathbf{T}\_{i \rightarrow i+1} =  \begin{bmatrix} \mathbf{R} & \mathbf{d} \ \mathbf{0}^\mathsf{T} & 1 \end{bmatrix}
$$

여기서 $\mathbf{R}$은 회전 행렬, $\mathbf{d}$는 평행 이동 벡터이다. Xacro에서 반복문을 사용해 링크와 조인트를 만들 때, 링크 간 변위와 회전 각도(예: $\theta\_i$) 등을 매크로 인자로 정의하면 자동으로 계산된 행렬을 각 링크에 적용하는 식으로 반복 패턴을 구성할 수 있다.

예를 들어, $\theta\_i$가 $i$번째 조인트 회전 각도라면, 다음과 같은 식으로 표현할 수 있다.

$$
\mathbf{R}\_z(\theta\_i) =  \begin{bmatrix} \cos \theta\_i & -\sin \theta\_i & 0 \ \sin \theta\_i & \cos \theta\_i & 0 \ 0 & 0 & 1 \end{bmatrix}
$$

이 식을 토대로 Xacro 내에서 $\theta\_i$를 변수로 받는 매크로를 정의하고, `<origin rpy="0 0 ${theta_i}"/>`와 같이 URDF 요소에 대입 가능하다.

#### 매크로와 반복문의 조합

실무에서는 매크로와 반복문을 조합하여 더욱 유연하게 로봇 모델을 생성한다. 예를 들어 매크로 안에서 `<xacro:for>`를 사용하거나, `<xacro:for>` 안에서 매크로를 호출함으로써 반복 구조를 한층 더 단순화할 수 있다.

다음은 3개의 바퀴를 가진 이동로봇 예시에서 바퀴를 자동 생성하는 매크로를 가정해보자.

```xml
<xacro:macro name="wheel_macro" params="wheel_name wheel_radius wheel_width">
  <link name="${wheel_name}">
    <visual>
      <geometry>
        <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
      </geometry>
    </visual>
    <collision>
      <geometry>
        <cylinder length="${wheel_width}" radius="${wheel_radius}"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="0.5"/>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
    </inertial>
  </link>
</xacro:macro>

<xacro:property name="wheel_names" value="${['wheel_left','wheel_right','wheel_back']}"/>
<xacro:property name="wheel_r" value="0.1"/>
<xacro:property name="wheel_w" value="0.05"/>

<xacro:for each="w" in="${wheel_names}">
  <xacro:wheel_macro wheel_name="${w}" wheel_radius="${wheel_r}" wheel_width="${wheel_w}"/>
</xacro:for>
```

이처럼 매크로(`wheel_macro`)를 정의하고, 반복문(`<xacro:for>`)에서 매크로를 호출함으로써 동일한 구조의 바퀴를 일괄 생성할 수 있다. 바퀴 개수가 바뀌더라도 `wheel_names`만 수정하면 되므로, 유지보수도 훨씬 용이해진다.

#### Xacro 인자(Argument)와 파라미터 연동

Xacro 매크로는 URDF 파일 내부에서 변수를 정의하고 활용하는 것뿐만 아니라, 외부에서 전달된 인자를 받아 로봇 모델을 동적으로 구성할 수도 있다. 이를 위해 ROS2 런칭 파일이나 명령줄에서 `xacro` 유틸리티를 호출할 때 인자를 넘겨줄 수 있다. 이렇게 하면 로봇의 길이, 질량, 링크 개수 등을 명령줄에서 한 번에 지정하여 다양한 로봇 구성 시나리오를 간단히 테스트할 수 있다.

예를 들어, 명령줄에서 다음과 같이 `xacro`를 실행하여 인자를 전달한다고 해보자.

```bash
xacro my_robot.xacro arm_length:=1.2 joint_count:=6
```

이 경우 Xacro 파일 내에서는 전달받은 `arm_length`와 `joint_count`를 사용할 수 있다. 다음은 이를 활용한 간단한 예시이다.

```xml
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
  <!-- xacro:arg 태그를 통해 기본값도 설정 가능 -->
  <xacro:arg name="arm_length" default="1.0"/>
  <xacro:arg name="joint_count" default="4"/>

  <xacro:property name="link_mass" value="1.0"/>

  <!-- joint_count만큼 반복 생성 -->
  <xacro:for each="i" in="${range(joint_count)}">
    <xacro:macro name="arm_segment" params="index length mass">
      <link name="link_${index}">
        <visual>
          <origin xyz="0 0 ${length/2}" rpy="0 0 0"/>
          <geometry>
            <cylinder length="${length}" radius="0.02"/>
          </geometry>
        </visual>
        <inertial>
          <mass value="${mass}"/>
          <origin xyz="0 0 0" rpy="0 0 0"/>
          <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
        </inertial>
      </link>
    </xacro:macro>

    <!-- 반복문 안에서 매크로 호출 -->
    <xacro:arm_segment index="${i}" length="${arm_length}" mass="${link_mass}"/>
    
    <!-- 이 뒤에 joint, 다음 link와의 관계 정의 등을 붙일 수 있음 -->
    <!-- 예시로 간단히 revolute joint만 표시 -->
    <joint name="joint_${i}" type="revolute">
      <parent link="link_${i}"/>
      <child link="link_${i+1}"/>
      <origin xyz="0 0 ${arm_length}" rpy="0 0 0"/>
      <limit lower="0" upper="1.57" effort="10" velocity="1.0"/>
      <axis xyz="0 0 1"/>
    </joint>
  </xacro:for>
</robot>
```

* `xacro:arg`로 인자를 정의하고, 필요할 경우 기본값을 설정한다.
* 명령줄에서 `arm_length:=1.2`처럼 값을 넘기면, 해당 값이 `xacro:arg`에 대입된다.
* `range(joint_count)`를 이용하여 `joint_count`만큼 반복하고, 매 반복마다 링크와 조인트를 생성한다.

이와 같이 Xacro 인자를 활용하면, 로봇 모델의 파라미터를 손쉽게 확장하거나 수정할 수 있으며, 여러 가지 로봇 변형 모델을 한꺼번에 관리하기에도 편리하다.

#### 파일 구조 분리와 Include

Xacro를 이용한 로봇 모델링 프로젝트가 커지면, 하나의 Xacro 파일 내에 모든 내용을 넣기보다는 역할별로 나누어 관리하는 것이 좋다. 예를 들어,

* **robot\_base.xacro**: 로봇의 기본 몸체(Baselink), 고정 조인트 등을 정의
* **robot\_arm.xacro**: 로봇 팔, 그리퍼 등을 정의
* **sensors.xacro**: 센서(카메라, LiDAR 등) 관련 모델을 정의
* **materials.xacro**: 로봇에 사용되는 색상, 재질(Material) 정보만 모아 놓음

그리고 최종적으로 메인 Xacro 파일(예: **my\_robot.xacro**)에서 아래와 같이 필요한 파일들을 포함(`xacro:include`)하고, 전체 로봇을 조립하는 식으로 구성할 수 있다.

```xml
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
  <xacro:include filename="$(find my_robot_description)/urdf/robot_base.xacro"/>
  <xacro:include filename="$(find my_robot_description)/urdf/robot_arm.xacro"/>
  <xacro:include filename="$(find my_robot_description)/urdf/sensors.xacro"/>
  <xacro:include filename="$(find my_robot_description)/urdf/materials.xacro"/>

  <!-- 필요한 매크로 호출 및 조립 -->
  <xacro:robot_base_macro />
  <xacro:robot_arm_macro />
  <xacro:sensors_macro />
</robot>
```

이렇게 분리해 두면, 특정 부분만 수정하거나 재사용하기가 훨씬 수월하다. 또한 여러 로봇 모델이 공통으로 사용하는 부품(예: 동일 규격 바퀴나 센서)이 있을 경우에도, 매크로를 모듈화해 두면 재사용하기 편리하다.

#### 조건부 매크로 생성

Xacro는 반복문 외에도 조건부로 특정 요소를 생성할 수도 있다. 예를 들어 어떤 구성에서는 카메라를 달고, 다른 구성에서는 카메라를 생략하고자 하는 경우가 있을 수 있다. 이럴 때 `<xacro:if>`나 `<xacro:unless>` 구문을 사용한다. 다음은 카메라 장착 여부에 따른 URDF 구조 예시이다.

```xml
<xacro:arg name="use_camera" default="true"/>

<xacro:if value="${use_camera}">
  <link name="camera_link">
    <visual>
      <!-- 카메라 모델 시각 요소 -->
    </visual>
  </link>
  <joint name="camera_joint" type="fixed">
    <parent link="head_link"/>
    <child link="camera_link"/>
    <origin xyz="0 0 0.1" rpy="0 0 0"/>
  </joint>
</xacro:if>
```

명령줄에서 `use_camera:=false`로 인자를 전달하면, 해당 `<link>`와 `<joint>`가 생성되지 않는다. 이를 통해 다양한 하드웨어 옵션을 하나의 Xacro 파일로 관리할 수 있다.

#### 중첩 매크로

때로는 매크로 안에서 다른 매크로를 다시 호출해야 할 수도 있다. 이를 **중첩 매크로**라고 부른다. 예컨대 복잡한 링크 구조를 여러 파트(하부, 중간부, 상부 등)로 나누어 각각 매크로화하고, 최종 링크 매크로에서 이들을 조합하는 식이다. 예시를 간단히 들어보자.

```xml
<xacro:macro name="wheel_part" params="wheel_name radius width">
  <!-- wheel_name이라는 링크를 정의하고, 시각/충돌/관성요소 포함 -->
  <link name="${wheel_name}">
    ...
  </link>
</xacro:macro>

<xacro:macro name="wheel_with_motor" params="base_name radius width motor_mass">
  <!-- 바퀴 부분 생성 -->
  <xacro:wheel_part wheel_name="${base_name}_wheel" radius="${radius}" width="${width}"/>

  <!-- 모터 부분 생성 -->
  <link name="${base_name}_motor">
    ...
    <inertial>
      <mass value="${motor_mass}"/>
      ...
    </inertial>
  </link>
  
  <!-- wheel과 motor를 연결하는 joint 정의 -->
  <joint name="${base_name}_wheel_joint" type="revolute">
    <parent link="${base_name}_motor"/>
    <child link="${base_name}_wheel"/>
    ...
  </joint>
</xacro:macro>
```

* `wheel_part` 매크로는 단순히 바퀴 링크만 생성한다.
* `wheel_with_motor` 매크로는 바퀴와 모터를 모두 생성하고 조인트까지 연결한다. 이 과정에서 내부적으로 `wheel_part` 매크로를 호출한다.

이런 식으로 매크로를 중첩하여 호출하면, 코드의 재사용성과 가독성을 더욱 높일 수 있다.

#### 실전 팁: 동일 매크로를 다양한 방식으로 재활용하기

Xacro 매크로를 설계할 때는, **이 매크로를 다른 로봇이나 다른 프로젝트에서도 재활용할 수 있을지**를 고민해 보는 것이 좋다.

* 매크로에 너무 구체적인 정보(특정 로봇 전용 상수나, 특정 좌표 변환 등)를 넣으면 범용적으로 쓰기 어렵다.
* 필요 최소한의 인자만 받고, 나머지는 기본값으로 처리하거나 상위에서 인자를 설정해 줄 수 있도록 설계하면, 여러 곳에서 쉽게 재활용할 수 있다.

예컨데 바퀴 매크로를 설계할 때, 재질(Material), 색상(Color), 충돌(Collision) 계수 등을 파라미터로 받을 수도 있고, 단순화하여 기본값만 적용하도록 할 수도 있다. 프로젝트 규모에 따라 적절히 설계하는 것이 중요하다.

#### 디버깅과 유지보수

Xacro 파일은 복잡해질수록 디버깅(오류 찾기)과 유지보수에 주의가 필요하다. 매크로와 반복문이 뒤섞여 있으면 로봇 모델이 제대로 생성되지 않을 수 있고, 특정 파라미터 값이 잘못 전달되어서 링크나 조인트가 예상대로 생성되지 않기도 한다. 아래는 디버깅과 유지보수 시에 유용한 팁이다.

**`xacro` 명령어로 중간 산출물 확인**:

URDF 형태의 결과물을 바로 확인해 보는 것이 좋다. 예를 들어, 다음 명령으로 Xacro를 URDF로 변환하여 결과를 표준 출력으로 확인할 수 있다.

```bash
xacro my_robot.xacro > my_robot.urdf
```

그런 다음 `my_robot.urdf` 파일을 열어 XML 구조가 원하는 대로 생성되었는지, 매크로가 제대로 확장되었는지 확인한다.

**오류 메시지 확인**:

Xacro 변환 시 문법 오류나 정의되지 않은 매크로를 호출하면 에러 메시지가 출력된다. 이 메시지를 주의 깊게 살펴보면 어느 부분에서 매크로가 제대로 정의되지 않았는지, 혹은 변수를 찾지 못했는지 등의 정보를 얻을 수 있다.

**각종 XML 에디터/플러그인 활용**:

코드 편집기(예: VSCode, Atom 등)에서 Xacro 파일의 XML 구조를 자동으로 들여쓰기하거나 문법을 검증해 주는 플러그인을 사용하면 오류를 사전에 줄일 수 있다.

**코드 구조 단순화**:

* 매크로 중복 호출, 중첩 반복문 등이 너무 복잡해지면 구조를 다시 나누거나, 일부 중복을 제거할 수 있는지 고민해야 한다.
* 매크로 이름, 파라미터 이름을 직관적으로 짓고, 주석을 충분히 달아두면 유지보수가 편리하다.

**중간 결과 파라미터 출력**:

Xacro에서는 `print()` 같은 함수를 직접 쓸 수 없지만, 필요하다면 `<xacro:property name="debug_param" value="${some_python_expression}"/>` 형태로 값을 잠깐 저장해 보고, 변환된 URDF를 살펴보면 그 값이 잘 들어갔는지 파악할 수 있다.

#### Xacro 표현식(Expression) 활용

Xacro 내에서는 Python 형태의 간단한 수식(Arithmetic expression)이나 리스트/딕셔너리 접근 같은 기초적인 표현식을 사용할 수 있다. 예를 들어,

```xml
<xacro:property name="lengths" value="${[0.5, 0.7, 1.0]}"/>
<xacro:property name="sum_length" value="${sum(lengths)}"/>
```

* `sum(lengths)`는 3개의 값(0.5, 0.7, 1.0)을 모두 더한 결과인 2.2를 반환한다.
* `xacro:macro` 파라미터로 전달 시에도 간단한 파이썬 표현식을 직접 적어 줄 수 있다. 예: `${0.01 * i}`, `${math.sin(i * 0.1)}`, 등.

다만, Xacro가 지원하는 파이썬 표현식은 제한적이므로(추가 라이브러리를 임의로 import하기는 어려움), 복잡한 계산 로직은 별도의 노드나 스크립트로 처리하고 결과를 파라미터로 넘겨주는 방식을 고려해야 한다.

#### Launch 파일과 Xacro 연동

ROS2에서는 Launch 파일을 사용해 노드 실행, 파라미터 설정, URDF/Xacro 로딩 등을 종합적으로 제어한다. 예를 들어, `urdf.xacro`에서 로봇 모델을 정의하고, Launch 파일에서 `xacro`를 호출해 변환한 결과를 `robot_state_publisher` 노드로 전달할 수 있다. 간단한 예시를 들어보자.

```python
# my_robot_launch.py (ROS2 launch file)

from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch.substitutions import Command, LaunchConfiguration
from launch_ros.actions import Node

def generate_launch_description():
    use_camera = LaunchConfiguration('use_camera', default='true')

    robot_description = Command([
        'xacro ',
        ' ', 'path/to/my_robot.xacro',
        ' ', 'use_camera:=', use_camera
    ])

    return LaunchDescription([
        # 로봇 모델 파라미터를 robot_state_publisher에 전달
        Node(
            package='robot_state_publisher',
            executable='robot_state_publisher',
            output='screen',
            parameters=[{'robot_description': robot_description}]
        ),

        # RViz 실행
        ExecuteProcess(
            cmd=['rviz2', '-d', 'path/to/rviz_config.rviz'],
            output='screen'
        )
    ])
```

* `robot_description` 파라미터에 Xacro 변환 결과(즉 URDF 완성본)를 넣고, 이를 `robot_state_publisher` 노드에 제공한다.
* `use_camera`라는 LaunchConfiguration 인자를 `my_robot.xacro`로 넘겨주어, 카메라 유무를 동적으로 반영할 수 있다.
* Launch 파일을 실행할 때 `ros2 launch my_package my_robot_launch.py use_camera:=false` 처럼 옵션을 달면, 카메라가 없는 모델이 생성될 것이다.

#### 복합 형상의 반복 구조

로봇 모델링을 하다 보면, 실린더나 박스와 같은 단순 형상이 아닌 STL, DAE, COLLADA 파일 등을 사용하는 경우가 있다. 예를 들어, 로봇 팔의 특정 부위를 캐드 모델로 만들고 STL 파일로 내보냈다고 하면, 반복되는 파트들을 STL 형태로 지정해야 할 수도 있다. 이런 경우에도 매크로나 반복문을 사용해 경로만 달리하면 된다.

```xml
<xacro:macro name="stl_link" params="link_name stl_path mass">
  <link name="${link_name}">
    <visual>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <geometry>
        <mesh filename="${stl_path}" scale="1 1 1"/>
      </geometry>
    </visual>
    <inertial>
      <mass value="${mass}"/>
      <!-- 관성 모멘트는 STL 기반으로 정확히 산출하기 어려우므로 근사값을 사용하거나 별도 툴로 계산 -->
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
    </inertial>
  </link>
</xacro:macro>
```

이 매크로를 `<xacro:for>` 안에서 여러 번 호출하여, 로봇의 동일한 모듈형 부품(STL)을 반복 배치하는 식으로 활용할 수 있다.

#### 실습: 간단한 로봇팔 반복 구조 예시

다음은 3자유도 로봇팔을 Xacro로 반복 생성하는 예시이다. 링크 1, 2, 3이 모두 같은 형상이나 길이만 다르다고 가정하자.

```xml
<xacro:property name="link_lengths" value="${[0.3, 0.2, 0.2]}"/>
<xacro:property name="link_masses" value="${[0.5, 0.4, 0.4]}"/>

<xacro:macro name="robot_arm_link" params="index length mass">
  <link name="link_${index}">
    <visual>
      <origin xyz="0 0 ${length/2}" rpy="0 0 0"/>
      <geometry>
        <cylinder length="${length}" radius="0.03"/>
      </geometry>
    </visual>
    <inertial>
      <mass value="${mass}"/>
      <origin xyz="0 0 0" rpy="0 0 0"/>
      <inertia ixx="0.001" ixy="0" ixz="0" iyy="0.001" iyz="0" izz="0.001"/>
    </inertial>
  </link>
</xacro:macro>

<xacro:for each="i" in="${range(0,3)}">
  <xacro:robot_arm_link 
    index="${i}"
    length="${link_lengths[i]}"
    mass="${link_masses[i]}"/>
  
  <!-- 각 링크 사이를 연결하는 조인트 -->
  <joint name="joint_${i}" type="revolute">
    <parent link="link_${i}"/>
    <child link="link_${i+1}"/>
    <origin xyz="0 0 ${link_lengths[i]}" rpy="0 0 0"/>
    <limit lower="0" upper="1.57" effort="10" velocity="1.0"/>
    <axis xyz="0 0 1"/>
  </joint>
</xacro:for>
```

* `link_lengths`와 `link_masses` 리스트를 정의하고, 반복문에서 `i`번째 원소를 꺼내서 각 링크와 조인트를 구성한다.
* 3개의 링크가 순차적으로 연결되는 단순한 구조지만, 만약 링크 개수가 7\~8개로 늘어나도 반복문만 수정하면 되므로 확장성이 높다.

#### 고급 Xacro 기법: 버전과 호환성

Xacro는 ROS 1 시절부터 꾸준히 개선되어 왔으며, ROS 2(특히 Humble)에서도 안정적으로 사용 가능하다. 다만 오래된 튜토리얼이나 기존 ROS 1 패키지에서 작성된 Xacro 예시를 그대로 가져오면, 구문이 변경되었거나 호환되지 않는 부분이 있을 수 있으므로 주의해야 한다.

**Old vs New Xacro**:

예전에는 `<xacro:property>` 대신 `<property>`를, `${}` 대신 `$(...)` 형태를 사용하는 등 여러 문법 차이가 있었다. ROS 2에서 사용 시에는 최신 Xacro 문법(`xacro:` 네임스페이스, `${ }` 사용 등)으로 통일하는 것이 좋다.

**호환 모드**:

Xacro는 버전에 따라 ‘old’ 모드와 ‘new’ 모드를 제공하기도 했다. 최근 ROS 2 배포판에서는 기본적으로 최신 문법을 사용하므로, 특별한 이유가 없다면 호환 모드를 따로 고려하지 않아도 된다.

#### 매크로 스코프와 전역 속성

Xacro에는 **스코프** 개념이 존재해, 매크로 내부에서 정의한 프로퍼티(예: `<xacro:property>`)가 매크로 바깥쪽에서 바로 보이지 않을 수도 있다.

**매크로 내부 전용 속성**: 매크로 내부에서만 유효한 임시 변수나 계산 결과는, 굳이 전역으로 노출할 필요가 없다.

**전역 속성**: 로봇 전체가 공유해야 하는 상수(예: 중력 가속도, 로봇의 전체 색상 팔레트 등)는 매크로 밖에서 `<xacro:property>`로 정의해두고 매크로 인자로 넘기거나, 매크로 안에서 전역 스코프로 다시 정의할 수도 있다.

아래는 매크로 내부에서 계산된 결과값을 다시 전역에 설정하는 예시이다.

```xml
<xacro:macro name="calc_inertia" params="mass radius">
  <!-- 매크로 내부에서 관성 모멘트 계산 (원기둥 단순화 가정) -->
  <xacro:property name="Ixx" value="${0.5 * mass * radius * radius}" scope="global"/>
  <xacro:property name="Iyy" value="${0.5 * mass * radius * radius}" scope="global"/>
  <xacro:property name="Izz" value="${mass * radius * radius}" scope="global"/>
</xacro:macro>
```

`scope="global"`을 지정하면, 매크로 바깥에서도 `Ixx`, `Iyy`, `Izz`를 참조할 수 있다.

#### 환경 변수와 Xacro

ROS2에서 패키지 경로 등을 확인할 때, 종종 시스템 환경 변수를 참조해야 하는 경우가 있다. 예를 들어, STL 파일이나 COLLADA 파일이 패키지가 아닌 특정 디렉토리에 위치할 수 있다. 이런 경우 `<env name=""/>` 식으로 환경 변수를 읽어와서 Xacro의 파라미터로 넘길 수도 있다.

```xml
<xacro:property name="model_path" value="${env('MY_MODEL_PATH')}" />
<xacro:macro name="external_mesh_link" params="link_name mesh_file">
  <link name="${link_name}">
    <visual>
      <geometry>
        <mesh filename="${model_path}/${mesh_file}" />
      </geometry>
    </visual>
    ...
  </link>
</xacro:macro>
```

이처럼 `env('MY_MODEL_PATH')`는 시스템에 설정된 `MY_MODEL_PATH` 환경 변수를 읽어온다. 단, Launch 파일에서 환경 변수를 따로 설정하거나, 터미널에서 `export MY_MODEL_PATH=...`처럼 미리 지정해 두어야 한다.

#### 성능 및 복잡도 고려

Xacro는 로봇 모델의 반복 구조를 간단히 기술하는 데 큰 도움을 주지만, **너무 복잡한 반복문**과 **과도한 중첩 매크로**로 인해 처리 시간이 길어질 수 있다. 특히 링크나 조인트 개수가 수백 개에 달하는 대규모 로봇을 모델링할 때는 다음을 고려해야 한다.

**불필요한 중복 매크로 제거**: 비슷한 역할을 하는 매크로가 여러 개 있을 경우, 하나로 통합할 수 있는지 점검한다.

**단순 로직 우선**: 매크로 안에서 지나치게 복잡한 파이썬 표현식을 쓰거나, 여러 함수 호출을 섞어 쓰면 디버깅이 어려워진다. 필요한 만큼만 수식을 쓰도록 한다.

**중간 산출물 캐싱**: 링크 개수가 많을 경우, 간단한 테스트용(예: 2\~3개 링크만 생성) Xacro 파일과 실제 대형 로봇용 Xacro 파일을 분리해 두면 좋다. 필요 시 범용적인 대형 파일을 로딩하고, 디버깅 시에는 작은 예제를 테스트할 수 있다.

#### CI(Continuous Integration)에서의 Xacro 검증

프로젝트가 커지면, 로봇 모델 파일(Xacro/URDF)도 Git으로 버전 관리를 하고, CI 파이프라인에서 자동 검증하는 방법을 도입할 수 있다.

**Syntax Check**: `xacro my_robot.xacro --check-order` 같은 명령으로 문법 검사를 수행한다.

**URDF 통합 테스트**: 변환된 `my_robot.urdf`를 `check_urdf`(ROS 1 툴이지만 ROS 2에서도 비슷하게 사용 가능) 명령이나 RViz로 로딩해 보는 스크립트를 CI에서 실행한다.

**Robot State Publisher Test**: Launch 파일로 `robot_state_publisher`를 띄우고 TF 트리 구성이 정상적인지 체크한다.

이 과정을 자동화해 두면, 매크로나 인자 변경으로 인해 모델이 깨졌을 때 빠르게 피드백을 받을 수 있다.

#### YAML/파라미터 파일과 Xacro 연동

ROS2에서는 YAML 파일로 파라미터를 관리하고, Launch 파일에서 이를 로드하는 패턴이 흔하다. Xacro 파일 또한 YAML 파라미터와 엮을 수 있다. 예를 들어, ROS2 Launch 파일에서 다음과 같이 YAML 파라미터를 불러온 뒤 Xacro 인자로 넘길 수 있다.

```python
params_file = LaunchConfiguration('params_file', default='config/robot_params.yaml')

robot_description = Command([
    'xacro ',
    PathJoinSubstitution([FindPackageShare('my_robot_description'), 'urdf', 'my_robot.xacro']),
    ' ', 'arm_length:=', LaunchConfiguration('arm_length'),
    ' ', 'joint_count:=', LaunchConfiguration('joint_count')
])

return LaunchDescription([
    DeclareLaunchArgument('params_file', default_value='config/robot_params.yaml'),
    IncludeLaunchDescription(
        PythonLaunchDescriptionSource(
            [FindPackageShare('my_robot_bringup'), '/launch/include_robot.launch.py']
        ),
        launch_arguments={'arm_length': '1.2', 'joint_count': '6'}.items()
    ),
    ...
])
```

YAML(예: `robot_params.yaml`)에서 `arm_length: 1.2`, `joint_count: 6` 등을 정의해 두고, Launch 파일에서 이를 불러와 인자로 넘기면 Xacro에서 해당 값을 받아 링크와 조인트를 반복 생성한다.

이렇게 하면 런타임에 손쉽게 파라미터를 바꿔 가며 동일 로봇의 여러 변형 모델(팔 길이가 다른 로봇 등)을 테스트할 수 있다.

#### SDF/Gazebo와 Xacro

Gazebo(또는 Ignition/Gazebo)에서 사용하는 SDF(Structured Description Format) 모델 역시 Xacro로 생성할 수 있다. 다만, Xacro는 기본적으로 **URDF 문법**을 확장하는 방식에 최적화되어 있으므로, SDF를 대상으로 Xacro를 사용할 때는 주의할 점이 있다.

**태그 네임스페이스**: URDF 태그와 다른 SDF 태그가 혼재될 수 있다. Xacro는 단순히 XML 템플릿 처리이므로, SDF 문법에 맞춰 `<model>`, `<link>`, `<joint>`, `<plugin>` 등을 작성하면 동일하게 확장 가능하다.

**Gazebo 전용 태그**: URDF 안에서 `<gazebo>` 태그를 쓰는 방식 대신, SDF 고유의 `<plugin>`이나 `<model>` 문법을 쓰면 된다. 이 또한 Xacro 매크로와 반복문으로 간소화가 가능하다.

예를 들어, 여러 개의 물체(Obstacle)를 SDF로 생성하는 매크로를 다음과 같이 작성할 수 있다.

```xml
<xacro:macro name="create_obstacle" params="obs_name pos_x pos_y">
  <model name="${obs_name}">
    <pose>${pos_x}${pos_y} 0 0 0 0</pose>
    <link name="${obs_name}_link">
      <collision name="collision">
        <geometry>
          <box>
            <size>1 1 0.5</size>
          </box>
        </geometry>
        <max_contacts>10</max_contacts>
      </collision>
      <visual name="visual">
        <geometry>
          <box>
            <size>1 1 0.5</size>
          </box>
        </geometry>
      </visual>
    </link>
  </model>
</xacro:macro>
```

그리고 반복문으로 여러 장애물을 배치할 수 있다.

```xml
<xacro:for each="i" in="${range(3)}">
  <xacro:create_obstacle obs_name="obstacle_${i}" pos_x="${i*2}" pos_y="${i*1.5}"/>
</xacro:for>
```

결과적으로 SDF 파일이 생성되어, Gazebo 환경에 동일 규격의 장애물들을 깔끔하게 배치할 수 있다.

여기까지 Xacro를 이용해 반복 구조를 간소화하는 다양한 기법과 팁을 살펴보았다. 복잡한 로봇 모델을 간결하고 유지보수 용이하게 유지하는 핵심은 **적절한 매크로 설계와 반복문 활용**이며, 이를 통해 **중복 코드를 최소화**하고 **파라미터 유연성**을 극대화할 수 있다.
