MoveIt2 핵심 개념 정리: 파싱, 쿼터니언, 비동기, Cartesian Path

파싱(Parsing)이란?

**파싱(Parsing)**이란 텍스트 데이터를 읽어서 구조화된 형태로 분해하는 과정입니다. MoveIt2에서는 로봇의 설정 파일인 SRDF(XML 형식)를 파싱하여 관절 정보, 그룹 정의 등을 추출합니다.

flowchart LR
    A["XML 텍스트<br/>(긴 문자열)"] -->|ET.fromstring| B["트리 구조"]
    B --> C["robot"]
    C --> D["group_state"]
    D --> E["joint1: 0.0"]
    D --> F["joint2: -1.57"]
    D --> G["joint3: 1.57"]

파싱 전에는 그냥 텍스트 덩어리지만, 파싱 후에는 프로그램이 원하는 값을 바로 꺼낼 수 있습니다.

<!-- SRDF 파일 원본 (그냥 텍스트) -->
<robot name="my_robot">
  <group_state name="home" group="manipulator">
    <joint name="joint1" value="0.0"/>
    <joint name="joint2" value="-1.57"/>
    <joint name="joint3" value="1.57"/>
  </group_state>
</robot>
import xml.etree.ElementTree as ET
 
# 파싱: 텍스트 → 트리 구조로 변환
root = ET.fromstring(srdf_text)
 
# 파싱 후: 원하는 값을 바로 접근 가능
group_state = root.find('.//group_state[@name="home"]')
for joint in group_state.findall('joint'):
    name = joint.get('name')     # "joint2"
    value = joint.get('value')   # "-1.57"
    print(f"{name}: {value}")

핵심: 파싱 = 텍스트를 프로그램이 다루기 쉬운 구조로 쪼개는 것


SRDF란 무엇인가?

**SRDF (Semantic Robot Description Format)**는 URDF에 의미(semantic) 정보를 추가하는 파일입니다. URDF가 로봇의 물리적 설계도라면, SRDF는 그 로봇을 어떻게 사용할지에 대한 설정 파일입니다.

항목URDFSRDF
내용물리적 구조 (링크, 관절, 질량)의미/사용 정보 (그룹, 자세, 충돌)
비유자동차 설계도운전 매뉴얼
예시joint1 범위: π-\pi ~ π\pijoint1~6을 "manipulator"로 묶음
없으면?로봇 자체를 못 만듦MoveIt이 못 돌아감
flowchart TB
    subgraph URDF ["URDF (물리적 구조)"]
        U1["링크 크기/질량"]
        U2["관절 범위/타입"]
        U3["메시 모델"]
    end
    subgraph SRDF ["SRDF (사용 설정)"]
        S1["그룹 정의"]
        S2["이름 포즈"]
        S3["충돌 무시"]
        S4["엔드이펙터"]
    end
    URDF -->|"+의미 정보"| SRDF
    SRDF --> MoveIt["MoveIt2 동작"]

1. 그룹 정의 - 어떤 관절들을 묶어서 제어할지

<group name="manipulator">
  <chain base_link="base_link" tip_link="gripper_base"/>
</group>
<group name="gripper">
  <joint name="left_finger_joint"/>
</group>

2. 이름 포즈 - 자주 쓰는 자세를 이름으로 저장

<group_state name="home" group="manipulator">
  <joint name="joint1" value="0.0"/>
  <!-- ... -->
</group_state>

3. 충돌 무시 설정 - 인접한 링크 간 불필요한 충돌 검사 비활성화

<disable_collisions link1="base_link" link2="link1" reason="Adjacent"/>

4. 엔드 이펙터 정의

<end_effector name="gripper_ee" parent_link="gripper_base"
              group="gripper" parent_group="manipulator"/>

팁: SRDF는 MoveIt 전용 파일입니다. 로봇 제조사는 URDF만 제공하고, MoveIt을 사용하려는 개발자가 moveit_setup_assistant를 통해 SRDF를 직접 생성합니다.


utils.py로 코드 통합하기 (ex02 vs ex03)

# SRDF XML 직접 파싱
tree = ET.fromstring(srdf_text)
for joint in group_state.findall('joint'):
    name = joint.get('name')
    value = joint.get('value')
 
# MoveIt 메시지 직접 구성
request = MotionPlanRequest()
constraint = JointConstraint()
constraint.joint_name = 'joint1'
constraint.position = 0.0
# ... 하나하나 다 만듦
 
# Action 직접 호출
goal = MoveGroup.Goal()
goal.request = request
self._action_client.send_goal_async(goal)

helper = MoveGroupHelper(node)
helper.go_to_named_target('home')          # 한 줄로 끝
helper.go_to_joint_goal([0, 0.5, 0, ...])  # 한 줄로 끝

같은 일을 하는데 ex02는 ~230줄, ex03는 ~115줄.

flowchart LR
    subgraph before ["ex02: 매번 직접 작성"]
        A1["SRDF 파싱"] --> B1["메시지 구성"] --> C1["Action 호출"]
    end
    subgraph after ["ex03~: utils.py 활용"]
        U["utils.py<br/>MoveGroupHelper"] --> D1["go_to_named_target()"]
        U --> D2["go_to_joint_goal()"]
        U --> D3["go_to_pose_goal()"]
        U --> D4["execute_trajectory()"]
    end
항목ex02 (직접)ex03+ (utils)
코드량~230줄~115줄
SRDF 파싱매번 직접go_to_named_target()
메시지 구성매번 직접go_to_joint_goal()
Action 호출매번 직접execute_trajectory()
목적내부 동작 교육실제 작업에 집중

요약: ex02는 "MoveIt 통신이 내부적으로 이렇게 동작한다"를 보여주는 교육용이고, ex03부터는 반복 코드를 utils로 빼서 실제로 하고 싶은 일에만 집중하는 구조입니다.


쿼터니언(Quaternion)과 3D 회전

3D 공간에서 회전을 표현하는 방법은 크게 두 가지가 있습니다.

항목오일러 각 (Euler)쿼터니언 (Quaternion)
표현roll, pitch, yaw (3개 값)x, y, z, w (4개 값)
가독성사람이 읽기 쉬움직관적이지 않음
짐벌락발생함없음
계산 효율보통높음
보간(Slerp)부자연스러움부드럽고 자연스러움

쿼터니언 (x,y,z,w)(x, y, z, w)에서:

x=axsinθ2,y=aysinθ2,z=azsinθ2,w=cosθ2x = a_x \sin\frac{\theta}{2}, \quad y = a_y \sin\frac{\theta}{2}, \quad z = a_z \sin\frac{\theta}{2}, \quad w = \cos\frac{\theta}{2}

  • [ax,ay,az][a_x, a_y, a_z] = 회전축의 방향 (단위 벡터)
  • θ\theta = 회전 각도
# 자주 쓰는 쿼터니언 값
no_rotation      = (0, 0, 0, 1)       # 회전 없음
x_180            = (1, 0, 0, 0)       # X축 180도 회전
z_90             = (0, 0, 0.707, 0.707)  # Z축 90도 회전

짐벌락은 실제 물체에서 생기는 문제가 아니라, 오일러 각이라는 표현 방식에서 생기는 수학적 문제입니다.

stateDiagram-v2
    state "오일러 각 회전 순서" as euler {
        [*] --> Roll: 1단계: X축 회전
        Roll --> Pitch: 2단계: Y축 회전
        Pitch --> Yaw: 3단계: Z축 회전
    }
    state "Pitch = 90도일 때" as lock {
        [*] --> Problem: Roll과 Yaw가 같은 축!
        Problem --> Lost: 자유도 3 → 2로 감소
    }
    euler --> lock: 짐벌락 발생 조건

오일러 각은 축 3개를 순서대로 회전합니다. Pitch가 90도가 되면 Roll축과 Yaw축이 겹쳐서 자유도가 하나 사라집니다.

반면 쿼터니언은 "이 방향의 축을 중심으로 이 각도만큼 돌려"라고 한 번에 표현하기 때문에 축이 겹칠 일이 없습니다.

쿼터니언이 유효한 회전을 나타내려면 반드시 단위 쿼터니언이어야 합니다:

x2+y2+z2+w2=1x^2 + y^2 + z^2 + w^2 = 1

# 유효한 쿼터니언 (길이 = 1)
(0, 0, 0.707, 0.707)  # Z축 90도 회전 ✅
 
# 유효하지 않은 쿼터니언 (길이 ≠ 1)
(0, 0, 0, 2)          # 의미 없는 숫자 ❌

팁: euler_to_quaternion() 함수를 사용하면 항상 정규화된 값이 나오므로 직접 정규화할 필요가 없습니다.


관절 공간 vs 카테시안 공간 (ex02/03 vs ex04)

MoveIt2 예제에서 가장 중요한 개념 전환이 ex04에서 일어납니다.

항목ex02/03 (Joint Space)ex04 (Cartesian Space)
입력관절 각도 직접 지정목표 위치/자세 지정
IK 필요불필요 (답을 직접 줌)필요 (역기구학 계산)
실패 가능성거의 없음있음 (도달 불가, 특이점 등)
사용 예홈 포지션 이동"이 좌표로 그리퍼 이동"
flowchart TB
    subgraph joint ["Joint Space (ex02/03)"]
        JI["joint1=0, joint2=45, ..."] --> JM["바로 모터 구동"]
    end
    subgraph cart ["Cartesian Space (ex04)"]
        CI["x=0.3, y=0.0, z=0.3 + 자세"] --> IK["IK 계산<br/>(역기구학)"]
        IK -->|성공| CM["모터 구동"]
        IK -->|실패| CF["NO_IK_SOLUTION"]
    end

에러원인해결
NO_IK_SOLUTION목표가 팔이 못 닿는 곳이거나 특이점위치를 조금 조정, 자세 변경
Planning failedIK는 풀렸는데 경로에 충돌이 있음planning_time 늘리기, 다른 자세 시도
Quaternion 정규화 오류x2+y2+z2+w21x^2+y^2+z^2+w^2 \neq 1make_pose() 사용하면 자동 처리

비동기(Async)가 ROS2에서 중요한 이유

ROS2에서 MoveIt Action을 호출할 때 비동기 패턴은 필수입니다.

sequenceDiagram
    participant Node as ex04 노드
    participant MG as move_group
    participant CB as 콜백 (joint_states, tf)
 
    Note over Node,CB: 비동기 방식 (async)
    Node->>MG: send_goal_async(goal)
    activate MG
    Note over Node: spin_until_future_complete()
    loop 대기 중에도 이벤트 처리
        CB-->>Node: /joint_states 콜백 ✅
        CB-->>Node: /tf 업데이트 ✅
    end
    MG-->>Node: 결과 반환
    deactivate MG
# 비동기 호출: 보내고 바로 리턴
future = action_client.send_goal_async(goal)
 
# 기다리면서도 콜백은 계속 처리됨
rclpy.spin_until_future_complete(node, future)
방식경로 계획 대기 중콜백 처리결과
동기블로킹/joint_states, /tf 멈춤상태 정보 유실
비동기논블로킹정상 처리안정적 동작

핵심: 비동기를 사용하지 않으면 경로 계획 5초 대기 동안 /joint_states 콜백과 /tf 업데이트가 전부 멈춥니다. 비동기를 사용하면 대기하는 동안에도 ROS 이벤트 루프가 계속 동작합니다.


Cartesian Path로 직선 경로 만들기 (ex05)

항목ex04ex05
제어 대상목표 지점목표 경로
경로 형태MoveIt이 알아서 결정개발자가 직선으로 지정
활용위치 이동용접, 도색, 글씨 쓰기

ex05는 YZ 평면에 8cm 사각형을 그립니다. **직선 보간(linear interpolation)**이 제대로 동작하는지 시각적으로 확인하기 위한 예제입니다.

flowchart LR
    subgraph square ["YZ 평면 8cm 사각형"]
        P1["① 시작점"] -->|"Y+8cm"| P2["②"]
        P2 -->|"Z+8cm"| P3["③"]
        P3 -->|"Y-8cm"| P4["④"]
        P4 -->|"Z-8cm"| P1
    end

trajectory, fraction = helper.compute_cartesian_path(waypoints, max_step=0.02)

max_step = 0.02 (2cm): 웨이포인트 사이를 2cm 간격으로 쪼개서 각 점마다 IK를 계산합니다.

① ──────────────── ② (8cm 구간)
max_step=0.02 적용 후:
① · · · · · · · · ② (4개 중간점 생성, 각 점마다 IK 계산)

fraction = 경로 달성률 (0.0 ~ 1.0)

fraction 값의미조치
1.0100% 경로 계획 성공바로 실행
0.7575% 지점에서 IK 실패 또는 충돌max_step 조정 또는 경로 수정
0.0시작부터 실패시작 자세/위치 확인

sequenceDiagram
    participant EX as ex05 노드
    participant MG as move_group 노드
    participant RC as ros2_control
    participant RSP as robot_state_publisher
 
    EX->>MG: /compute_cartesian_path (서비스 요청)
    Note over MG: 웨이포인트 쪼개기 → IK → 궤적 생성
    MG-->>EX: trajectory + fraction (서비스 응답)
    EX->>MG: /execute_trajectory (액션 요청)
    MG->>RC: /arm_controller/joint_trajectory (토픽)
    RC->>RC: 모터 구동
    RC-->>RSP: /joint_states
    RSP-->>MG: /tf

한 줄 요약: ex05는 "어디로 가냐"가 아니라 **"어떤 경로로 가냐"**를 지정하는 예제입니다. 직선 이동이 필요한 작업(용접, 도색, 글씨 쓰기 등)에 필수적인 기능입니다.


정리

개념핵심 요약
파싱텍스트를 프로그램이 다룰 수 있는 구조로 변환
SRDFURDF에 의미 정보를 추가하는 MoveIt 전용 설정 파일
utils.py반복되는 MoveIt 통신 코드를 추상화하여 재사용
쿼터니언짐벌락 없이 3D 회전을 표현하는 4차원 수 (x,y,z,w)(x,y,z,w)
비동기ROS 이벤트 루프를 막지 않고 Action을 호출하는 패턴
Cartesian Path직선 보간으로 경로의 형태를 직접 지정하는 방식
  • ex02: MoveIt 내부 동작을 직접 구현하며 학습
  • ex03: utils.py로 반복 코드를 제거하고 핵심 로직에 집중
  • ex04: Joint Space에서 Cartesian Space로 전환, IK와 쿼터니언 활용
  • ex05: Cartesian Path로 직선 경로를 지정하여 정밀 작업 수행