# Isaac Sim 스크립팅(Python) 기초

#### Isaac Sim에서 Python 활용의 의의

Isaac Sim은 NVIDIA의 물리 시뮬레이션 기반 로보틱스 및 AI 개발 플랫폼이다. 이 환경에서는 Python 스크립팅이 핵심적인 역할을 맡는다. Python은 동적 타이핑 언어로 배우기 쉽고 Isaac Sim과 결합하여 다양한 시뮬레이션 요소를 제어하고 자동화할 수 있다. Isaac Sim의 스크립팅 기능을 제대로 익히면 로봇 모델 및 시뮬레이션 월드를 프로그래밍적으로 조작할 수 있으며, ROS2 및 기타 패키지와 연계하여 시뮬레이션을 확장할 수 있다.

#### 기본 환경과 스크립트 실행 방식

Isaac Sim에서 Python 스크립팅을 실행하기 위한 방법은 크게 확장기(Extension), Script Editor(내장 편집기), 외부 Python 스크립트 실행으로 나눌 수 있다. Extension 방식은 NVIDIA Omniverse의 확장 구조를 통해 Isaac Sim에 스크립트를 추가하는 방법이고, Script Editor는 Isaac Sim 내부에 있는 편집기에서 코드를 작성하고 곧바로 실행할 수 있는 방법이다. 외부 Python 스크립트 실행은 시스템 환경에서 Isaac Sim의 파이썬 해석기를 사용하여 별도의 코드를 구동하는 방식이다.

#### Isaac Sim Python API 개요

Isaac Sim은 NVIDIA Omniverse Kit를 기반으로 하므로, Omniverse Kit API에 접근할 수 있다. Python 코드에서 자주 사용하는 모듈과 클래스는 다음과 같은 특징을 가진다. (아래는 구체적인 리스트가 아닌 개념적 요약)

OmniKit: Isaac Sim의 주요 서비스와 상호작용하는 인터페이스를 제공한다. 예를 들어, 스테이지(Stage)나 이벤트(event) 등을 핸들링하는 함수들이 있다.

Usd, UsdGeom, UsdPhysics 등: 시뮬레이션 세계(USD Stage)에서 기하학적 객체(Geometry), 물리(Physics), 재질(Material) 등을 정의하고 조작하는 데 유용하다.

Carb: Omniverse의 하위 레벨 기능에 접근할 수 있는 모듈이며, 그래픽스나 시스템 호출 등을 다룰 때 종종 사용된다.

#### 기본 스크립트 예제

Isaac Sim 내부 Script Editor를 열어 간단한 예제를 실행할 수 있다. 다음 코드는 Isaac Sim의 스테이지에 Cube를 생성하고 위치를 변경하는 Python 예제이다.

```python
import omni
from pxr import UsdGeom, Gf

# 현재 스테이지 얻기
stage = omni.usd.get_context().get_stage()

# Cube 생성
cube_path = "/World/MyCube"
UsdGeom.Cube.Define(stage, cube_path)

# 변환(Transform) 적용
prim = stage.GetPrimAtPath(cube_path)
xform = UsdGeom.Xformable(prim)
xform.AddTranslateOp().Set(Gf.Vec3f(100, 0, 0))

print("큐브 생성 및 위치 이동 완료!")
```

Script Editor에 위 코드를 입력한 후 실행하면, 스테이지 내에서 해당 경로에 Cube가 생성되고 변환이 적용된다.

#### Isaac Sim API 설계와 이벤트 모델

Isaac Sim Python 인터페이스는 다양한 이벤트 기반 구조를 지원한다. 스테이지 로드, UI 요소 상호작용, 마우스나 키보드 입력 등 여러 이벤트를 처리할 수 있도록 설계되어 있다. 이러한 이벤트 처리 과정을 통해 사용자 상호작용 시 동적으로 스크립트를 실행하거나 시뮬레이션 상태 변화를 모니터링하고 대처할 수 있다.

아래는 Isaac Sim 이벤트 흐름 구조를 단순화한 mermaid 예시이다.

{% @mermaid/diagram content="flowchart TD
A\[Isaac Sim Scripting Start] --> B\[Python Scripting Core]
B --> C\[Omni.UI와 UI 스크립팅]
B --> D\[Scene Manipulation]
C --> E\[이벤트 핸들링]
D --> F\[오브젝트 생성 및 조작]" %}

#### Isaac Sim에서 UI 확장하기

UI 확장은 스크립팅을 통해 Isaac Sim 내의 창, 패널, 버튼 등을 직접 구현할 수 있게 해준다. 예를 들어, 새 패널을 추가하고 패널 안에 버튼을 배치한 후 사용자가 버튼을 클릭할 때마다 특정 함수를 호출하도록 만들 수 있다. Python 코드로 Omni.UI 라이브러리를 사용하면 Isaac Sim 창에 직접 인터페이스를 구성할 수 있다.

아래 예제는 간단한 UI 패널과 버튼을 생성하는 Python 코드다.

```python
import omni.ext
import omni.ui as ui

class MyUIPanelExtension(omni.ext.IExt):
    def on_startup(self, ext_id):
        self._window = ui.Window("My Custom Panel", width=300, height=200)
        with self._window.frame:
            with ui.VStack():
                ui.Label("여기에 UI를 배치할 수 있다.")
                def on_button_click():
                    print("버튼 클릭 이벤트 발생!")
                ui.Button("클릭", clicked_fn=on_button_click)

    def on_shutdown(self):
        if self._window:
            self._window.destroy()
            self._window = None
```

Isaac Sim 상단의 Extension 창(Extension Manager)에서 이 확장을 등록해두고 활성화하면 "My Custom Panel"이라는 창이 나타나며 내부에 표시된 버튼을 눌렀을 때 이벤트가 출력된다.

#### USD(Stage)와 노드(Prim) 구조

Isaac Sim이 사용하는 Omniverse 생태계는 Pixar의 USD(Universal Scene Description) 포맷을 기반으로 한다. USD는 3D 장면을 계층 구조로 표현하며, 각 계층 노드를 Prim이라고 부른다. Cube, Sphere 등의 기하 객체, Light, Camera도 전부 Prim으로 관리된다. Python 코드에서 stage를 받아 특정 경로의 Prim을 생성하거나 조회함으로써 장면을 조작한다.

USD Stage는 메모리 상에 존재하는 상태이며, Python 스크립트로 Stage의 상태를 변경하면 뷰포트(Viewport)에 즉시 반영된다. 이때 비활성화된(Stage Update Pause) 상태라면 일시적으로 갱신이 보류될 수 있지만, 일반적으로 Isaac Sim은 자동으로 USD Stage 변경 사항을 렌더링한다.

#### 기하학적 변환(Transform) 처리

Python 스크립팅에서 기하학적 변환을 적용할 때는 UsdGeom.Xformable 클래스를 사용한다. 이 클래스는 Translate, Rotate, Scale 등을 오퍼레이션(Op) 개념으로 구분하여 누적 적용한다. 예를 들어 위치 변환을 적용할 때는 다음과 같이 쓴다.

```python
from pxr import Gf, UsdGeom

prim = stage.GetPrimAtPath("/World/MyCube")
xform = UsdGeom.Xformable(prim)
xform.AddTranslateOp().Set(Gf.Vec3f(10.0, 5.0, 2.0))
```

회전과 스케일도 유사한 형태로 적용한다.

#### 간단한 수학적 예시

Isaac Sim의 물리 및 모션 제어 상황에서 벡터와 행렬 연산이 자주 등장한다. 예를 들어, 어떤 로봇 조인트에 대한 좌표 변환을 표현할 때 아래와 같은 변환 행렬을 사용할 수 있다.

$$
\begin{align} \mathbf{T} = \begin{bmatrix} R & \mathbf{d} \ \mathbf{0}^\mathsf{T} & 1 \end{bmatrix} \quad\in \mathbb{R}^{4\times 4}  \end{align}
$$

여기서 $R$은 $3\times 3$ 회전 행렬, $\mathbf{d}$는 위치 벡터다. Isaac Sim 스크립트에서 이들을 Gf.Matrix4f 형태로 구성해 활용할 수 있다.

#### Isaac Sim 스크립트 구조 설계

규모가 커지면 하나의 스크립트 파일만으로는 관리하기 어려워진다. 이때는 Python 모듈 구조를 활용해 스크립트를 여러 파일로 분할하고, 필요한 부분만 임포트하여 재사용하는 형태가 권장된다. 또한 Isaac Sim용 Extension 형태로 개발하면, 확장성 높은 플러그인 개념으로 코드를 모듈화할 수 있어 유지보수에 용이하다.

#### Isaac Sim Python 디버깅

Isaac Sim에는 Script Editor의 기본 디버깅 기능 외에도 Python 코드를 VSCode나 PyCharm 같은 외부 IDE에서 디버깅할 수 있도록 옵션을 제공한다. 외부 IDE에서 Isaac Sim의 Python 인터프리터를 설정하고 원격 디버깅(Attach to Process) 기능을 활용하면, Isaac Sim에서 실행되는 코드를 중단점(breakpoint)으로 검증할 수 있다.

#### Isaac Sim 스크립트 실행 시 주의사항

Isaac Sim은 물리 시뮬레이션, 렌더링 등 무거운 작업을 실시간으로 수행한다. Python 스크립트가 과도한 연산을 수행하면 Isaac Sim 전체 프레임 레이트가 낮아질 수 있다. 따라서 시뮬레이션 루프 안에서 복잡한 계산을 실행하기보다는 별도의 스레드나 비동기 방식을 활용하여 성능 영향을 최소화하는 방법을 고려해야 한다. 또한 GPU 사용량이 높은 상황에서 UI와 Python 작업을 동시에 하면 Isaac Sim 반응이 잠시 느려질 수 있다는 점도 인지해야 한다.

#### Isaac Sim의 고급 스크립팅 기법

Isaac Sim에서 Python 스크립팅으로 가능한 작업은 단순 오브젝트 생성과 이동에 국한되지 않는다. 로봇암이나 차량, 드론 등 복합 시스템의 동작 시뮬레이션, 센서 데이터 생성, 환경 자동 생성 등 다양한 고급 기능을 구현할 수 있다. 각 기능을 구체적으로 살펴보면 Isaac Sim 환경을 더 깊이 이해하고 활용하는 데 도움이 된다.

#### 로봇 조작(Manipulation)과 움직임 제어

Isaac Sim은 물리엔진을 사용하여 로봇의 멀티 조인트(Joint) 구조와 동적 거동을 사실적으로 구현한다. Python API로 로봇 조인트에 대한 값을 설정하거나 시뮬레이션된 센서 데이터를 읽어서 제어 알고리즘을 적용할 수 있다. 예를 들어 로봇팔의 조인트 각도 배열을 $\mathbf{q}\in \mathbb{R}^n$이라 할 때, 시뮬레이션 과정에서 매 timestep마다 다음과 같은 단계를 거친다.

qt+1=qt+Δqt\begin{align} \mathbf{q}\_{t+1} = \mathbf{q}\_t + \Delta \mathbf{q}\_t \end{align}

여기서 $\Delta \mathbf{q}\_t$는 제어 알고리즘이나 역운동학(Inverse Kinematics, IK) 계산 결과로 얻어진 조인트 변화량이다. Isaac Sim에서는 이러한 제어 과정을 Python 코드에서 자동화하여, 가상의 로봇팔이 특정 좌표로 이동하도록 만들 수 있다.

로봇암(Manipulator) 조작시 주의해야 할 점은 다음과 같다. Isaac Sim의 물리 시뮬레이션 업데이트 주기와 제어 스레드의 주기가 다를 수 있으므로, 서로 동기화가 잘 맞지 않으면 제어가 불안정하게 동작할 수 있다. 따라서 로봇 제어 루프를 만들 때는 Isaac Sim의 물리 스텝에 맞추어 제어 명령을 적용하거나, 별도의 조인트 명령 API를 활용하여 안정적으로 조인트 상태를 설정해야 한다.

#### 물리 속성(Physics Property) 제어와 동역학 시뮬레이션

Isaac Sim Python 스크립팅으로 물리 속성을 프로그래밍적으로 변경할 수 있다. 질량, 관성 텐서, 마찰 계수 등 물리적으로 중요한 속성들을 USD 속성으로 관리하며, 다음과 같은 Python 코드를 사용할 수 있다.

```python
from pxr import UsdPhysics, Gf

stage = omni.usd.get_context().get_stage()
prim = stage.GetPrimAtPath("/World/MyCube")

rigid_body_api = UsdPhysics.RigidBodyAPI.Apply(prim)
mass_api = UsdPhysics.MassAPI.Apply(prim)
mass_api.CreateMassAttr(10.0)
mass_api.CreateCenterOfMassOffsetAttr(Gf.Vec3f(0.0, 0.0, 0.0))
```

위 코드는 Cube 오브젝트에 RigidBodyAPI와 MassAPI를 적용하고 질량을 10.0으로 설정한다. 이처럼 다양한 물리 API를 활용해 동역학 시뮬레이션을 세밀하게 제어할 수 있다.

#### 센서 시뮬레이션과 데이터 캡처

카메라(Lidar, Depth, Stereo, RGB) 등 가상의 센서를 생성하고, Python 스크립트에서 센서 데이터를 실시간으로 읽거나 저장할 수 있다. 예를 들어 Depth 카메라를 만들어 3D 포인트 클라우드를 수집하거나, RGB 카메라로 렌더링된 이미지를 저장해 딥러닝 학습용 데이터셋을 생성하는 용도로 활용할 수 있다.

카메라 생성 예시는 다음과 같은 방식으로 진행한다.

```python
import omni
from pxr import UsdGeom

stage = omni.usd.get_context().get_stage()
camera_prim_path = "/World/CameraSensor"
camera = UsdGeom.Camera.Define(stage, camera_prim_path)
camera.CreateFocalLengthAttr(24.0)
camera.CreateFocusDistanceAttr(200.0)
camera.CreateHorizontalApertureAttr(20.955)
camera.CreateVerticalApertureAttr(15.2908)
```

이후 Isaac Sim이 제공하는 Sensor Extension 기능을 통해 뎁스 이미지, 포인트 클라우드 등 실제로 시뮬레이션에 기반한 센서 출력을 얻을 수 있다. Python API로 해당 데이터를 폴더로 저장하거나 ROS2 토픽으로 퍼블리시하도록 만들 수도 있다.

#### 라이트와 카메라 컨트롤

시뮬레이션 환경 내 조명(Light)과 카메라 시점을 스크립트로 제어하는 것은 시각적 검증, 데이터셋 수집, 시나리오 테스트에 필수적이다. Light와 Camera도 모두 USD Stage의 Prim으로서 Python API로 접근 가능하다. Isaac Sim 내 Light Prim은 리얼타임 레이트레이싱 RTX 기능을 고려해 물리 기반 조명(PBR)과 호환된다.

라이트 종류(점광원, 스포트라이트, 영역광원 등)에 따라 USD에서 설정해야 하는 파라미터가 약간씩 다르다. 아래는 스포트라이트를 생성하는 예시다.

```python
from pxr import UsdLux

light_prim_path = "/World/SpotLight"
spot_light = UsdLux.SphereLight.Define(stage, light_prim_path)
spot_light.CreateIntensityAttr(5000.0)
spot_light.CreateColorAttr((1.0, 1.0, 1.0))
```

이렇게 생성된 라이트는 Script Editor 또는 서드파티 IDE에서 Python 코드를 통해 실시간으로 수정할 수 있다.

#### 프로시저럴 월드 생성

자동 시나리오 테스트를 위해서는 주변 환경이나 오브젝트를 무작위로 배치하는 프로시저럴 월드 생성 기법이 유용하다. Python 스크립트에서 일정 범위 안에 다양한 오브젝트를 생성하고 위치를 랜덤으로 설정한 후, 그 결과를 가지고 수십\~수백 회 반복 시뮬레이션하여 로봇이 여러 환경 상황에서 잘 작동하는지 검증할 수 있다.

아래 예시는 10개의 랜덤 위치에 큐브를 생성하는 스니펫이다.

```python
import random
from pxr import UsdGeom, Gf

for i in range(10):
    x = random.uniform(-10, 10)
    y = random.uniform(-10, 10)
    z = 0
    path = f"/World/RandomCube_{i}"
    cube = UsdGeom.Cube.Define(stage, path)
    xform = UsdGeom.Xformable(cube.GetPrim())
    xform.AddTranslateOp().Set(Gf.Vec3f(x, y, z))
```

위 스크립트를 반복 호출하면 Isaac Sim Stage에 여러 큐브가 생성되고, 각 큐브는 위치가 무작위로 배치된다.

#### 사용자 정의 확장(Extension) 구조

Isaac Sim에서 복잡한 작업을 체계적으로 관리하려면 확장(Extension) 구조를 사용하는 것이 권장된다. Extension은 폴더 구조와 진입점(on\_startup, on\_shutdown)을 갖추어, 기능을 모듈화하고 필요에 따라 활성화·비활성화할 수 있게 해준다. 확장 정보를 담은 `ext.py`와 실제 로직을 담은 Python 소스들을 함께 배포해 재사용할 수도 있다.

이러한 확장 구조는 로보틱스 시뮬레이션 프로젝트에서 매우 유용하다. 예를 들어 로봇 팔 제어 확장, 모바일 로봇 내비게이션 확장, 학습용 RL 환경 확장 등으로 나누면, Isaac Sim을 큰 프레임워크처럼 다룰 수 있다.

#### 로깅(Logging)과 모니터링

Isaac Sim 스크립팅 중 발생하는 정보를 추적하기 위해서는 Python 표준 `logging` 모듈이나 Omniverse의 carb.logging을 사용할 수 있다. 다음과 같은 방법으로 로그 레벨을 설정하고, 유용한 정보를 로그로 남길 수 있다.

```python
import carb
carb.settings.get_settings().set("/app/verbosity", "INFO")

carb.log_info("이것은 정보 레벨 로그이다.")
carb.log_warn("이것은 경고 레벨 로그이다.")
carb.log_error("이것은 에러 레벨 로그이다.")
```

Script Editor 콘솔이나 외부 로그 파일에서 이러한 로그 기록을 실시간 모니터링할 수 있다. 대규모 시뮬레이션에서 로그 모니터링은 매우 중요하다.

#### Isaac Sim과 ROS2 연동의 전초

ROS2를 통해 실제 로봇 제어 프로세스와 Isaac Sim 시뮬레이션을 연결하려면, Python 스크립팅 레벨에서 ROS2 메시지를 퍼블리시·구독하거나 ROS2 액션, 서비스 등을 호출·제공하는 작업이 필요하다. Isaac Sim은 ROS2용 Extension을 제공하며, Python 노드로 동작시킬 수도 있다.

ROS2 통합 시에는 다음과 같은 순서를 거치게 된다. Isaac Sim에서 ROS2 Extension을 활성화하고, Python 스크립트에서 $rclpy$ 모듈을 임포트하여 노드를 초기화한다. 이후 로봇 상태를 토픽으로 퍼블리시하거나, 로봇 제어 명령을 구독해 시뮬레이션 내 로봇에게 적용한다. Isaac Sim의 확장 구조와 ROS2 노드 구조를 잘 조합해주면, 실제 현장에서 사용하는 ROS2 패키지와 같은 인터페이스로 시뮬레이션을 다룰 수 있다.

#### Isaac Sim 성능 최적화

다수의 오브젝트가 존재하거나 복잡한 물리 연산을 수행할 경우, Isaac Sim의 FPS가 떨어질 수 있다. Python 스크립팅 계층에서 해야 할 작업이 많을 때도 성능 저하가 발생한다. 다음과 같은 방식으로 성능을 관리할 수 있다.

스테이지 생성·편집 시 일괄 작업 모드로 전환하여 중간 렌더링 오버헤드를 줄인다. 복잡한 연산(대규모 행렬 연산, AI 모델 추론 등)은 별도 스레드나 확장 모듈을 통해 처리하고, Isaac Sim 메인 스레드에서는 최소한의 API 호출만 수행한다. Python 함수를 C++로 구현한 바인딩 라이브러리를 사용해 연산 부분을 최적화한다.

고성능 시뮬레이션을 위해서는 CPU, GPU 리소스를 충분히 확보하고, Isaac Sim을 지원하는 NVIDIA GPU를 사용하는 것이 필수다.

#### 예제: 강화학습(RL) 환경 구축

강화학습 시뮬레이션 환경을 Isaac Sim에서 구축할 때는 Python 스크립팅을 통해 에이전트(로봇) 상태 관측, 보상 계산, 액션 적용을 자동화한다. Isaac Sim을 Gym-like 환경으로 감싸는 방법이 주로 쓰인다.

아래는 Isaac Sim 내부에서 강화학습 루프를 일부 구현한 Python 스니펫 예시다.

```python
import omni
import random

def reset_environment():
    # 로봇과 오브젝트를 초기 위치로 되돌리는 로직
    pass

def get_observation():
    # 센서 데이터나 로봇 상태를 반환
    return []

def get_reward(obs):
    # 행동 결과에 따른 보상을 계산
    return 0.0

def step(action):
    # 로봇에 액션 적용
    # 물리 스텝 진행
    pass

reset_environment()
for episode in range(100):
    obs = get_observation()
    done = False
    while not done:
        action = random.uniform(-1, 1)  # 임의 액션
        step(action)
        new_obs = get_observation()
        reward = get_reward(new_obs)
        # 에피소드 종료 조건 검사
        if abs(reward) > 10:
            done = True
```

위 형태의 코드가 Isaac Sim 물리 업데이트 사이클과 동기화되어 돌아간다면, 가상의 로봇이 강화학습 환경에서 시뮬레이션을 통해 학습할 수 있다.

#### 시뮬레이션 이벤트와 타임라인 제어

Isaac Sim에서 물리 시뮬레이션은 타임라인(Timeline)이라는 개념으로 제어된다. 타임라인은 시뮬레이션 시간의 흐름을 담당하며, 재생(Play), 정지(Stop), 일시 중지(Pause) 등의 상태를 갖는다. Python으로 타임라인을 직접 제어하면, 특정 시점에 이벤트를 트리거하거나 시나리오를 단계적으로 진행할 수 있다.

아래 예시는 타임라인을 직접 구동하고, 특정 시간(프레임)마다 함수를 호출하는 방식이다.

```python
import omni

timeline = omni.timeline.get_timeline_interface()

def on_update(e: omni.timeline.TimelineEvent):
    if e == omni.timeline.TimelineEvent.CURRENT_TIME_CHANGE:
        current_frame = timeline.get_current_time()  # 프레임 번호 혹은 시간
        if current_frame == 100:
            print("프레임 100에서 특별한 이벤트 실행!")

omni.timeline.get_timeline_event_stream().create_subscription_to_pop(on_update, name="timeline_update_sub")
timeline.play()
```

위 코드는 Isaac Sim에서 시뮬레이션이 진행되며 매 프레임마다 `on_update` 콜백을 호출하도록 구독(subscription)을 생성한다. 특정 프레임(100)에 도달했을 때 원하는 작업을 실행할 수 있다.

#### 복합 USD 레이어 관리

USD 포맷은 레이어(Layer)와 컴포지션(Composition)을 통해 복잡한 장면을 구성할 수 있게 해준다. Isaac Sim에서는 Python 스크립트를 통해 레이어를 병합하거나 서브레이어(SubLayer)로 추가하여, 특정 요소만 분리·관리하는 방식을 취할 수 있다. 대규모 프로젝트에서는 지오메트리, 재질, 물리 설정, 애니메이션 등을 여러 레이어로 나누고 시뮬레이션 시에는 이들을 합치는 구조가 일반적이다.

아래 예시는 스크립트로 새로운 Stage를 생성하고 서브레이어를 추가하는 간단 예시다.

```python
import omni
from pxr import Sdf, Usd

new_stage_id = omni.usd.get_context().create_stage()
stage = omni.usd.get_context().get_stage()

layerA = Sdf.Layer.CreateNew("LayerA.usd")
layerB = Sdf.Layer.CreateNew("LayerB.usd")

stage.GetRootLayer().subLayerPaths.append(layerA.identifier)
stage.GetRootLayer().subLayerPaths.append(layerB.identifier)

print(stage.GetRootLayer().subLayerPaths)
```

이렇게 여러 레이어를 추가해두면, Isaac Sim에서 레이어별로 구분된 객체와 설정을 손쉽게 관리할 수 있다. 실시간 시뮬레이션 중에도 스크립트를 통해 레이어를 교체하거나 읽기 전용으로 전환하여 시나리오 변화를 구현할 수 있다.

#### Domain Randomization과 데이터셋 생성

Isaac Sim에서는 Domain Randomization(DR) 기능을 통해, 배경·광원·오브젝트·재질(Material) 등을 무작위로 변경하며 센서 데이터를 수집하는 작업을 자동화할 수 있다. 이는 딥러닝 모델 학습 시, 다양한 데이터로 일반화 성능을 높이는 데 활용된다.

DR 스크립팅 시에는 Isaac Sim이 제공하는 DR Extension(omni.isaac.dr) API를 사용할 수도 있고, 사용자가 직접 Python 로직을 짜서 무작위 설정을 적용할 수도 있다. 예를 들어 텍스처를 무작위로 바꿀 때 다음과 같은 방식으로 접근한다.

```python
import omni
import random
from pxr import UsdShade, Sdf

stage = omni.usd.get_context().get_stage()
prim_path = "/World/ObjectWithMaterial"
material_prim = stage.GetPrimAtPath(prim_path)
shader = UsdShade.MaterialBindingAPI(material_prim).GetDirectBinding().GetMaterial()
if shader:
    for input_name in ["diffuse_color", "specular_color"]:
        mat_input = shader.GetInput(input_name)
        if mat_input:
            rand_color = (random.random(), random.random(), random.random())
            mat_input.Set(rand_color)
```

이 코드는 임의의 컬러를 생성하여 머터리얼에 주입함으로써 객체의 색상을 계속 바꿀 수 있다. 이를 카메라 센서와 결합해 이미지를 찍으면 다채로운 훈련용 데이터셋을 얻을 수 있다.

#### 모듈식 파이프라인 구성

대규모 시뮬레이션 환경을 구성할 때는, Isaac Sim 스크립트를 작은 기능 단위로 나누어 모듈 형태로 구현하는 것이 유지보수에 유리하다. 예를 들어 다음과 같은 구조를 상정할 수 있다.

* 시나리오 초기화 모듈: 초기 배치, 오브젝트 생성
* 로봇 컨트롤 모듈: ROS2 동기화, 조인트 제어, 궤적 계획
* 센서 처리 모듈: 카메라·LiDAR 데이터 수집
* DR 모듈: 무작위 배치, 조명·재질 변화
* 학습(또는 테스트) 모듈: 에피소드 반복, 보상 계산, 로그 출력

각 모듈을 하나의 Python 패키지로 만들어두고, 메인 스크립트에서 이들을 결합하면 Isaac Sim에서 다루는 기능이 복잡해져도 비교적 깔끔하게 관리할 수 있다. 예컨대 Isaac Sim을 실행할 때 특정 `entry_point.py`를 호출하면, 모듈별 로직이 순차적으로 불려와 시뮬레이션을 제어한다.

#### 사용자 정의 물리 재질(Physics Material)

사실감 있는 시뮬레이션을 위해서는 마찰 계수, 탄성 계수 등을 반영한 물리 재질이 필요하다. Isaac Sim 스크립트에서 UsdPhysics.MaterialAPI를 통해 다양한 재질을 정의할 수 있다. 예컨데 로봇 휠과 지면 사이의 마찰 계수를 높게 설정해서 실제 로봇 주행에 가까운 거동을 재현할 수 있다.

```python
from pxr import UsdPhysics

material_path = "/World/PhysicsMaterial"
material_prim = stage.DefinePrim(material_path, "PhysicsMaterial")
physics_material_api = UsdPhysics.MaterialAPI(material_prim)
physics_material_api.CreateStaticFrictionAttr(1.0)
physics_material_api.CreateDynamicFrictionAttr(0.8)
physics_material_api.CreateRestitutionAttr(0.05)
```

이렇게 만들어진 물리 재질을 Rigid Body 오브젝트에 할당해주면, Isaac Sim의 물리엔진에서 해당 재질 파라미터를 고려하여 충돌·마찰 거동을 계산하게 된다.

#### C++로 확장된 Python 바인딩

Isaac Sim은 Python을 주요 스크립팅 언어로 사용하지만, 내부적으로 C++로 구현된 많은 기능을 제공한다. 사용자가 직접 C++로 성능이 중요한 로직을 작성하고, Python 바인딩을 통해 Isaac Sim 스크립트에서 호출하는 것도 가능하다. 이 방법은 반복 계산이나 대규모 수치 해석이 필요한 상황에서 유용하다.

C++ 확장 모듈을 작성한 뒤 Isaac Sim의 `python.sh`(또는 윈도우에서는 대응하는 python.exe)로 컴파일할 수도 있다. 컴파일 후에는 Isaac Sim Python 환경에서 `import`하여 사용할 수 있다. 이 과정을 통해 Isaac Sim 상의 스크립트 실행 성능을 크게 높일 수 있다.

#### Isaac Sim 내장 Task Graph 활용

Omniverse Kit 기반인 Isaac Sim에는 Task Graph라는 그래프 형식의 계산 파이프라인 시스템이 존재한다. Python 스크립트로 노드(Node)와 에지(Edge)를 생성해, 특정 순서로 작업을 처리할 수 있도록 구성할 수 있다. Task Graph를 잘 활용하면, 물리 업데이트 이후에 센서 업데이트를 하고, 그 이후에 제어 계산을 하는 순서 등 시뮬레이션 이벤트 흐름을 명확히 설정 가능하다.

예시로, Task Graph에 Custom Node를 추가하는 방법을 단편적으로 살펴보면 다음과 같이 진행된다.

```python
import omni.graph.core as og

graph = og.Graph("/MyGraph")

node = graph.create_node("omni.graph.examples.PythonNode", "MyPythonNode")
node.get_attribute("outputs:execOut").connect(another_node.get_attribute("inputs:execIn"))

script_attr = node.get_attribute("inputs:code")
script_attr.set_value("""
def compute(db):
    print("Task Graph 내에서 Python 코드 실행")
    db.outputs.execOut = True
""")
```

이런 식으로 그래프 내 PythonNode를 만들어 Python 코드를 등록하면, Task Graph 상에서 이벤트 순서에 따라 자동으로 해당 코드를 실행해준다. Task Graph를 이용하면, UI 이벤트 스크립팅과는 별개로 엔진 레벨에서 순차적·병렬적 처리를 설계할 수 있다.

#### 비동기·스레딩 기법과 Isaac Sim

Isaac Sim은 메인 스레드에서 물리, 렌더링, Python 스크립팅 등을 동작시킨다. 파이썬 스레딩(Threading)을 활용해 백그라운드에서 무거운 계산을 처리할 수 있지만, UI나 Stage와 직접 상호작용하는 작업은 반드시 메인 스레드에서 실행해야 한다. 따라서 스레드를 통해 병렬 계산을 수행한 뒤, Isaac Sim 엔진 관련 객체 접근 시에는 메인 스레드로 콜백을 보내는 구조가 자주 쓰인다.

다음은 간단히 파이썬 Thread를 사용해 병렬 계산을 하고 결과를 Isaac Sim 메인 스레드에서 적용하는 예시다.

```python
import threading
import omni

def heavy_calculation(data):
    # 복잡한 연산 수행
    result = sum(data)
    # 메인 스레드에서 Stage 변경을 하기 위한 콜백
    def apply_result_to_sim():
        print(f"결과 {result}를 Isaac Sim에 반영")
        # USD Stage 조작 로직 등
    # 메인 스레드에서 함수 실행
    omni.kit.app.get_app().update_on_main_thread(apply_result_to_sim)

data = list(range(10000000))
t = threading.Thread(target=heavy_calculation, args=(data,))
t.start()
```

이처럼 Isaac Sim에서는 멀티스레드 프로그래밍 시, 엔진 객체를 직접 제어하려면 반드시 메인 스레드로 작업을 전달해야 한다는 점이 핵심이다.

#### UI 커스터마이징과 마우스/키보드 이벤트

Omni.UI API를 활용하면 Isaac Sim 화면에 복잡한 커스텀 위젯을 만들어 넣을 수 있다. Isaac Sim은 3D 뷰포트의 마우스 이벤트, 키 이벤트를 Python 코드로 후킹할 수 있도록 지원한다. 예를 들어 특정 키를 누르면 로봇 팔이 동작하도록 하거나, 뷰포트에서 오브젝트를 클릭했을 때 선택된 객체 정보를 출력하는 식의 기능을 구현할 수 있다.

아래 예시는 뷰포트 마우스 클릭 시 그 위치를 로그에 출력하는 예시다.

```python
import omni.ui as ui
import carb

vp_window = ui.Window("Viewport Window", width=800, height=600)
viewport = ui.ViewportWidget()

def on_mouse_event(event):
    if event.type == carb.input.MouseEventType.LEFT_BUTTON_DOWN:
        pos = event.normalized_pos
        print(f"마우스 왼쪽 버튼 클릭: {pos}")

viewport.subscribe_to_mouse_events(on_mouse_event)
with vp_window.frame:
    viewport
```

이 코드를 실행하면, "Viewport Window" 위젯에서 마우스를 클릭할 때마다 해당 좌표가 콘솔에 표시된다. 이 좌표는 뷰포트 상에서 정규화된 0\~1 범위를 갖는다.

#### Isaac Sim과 클라우드 환경

대규모 시뮬레이션이나 데이터셋 생성을 위해 Isaac Sim을 클라우드(예: AWS, Azure, Google Cloud) 환경에 배포하는 사례도 있다. 이 경우 Isaac Sim 클라이언트(로컬)와 클라우드 VM(서버) 간에 스트리밍 기술이 사용되는데, Python 스크립트는 서버 측 Isaac Sim에서 수행되어야 한다. 원격 실행 환경에서 Script Editor를 직접 사용하는 대신, SSH나 RDP를 통해 접근하거나, Isaac Sim 전용 웹 UI를 사용하는 방식을 취할 수 있다.

클라우드 환경에서 스크립팅을 하면 GPU 자원을 쉽게 확장할 수 있고, 여러 VM 인스턴스에서 병렬로 시뮬레이션을 돌려 대량의 데이터를 빠르게 수집할 수 있다. 다만 네트워크 지연과 스트리밍 오버헤드를 고려해야 하므로, 상호작용보다는 자동화·배치(batch) 방식의 시뮬레이션에 최적화되어 있다.

#### 시나리오 스크립팅과 자동화

Isaac Sim은 특정 이벤트 흐름이나 시나리오를 구성하여, 로봇이 주어진 상황에서 어떻게 반응하는지 자동으로 테스트하거나 시뮬레이션을 재생·중단·재설정하는 기능을 제공한다. Python 스크립트로 이러한 시나리오를 프로그래밍적으로 구현하면, 대규모 테스트나 반복 학습을 간편하게 진행할 수 있다.

예를 들어 시나리오 스크립트에서 다음과 같은 단계로 자동화를 구성할 수 있다.

* Isaac Sim 초기화
* 환경(월드) 설정
* 로봇 생성 및 초기 포즈 설정
* 시뮬레이션 플레이(Play)
* 특정 시간이 흐른 후, 이벤트 트리거 또는 시뮬레이션 정지
* 결과 측정 및 로깅
* 환경 재설정(reset)
* 반복

이 과정을 잘 설계하면, 사람이 일일이 GUI로 작업하지 않고도 여러 환경 조건에서 동일한 테스트를 실행할 수 있다. 뿐만 아니라 HUD(Heads-Up Display) 오버레이로 텍스트 정보를 띄워주거나, 임의 시점에 애니메이션을 재생하는 등 고급 기능도 자유롭게 스크립팅할 수 있다.

#### 상호작용 스크립트(Interactivity)와 핫 리로드(Hot Reload)

Isaac Sim에는 Script Editor를 이용해 코드를 실시간으로 바꾼 뒤 바로 실행할 수 있는 기능이 있으므로, 개발 과정에서 빠르게 수정과 테스트를 반복 가능하다. Script Editor 외에도 확장(Extension)을 사용할 때는 코드를 수정하면 Isaac Sim이 해당 확장을 재로드해주는 옵션을 제공한다(Hot Reload). 이를 통해 스크립트가 포함된 확장 디렉터리 내 Python 파일을 고치고 저장하면 Isaac Sim이 곧바로 코드를 다시 불러와 반영한다.

Hot Reload는 편리한 반면, 싱글톤 객체나 전역 변수를 다루는 과정에서 예기치 못한 충돌이나 메모리 누수 문제가 발생할 수도 있다. 따라서 확장을 구조적으로 설계할 때는 on\_startup, on\_shutdown에 초기화와 자원 해제를 명확히 정의해서, 매번 재로드 시 동일한 로직이 잘 수행되도록 해야 한다.

#### 다중 로봇 시뮬레이션

Isaac Sim에서는 하나의 스테이지 안에 여러 로봇을 배치하고, 이들의 상호작용을 시뮬레이션할 수 있다. 로봇끼리 충돌 회피를 수행하거나 협업 작업을 처리하는 시나리오를 만들려면, Python 코드를 통해 각각의 로봇 Prim을 생성·제어하고 센서 데이터를 별도로 취급한다. 로봇이 두 대 이상일 때는 각 로봇 이름 또는 Prim 경로를 구분해 제어해야 하며, ROS2를 사용한다면 토픽 네임스페이스(namespace)를 분리해주는 것이 일반적이다.

예컨대 두 대의 모바일 로봇(A, B)이 있을 때, Python 스크립트에서 A가 주행 중일 때 B의 경로를 실시간으로 회피하도록 제어 로직을 구현할 수 있다. Isaac Sim 물리엔진이 서로 다른 로봇 간의 충돌 판정을 수행해주므로, 개발자는 충돌시 반발력이나 조인트 움직임을 관측하며 복합 시나리오를 디자인할 수 있다.

#### Isaac Sim과 외부 데이터 소스 연동

외부에서 들어오는 데이터(예: CSV, JSON, SQL, 메시지 큐 등)를 Isaac Sim 내부 스크립트로 받아와 시뮬레이션 상태를 변경하거나, 반대로 시뮬레이션 결과를 외부로 내보낼 수도 있다. 예를 들어 다음과 같은 흐름이 가능하다.

* 로컬 데이터베이스(또는 원격 DB)에서 로봇 경로점을 불러온다.
* 시뮬레이션 로봇에게 이 경로점을 순차적으로 적용해 움직인다.
* 각 위치에서 센서 데이터를 캡처해 로컬 폴더나 원격 서버에 저장한다.

Python 스크립트 내에서 표준 라이브러리나 서드파티 라이브러리를 사용해 파일 입출력 또는 네트워크 통신을 수행하고, Isaac Sim API로 스테이지를 제어하면 된다. 이 방식으로 Isaac Sim을 대규모 분산 시뮬레이션 파이프라인의 한 구성 요소로 통합할 수 있다.

#### Isaac Sim에서 애니메이션(USD Skel) 다루기

USD는 Skel(스켈레톤) 기반 애니메이션 기능을 지원한다. Isaac Sim에서도 캐릭터 애니메이션, 로봇 조인트 움직임 등을 Skel 프리미티브로 표현할 수 있다. Python 스크립트로 USD Skel을 다뤄서 특정 프레임마다 조인트의 포즈를 업데이트하거나, 사전에 녹화된 모션 캡처 데이터를 불러와 시뮬레이션에서 재생할 수 있다.

애니메이션 작업 흐름은 다음과 같이 요약할 수 있다.

* USD Skel 정의(스켈레톤 계층 구조, 본)
* 메시(Character Mesh)와 스켈을 바인딩
* 조인트 변환을 타임샘플(TimeSample)로 기록
* 시간에 따라 애니메이션을 재생

Isaac Sim 스크립트에서 Skel에 접근해 각 조인트의 로컬 변환을 수정하면, 프레임 업데이트 시 해당 애니메이션을 재생하게 된다. 이를 활용해 가상 캐릭터나 가상 로봇 팔이 실제처럼 움직이는 동작 시뮬레이션을 만들 수 있다.

#### Isaac Sim 물리 이벤트 훅(Hook)

물리 시뮬레이션 중 충돌이 발생하거나, 조인트에 설정된 힘이 임계점을 초과하는 등 특정 물리 이벤트가 발생했을 때 Python 콜백을 호출하도록 설정할 수 있다. 이 기능은 Isaac Sim의 확장 API나 Task Graph 노드 형태로 제공된다. 이벤트 기반 시스템을 잘 활용하면, 예컨대 로봇 팔이 장애물을 만났을 때 자동으로 경로 우회 로직을 실행하거나, 충돌 횟수를 카운트하여 안전 진단을 수행하는 식의 응용이 가능하다.

직접적으로 충돌 이벤트를 후킹하는 예시는 아래와 같이 간단한 형태로 나타낼 수 있다(내부 API는 버전에 따라 달라질 수 있다).

```python
import omni.physx
def on_collision(event_data):
    print("충돌 이벤트:", event_data)

collision_sub = omni.physx.acquire_physics_interface().subscribe_on_collision(on_collision)
```

이렇게 구독을 걸어두면, 시뮬레이션 중 충돌이 발생할 때마다 on\_collision 콜백이 호출되고 관련 정보를 이벤트 객체로 받을 수 있다.

#### Isaac Sim 확장(Extension) 배포

개인 프로젝트든 기업용 솔루션이든, Isaac Sim 스크립트를 재사용하고 배포하기 위해서는 확장(Extension) 형태가 적합하다. 확장을 배포할 때 고려해야 할 요소는 다음과 같다.

* 확장의 메타데이터(ext\_id, 버전, 권장 Isaac Sim 버전 등)를 `ext.py`에 정의
* 의존하는 다른 확장의 ID를 명시
* Python 요구사항(requirements.txt 등)이나 추가 리소스(머터리얼, 텍스처, USD 파일) 경로 설정
* 테스트 환경에서 확장을 로드했을 때 정상적으로 작동하는지 확인

배포 시에는 확장 폴더 전체를 공유하거나, 사내/사외 Omniverse Registry에 업로드해 필요한 사용자가 다운받아 사용할 수 있게 할 수 있다. Isaac Sim 사용자는 Extension Manager를 통해 이 확장을 활성화하면, 자동으로 관련 Python 코드를 로드하고 UI나 이벤트 기능을 사용할 수 있다.

#### Isaac Sim UI/UX 심화

Omni.UI 라이브러리에는 라벨, 버튼, 텍스트 필드, 콤보박스, 트리뷰, 테이블, 플롯(plot) 등 다양한 위젯이 포함되어 있다. 이를 조합하면 Isaac Sim 안에 독자적인 GUI 툴을 구축할 수 있다. 예컨대 로봇 암 제어 패널, 센서 데이터 실시간 플로팅, 매개변수 슬라이더 등을 만들어, 시뮬레이션 결과를 시각적으로 확인하고 제어할 수 있다.

실시간 플롯 예시는 다음과 같이 접근할 수 있다.

```python
import omni.ui as ui
import math

window = ui.Window("Real-time Plot", width=400, height=300)
plot_data = [0] * 100
frame_count = 0

with window.frame:
    with ui.VStack():
        plot = ui.Plot()
        curve = plot.get_curve("sin_curve")

def on_tick(dt):
    global frame_count
    frame_count += 1
    val = math.sin(frame_count * 0.1)
    plot_data.pop(0)
    plot_data.append(val)
    curve.set_data(range(len(plot_data)), plot_data)

subscription = omni.kit.app.get_app().get_update_event_stream().create_subscription_to_pop(on_tick, name="plot_tick")
```

위 코드는 Isaac Sim 메인 스레드의 업데이트 이벤트마다 `on_tick` 함수가 호출되어, 플롯 위에 실시간으로 사인 곡선을 그린다. 이런 방식으로 시뮬레이션 데이터(예: 로봇 조인트 각도, 속도, 힘)를 시각화할 수 있다.

#### Isaac Sim 스크립팅 베스트 프랙티스

Isaac Sim은 시뮬레이션, 렌더링, 이벤트, Python 스크립팅이 모두 동시다발적으로 이뤄지는 환경이다. 안정적이고 확장 가능하며 유지보수가 쉬운 스크립트 구조를 원한다면, 다음과 같은 점을 염두에 두면 좋다.

* 모든 Isaac Sim API 호출은 메인 스레드에서 이루어지도록 관리
* USD Stage 조작 시, 꼭 필요한 상황이 아니면 무수히 많은 객체를 한 번에 생성하지 않도록 주의
* 프레임 루프(물리 스텝) 내부에 무거운 계산이나 블로킹 I/O 작업을 넣지 말 것
* 테스트 스크립트와 실제 운영 스크립트를 구분해 배포(Extension) 구조화
* 충분한 로깅과 예외처리로, 스크립트 오동작 시 원인을 쉽게 파악할 수 있도록 설계

Isaac Sim 스크립트를 잘 설계하면, 복잡한 로봇 환경 시뮬레이션이나 AI 학습 파이프라인도 안정적으로 운용할 수 있다.

#### 고급 디버깅과 트레이싱(Tracing)

Isaac Sim은 많은 양의 병렬 프로세스와 이벤트를 처리하므로, 복잡한 문제를 디버깅하기 위해서는 체계적인 접근이 필요하다. Python 스크립트 내 로그를 남기는 것 외에도, NVIDIA Nsight Systems와 같은 툴로 GPU/CPU 성능 프로파일링을 진행하거나, Omniverse Kit 자체가 제공하는 디버깅 기능을 활용할 수 있다. Isaac Sim에서는 다음과 같은 트레이싱(Tracing) 기법을 지원한다.

* Omni::Diagnostics 모듈을 통한 이벤트 트레이싱
* Omni::Graphics 디버깅 기능으로 GPU 호출 추적
* PhysX 디버그 드로잉(Debug Drawing) 기능을 활성화하여 충돌체와 관성 텐서 시각화

특히 물리 시뮬레이션 문제를 진단할 때는 USD Stage 상에서 각 오브젝트의 충돌체 모양, 질량 중심, 조인트 구속조건 등을 시각적으로 표시하게 하면 훨씬 직관적이다. Python 스크립트로 이러한 디버그 드로잉 모드를 켜고 끌 수 있으므로, 필요할 때만 시각화하여 성능 부담을 줄일 수 있다.

#### Isaac Sim 물리 엔진 추가 설정

Isaac Sim은 PhysX를 물리 엔진으로 기본 채택하고 있다. Python 스크립트로 PhysX의 특정 옵션을 세부 설정할 수 있다. 예를 들어 서브스텝(Substep) 개수, Solver Iteration 횟수, Contact Offset, Rest Offset, CCD(Continuous Collision Detection) 활성화 등을 조정하면 시뮬레이션 정밀도와 성능 사이의 균형을 맞출 수 있다. 예시는 아래와 같다.

```python
import omni.physx as physx

physx_interface = physx.acquire_physics_interface()
settings = physx_interface.get_physx_scene().GetPhysxSceneAPI()
settings.CreateGravityAttr().Set((0.0, 0.0, -9.81))
settings.CreateEnableCCDAttr().Set(True)  # 연속 충돌 감지
settings.CreateSolverPositionIterationCountAttr().Set(16)
settings.CreateSolverVelocityIterationCountAttr().Set(8)
```

이렇게 설정을 바꾸면 Isaac Sim 재시작 없이도 물리 엔진 파라미터가 바뀌어, 더 현실적인 충돌 처리나 더 빠른 시뮬레이션 등을 선택할 수 있다.

#### 리플레이(Replay)와 데이터 녹화

Isaac Sim에서 진행했던 시뮬레이션을 USD Time Samples 형태로 녹화해두고, 나중에 재생(Replay)할 수 있다. 이는 디버깅이나 프레젠테이션 목적에서 매우 유용하다. Python 스크립트로 Time Sample을 저장하거나 Omniverse Nucleus 서버에 업로드한 뒤, 재생 시점을 자유롭게 조절할 수 있다. 다른 방식으로는 Isaac Sim이 제공하는 로깅 인터페이스를 통해 센서나 로봇 상태를 기록하고, 이를 CSV나 ROS2 Bag 형식으로 변환해두었다가 다시 로드할 수도 있다.

Python 스크립트를 사용해 USD Time Samples를 생성할 때는, 각 프레임에서 Prim의 속성을 타임스텝별로 설정해주면 된다. 예를 들어 Cube 오브젝트가 프레임마다 위치가 달라지는 애니메이션을 만들려면 다음처럼 처리한다.

```python
from pxr import Usd, UsdGeom, Sdf, Gf

stage = omni.usd.get_context().get_stage()
timeline = omni.timeline.get_timeline_interface()

cube_path = "/World/AnimatedCube"
UsdGeom.Cube.Define(stage, cube_path)
for frame in range(100):
    time_code = Usd.TimeCode(frame)
    prim = stage.GetPrimAtPath(cube_path)
    xform = UsdGeom.Xformable(prim)
    translate_op = xform.AddTranslateOp()
    translate_op.Set(Gf.Vec3f(frame*0.1, 0, 0), time_code)
```

위 코드를 실행한 뒤 Time Slider를 0\~99 프레임으로 이동해보면, Cube가 매 프레임마다 움직이는 애니메이션이 생성되었음을 확인할 수 있다. 이 과정을 물리 시뮬레이션과 결합하면, 실제 물리 거동을 USD Time Sample로 기록해 재생 가능해진다.

#### Isaac Sim 시뮬레이션과 인공지능(AI) 파이프라인 결합

Isaac Sim은 딥러닝 및 인공지능 연구용으로 널리 활용된다. 시뮬레이션 환경에서 생성한 대량의 이미지 데이터를 AI 모델 훈련에 사용하거나, AI 모델이 예측·제어한 결과를 즉시 시뮬레이션에 반영해 피드백 루프를 구성할 수 있다. Python 스크립트로 PyTorch나 TensorFlow 라이브러리를 임포트하면, Isaac Sim 안에서 곧바로 AI 모델 추론을 실행할 수도 있다.

다만 Isaac Sim 프로세스가 수행해야 할 작업(렌더링, 물리 시뮬레이션)과 AI 연산이 동시에 GPU를 사용하면, GPU 메모리 부족이나 리소스 경합이 발생할 수 있다. 이를 피하기 위해 별도 프로세스나 별도 GPU를 사용하거나, Docker 컨테이너를 통해 Isaac Sim과 AI 모듈을 분리 구동하는 방법이 있다.

아래 예시는 Isaac Sim 내 Script Editor에서 PyTorch를 활용해 간단한 텐서 연산을 수행한 뒤, 시뮬레이션에 반영하는 데모 코드다. 실제 ML 작업은 훨씬 복잡하겠지만, 개념적으로 이러한 흐름이 가능함을 보여준다.

```python
import torch
import omni
from pxr import UsdGeom, Gf

x = torch.tensor([1.0, 2.0, 3.0], device="cuda")
y = 2.0 * x
print(y)  # tensor([2.0, 4.0, 6.0], device='cuda:0')

stage = omni.usd.get_context().get_stage()
cube = UsdGeom.Cube.Define(stage, "/World/MLCube")

xform = UsdGeom.Xformable(cube.GetPrim())
result_sum = y.sum().item()
xform.AddTranslateOp().Set(Gf.Vec3f(result_sum, 0.0, 0.0))
```

위 예시에서 PyTorch 텐서를 GPU에서 연산한 뒤 결과를 Cube 위치로 반영한다. 실제 프로젝트에서 이를 확장해, 예측 모듈(예: 물체 인식, 포즈 추정)을 시뮬레이션과 실시간으로 연동할 수 있다.

#### Isaac Sim 리소스(Asset) 관리

시뮬레이션에서 사용하는 3D 모델, 텍스처, 재질, 파티클 시스템 등은 Omniverse Nucleus 서버나 로컬 디렉터리에서 관리한다. Python 스크립트로 USD 파일을 임포트(import)하여 스테이지에 배치하거나, 필요 없는 오브젝트를 삭제하거나, 동적으로 모델 버전을 교체할 수 있다. 이를 통해 시뮬레이션 자산을 자동으로 로드·언로드하여 시나리오를 스크립팅할 수 있다.

아래는 외부 USD 파일을 스테이지에 불러오는 코드 예시다.

```python
import omni.usd
from pxr import Sdf

stage = omni.usd.get_context().get_stage()
reference_prim_path = "/World/RobotReference"
robot_usd_path = "omniverse://my_nucleus_server/Assets/Robots/MyRobot.usd"

stage.DefinePrim(reference_prim_path, "Xform")
prim = stage.GetPrimAtPath(reference_prim_path)
prim.GetReferences().AddReference(robot_usd_path)
```

이처럼 참고(Reference) 방식으로 USD 파일을 불러오면, 원본 모델이 갱신될 때 자동으로 최신 버전을 반영하는 장점이 있다. Isaac Sim에서 여러 개발자가 협업할 때는, Omniverse Nucleus 서버를 통해 이와 같은 방식의 자산 공유가 편리하다.

#### Isaac Sim에서 텔레옵(Teleoperation) 구축

실제 조이스틱, 키보드, 마우스, VR 컨트롤러 등으로 로봇을 원격 조작하는 텔레옵 기능을 Isaac Sim 스크립트로 구현할 수 있다. 예컨대 사용자의 컨트롤 입력을 받아 로봇의 속도 명령을 만들고, 이를 시뮬레이션 로봇에 적용하여 마치 게임을 플레이하듯 조종하는 방식이다. Python 스크립트에서는 carb.input API나 omni.kit.window\.viewportCamera API를 통해 입력 장치를 추적할 수 있다.

예시 스니펫:

```python
import carb.input

input_interface = carb.input.acquire_input_interface()
device_id = input_interface.find_device_by_tag("Keyboard")

def on_keyboard_event(event, *args):
    if event.type == carb.input.KeyboardEventType.KEY_PRESS:
        if event.input == carb.input.KeyboardInput.W:
            # 전진 명령
            print("로봇 전진")
        elif event.input == carb.input.KeyboardInput.S:
            # 후진 명령
            print("로봇 후진")

keyboard_sub = input_interface.subscribe_to_keyboard_events(device_id, on_keyboard_event)
```

위 코드를 통해 W, S 키를 누르면 특정 동작을 실행하도록 만들 수 있다. 실제 로봇에 대응하듯 조인트 제어 또는 휠 속도 제어를 연결해두면, Isaac Sim에서 로봇 텔레옵 시뮬레이션을 체험 가능하다.

#### 확장된 센서 모델(초음파, IR, EMI 등)

기본적으로 Isaac Sim은 LiDAR, RGB 카메라, 깊이 카메라, IMU, Force/Torque 센서 등 대표적인 센서 모델을 제공한다. 하지만 초음파 센서, 적외선(IR) 센서, 전자기파(EMI) 기반 센서 등은 기본 제공되지 않을 수도 있다. 이 경우 Python 스크립트와 물리 엔진의 충돌 검출 결과를 조합해 직접 센서 모델을 구현할 수 있다. 예를 들어 초음파 센서를 단순화하여, 센서에서 특정 각도 범위 내의 거리 측정만 추정하도록 스크립트를 작성할 수도 있다.

이런 사용자 정의 센서 모델을 만드는 전형적인 방식은 다음과 같다.

* 센서 발사 빔(레이)을 가상으로 시뮬레이션(Physics Raycast, 회전 루프 등)
* 충돌 또는 최근접 거리 계산
* 센서 잡음(Noise) 모델 적용
* ROS2 토픽 또는 내부 변수로 결과 출력

Python에서 이런 로직을 돌려 초음파 거리 값을 생성하고, 이를 실제 로봇 제어 알고리즘에 전달하면, Isaac Sim 환경에서 초음파 센서가 있는 로봇을 대략적으로 재현할 수 있다.

#### Isaac Sim 버전 호환성 주의

Isaac Sim은 Omniverse Kit 버전, PhysX 버전, ROS2 버전 등에 따라 API나 기능이 일부 달라진다. 따라서 Python 스크립트가 특정 Isaac Sim 릴리스에 종속된 함수를 호출한다면, 업데이트된 버전에서는 호환이 깨질 수 있다. NVIDIA 측 문서나 릴리스 노트를 항상 확인하여, 자주 변경되는 함수명이나 모듈 구조를 파악해야 한다. 예를 들어 특정 버전에서 `omni.physx.acquire_physics_interface()`가 `omni.physx.get_physx_interface()`로 바뀔 수 있으며, 이벤트 이름이나 속성 이름이 변경되기도 한다.

실무에서는 Isaac Sim 버전을 고정해두고 개발하는 사례가 많으며, 새 버전으로 옮길 때는 Python 스크립트 전체를 점검해가며 마이그레이션을 진행한다. ROS2 Humble이나 이후 버전에 맞춰 Isaac Sim이 업데이트될 때도 이런 점을 염두에 두어야 한다.

#### Isaac Sim Scripting과 협업 워크플로

팀 단위 협업 시, Isaac Sim의 USD 기반 장면과 Python 스크립트를 분리 관리한다. 아티스트나 3D 디자이너는 Omniverse Create 혹은 DCC 툴(Blender, Maya 등)에서 3D 에셋을 제작하고 USD로 내보낸다. 로보틱스 엔지니어나 AI 개발자는 Python 스크립트를 작성하여 시뮬레이션 논리를 구현한다. 둘을 Omniverse Nucleus 서버로 연결해두면, USD와 Python 파일이 실시간으로 동기화되어 원격 협업이 가능하다.

이때 Git 등 버전 관리 시스템과 Omniverse Nucleus를 병행하여, 코드(스크립트)는 Git에서 관리하고 3D 모델 리소스는 Nucleus에서 관리하는 방식이 자주 쓰인다. Isaac Sim 내 Script Editor 설정을 Git 저장소 디렉터리로 맞춰두면, 브랜치를 변경할 때마다 스크립트 버전이 바뀌므로 손쉽게 작업 이력을 추적할 수 있다.

#### Isaac Sim 추가 학습 리소스

NVIDIA에서 제공하는 Isaac Sim 샘플(Example) 코드, 개발자 블로그, 포럼, GitHub 리포지토리가 다수 존재한다. Python 스크립트로 구현된 예제들을 분석해보면, 스테이지 구성, 확장 제작, 물리 제어, ROS2 통합 등 실제 사용 사례를 바로 확인할 수 있다. 또한 Isaac Sim 배포본에 포함된 예제 확장(omni.isaac.examples.\*)을 살펴보면 좋은 참고가 된다. 예를 들어 omni.isaac.examples.ros2나 omni.isaac.examples.robots 등 디렉터리를 살펴보면, 대표 로봇을 시뮬레이션하는 코드가 잘 정리되어 있다.

#### Isaac Sim과 ROS2 통합 스크립팅 개요

ROS2 Humble과 Isaac Sim을 결합하면 실제 로봇 시스템의 ROS2 메시지를 Isaac Sim의 시뮬레이션으로 접목할 수 있다. Python 스크립트에서 ROS2 노드를 직접 실행하거나, Isaac Sim이 제공하는 ROS2 Extension을 통해 메시지를 퍼블리시·구독하는 형식으로 동작한다. 이를 통해 실제 로봇 소프트웨어 스택과 동일한 코드를 시뮬레이션 환경에서 검증할 수 있다.

ROS2 노드 기반으로 Isaac Sim을 구동하려면 `rclpy` 라이브러리를 사용한다. 다음 단계로 정리하면 Isaac Sim 내부 확장(Extension)이나 Script Editor에서 ROS2 노드를 초기화하고, 메시지 통신을 수행하는 구조가 된다. 예를 들어 Isaac Sim 스크립트에서 `rclpy.init` 호출 후 노드를 만들고, 수신 콜백을 등록해 로봇 상태를 업데이트하도록 짤 수 있다.

ROS2 통합은 Isaac Sim 개발 환경과 동일한 Python 인터프리터를 사용해야 하므로, 종종 ROS2 workspace 내에서 Isaac Sim용 가상환경(venv)을 구성하거나, Isaac Sim 내에 ROS2 Humble이 위치한 경로를 알맞게 설정하는 과정을 거친다.

아래는 Isaac Sim 확장에서 ROS2 노드를 구동하는 기초 예시다.

```python
import omni.ext
import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MyRos2Node(Node):
    def __init__(self):
        super().__init__("my_ros2_node")
        self.subscription = self.create_subscription(
            String,
            "my_topic",
            self.listener_callback,
            10
        )
    
    def listener_callback(self, msg):
        print(f"ROS2 메시지 수신: {msg.data}")

class MyRos2Extension(omni.ext.IExt):
    def on_startup(self, ext_id):
        rclpy.init()
        self.ros_node = MyRos2Node()
        self.ros_spin = True
        
        # Isaac Sim의 메인 스레드에서 주기적으로 rclpy.spin_once를 호출하기 위한 subscription
        import omni.kit.app
        self._update_sub = omni.kit.app.get_app().get_update_event_stream().create_subscription_to_pop(self._on_update, name="my_ros2_update")

    def on_shutdown(self):
        self.ros_spin = False
        rclpy.shutdown()
        if self._update_sub:
            self._update_sub = None

    def _on_update(self, dt):
        if self.ros_spin:
            rclpy.spin_once(self.ros_node, timeout_sec=0.001)
```

이 확장을 활성화하면 Isaac Sim 환경에서 `my_ros2_node`라는 ROS2 노드가 만들어지고, `my_topic`을 구독해 들어오는 문자열 메시지를 콘솔에 출력한다. Isaac Sim 밖에서 ros2 pub 명령으로 메시지를 퍼블리시하면, 위 확장에서 수신 사실을 확인할 수 있다. 실제 로봇 하드웨어 드라이버 대신 Isaac Sim 시뮬레이션이 그 역할을 대체한다면, ROS2 애플리케이션 단에서도 Isaac Sim을 실제 로봇처럼 다룰 수 있다.

#### TF(Transform) 브로드캐스팅 및 Subscribe

ROS2에서 로봇 관련 좌표계를 다루기 위해서는 TF2를 사용한다. Isaac Sim 시뮬레이션 로봇의 조인트 상태나 월드 좌표를 TF 트리로 송신하고, 외부에서 TF를 수신해 시각화하거나 사용 가능하다. Python 스크립트로 로봇 각 조인트 위치를 읽어와 TF2 메시지로 변환하고, ROS2 토픽으로 브로드캐스팅하는 로직을 구성할 수 있다. 반대로 외부에서 퍼블리시된 TF를 받아 시뮬레이션 내 객체를 움직이는 식의 연동도 가능하다.

예를 들어 Isaac Sim에서 로봇 베이스(Base)와 툴(End-Effector) 간 변환 행렬을 계산해 TF로 보내려면, 우선 USD Stage에서 해당 Prim의 세계 변환 매트릭스를 Python 코드로 읽는다.

```python
from pxr import UsdGeom

robot_path = "/World/Robot/BaseLink"
stage = omni.usd.get_context().get_stage()
base_prim = stage.GetPrimAtPath(robot_path)
xform = UsdGeom.Xformable(base_prim)
mat = xform.ComputeLocalToWorldTransform(0.0)  # TimeCode=0

# mat을 ROS2 Pose 데이터로 변환
# TF 브로드캐스트
```

ROS2에서 변환 행렬을 Pose나 Transform으로 변환할 때는 `rclpy` 및 geometry\_msgs.msg.TransformStamped 등을 사용한다.

#### Isaac Sim + ROS2 서비스/액션 연동

단순 퍼블리시·구독 외에도 ROS2 서비스나 액션(Action)을 Isaac Sim과 연계할 수 있다. 예컨대 “로봇팔을 특정 각도로 움직여 주세요”라는 명령을 액션 서버에 보내고, Isaac Sim이 액션 서버 역할을 맡아 로봇팔을 움직인 뒤 결과를 돌려주는 식이다. Python 스크립트로는 `rclpy`의 ActionServer, ServiceServer를 정의하고, Isaac Sim Stage 제어 로직을 연결하면 된다.

ROS2 액션 예시는 아래와 비슷한 구조로 작성할 수 있다(단순화 예시).

```python
import rclpy
from rclpy.action import ActionServer
from control_msgs.action import FollowJointTrajectory

class RobotArmActionServer(Node):
    def __init__(self):
        super().__init__('robot_arm_action_server')
        self._action_server = ActionServer(
            self,
            FollowJointTrajectory,
            'follow_joint_trajectory',
            self.execute_callback
        )
    
    def execute_callback(self, goal_handle):
        trajectory = goal_handle.request.trajectory
        # Isaac Sim 로봇 조인트에 trajectory를 적용하여 움직임 시뮬레이션
        # ...
        goal_handle.succeed()
        return FollowJointTrajectory.Result()

def main():
    rclpy.init()
    node = RobotArmActionServer()
    rclpy.spin(node)

if __name__ == "__main__":
    main()
```

위와 같은 구조를 Isaac Sim 확장이나 Script Editor 내에서 구동하면, 외부 ROS2 클라이언트가 /follow\_joint\_trajectory 액션을 호출해 Isaac Sim 로봇을 원격으로 제어할 수 있다.

#### Isaac Sim 시나리오 자동화와 ROS2

ROS2를 통해 시뮬레이션 시나리오를 직접 관리하는 경우가 많다. 예컨대 ROS2 Launch 파일에서 Isaac Sim을 실행하고, 로봇 상태를 초기화하는 서비스 호출, 특정 시간마다 카메라 이미지를 저장하거나 로봇 포즈를 바꾸는 명령 등을 발행한다. Isaac Sim 측은 Python 스크립트로 ROS2 명령을 수신해 시나리오 단계별 로직을 수행한다. 이때 Launch 시스템을 통해 여러 노드(로봇 제어, 센서 처리, Isaac Sim)를 한 번에 띄우고, 서로 통신하도록 구성할 수 있다.

#### 멀티 머신, 멀티 로봇, 멀티 노드

Isaac Sim과 ROS2를 조합할 때, 여러 로봇을 동시에 시뮬레이션하거나 여러 기계(Host)에서 노드를 구동하는 상황이 생긴다. 분산 시뮬레이션 구조를 짜려면 ROS2 네트워크(Discovery) 설정을 잘 해줘야 하며, Isaac Sim 확장 측에서도 복수 개 노드를 띄워 각각 다른 로봇 토픽을 담당할 수 있다.

예를 들어 로봇 A, B, C를 시뮬레이션할 때, Isaac Sim에서 ROS2 노드를 3개 생성하고 각각 /robotA/cmd\_vel, /robotB/cmd\_vel, /robotC/cmd\_vel을 구독하면, ROS2 측에서 분리된 토픽으로 제어 가능해진다. 또는 하나의 Isaac Sim 노드가 여러 로봇 토픽을 동시에 처리해도 된다. 이러한 구조 설계는 프로젝트 규모와 아키텍처 요구사항에 따라 달라진다.

#### Isaac Sim GUI에서 ROS2 상태 모니터링

Isaac Sim은 UI 패널을 커스터마이징할 수 있으므로, ROS2 통신 상태를 모니터링하는 전용 패널을 만들 수도 있다. 예를 들어 “현재 구독 중인 토픽 목록”, “최근 수신된 메시지”, “ROS2 네임스페이스 설정” 등을 표시하는 패널을 Python으로 작성해두면, Isaac Sim을 사용하는 사람이 GUI 상에서 손쉽게 ROS2 상태를 확인할 수 있다. 이와 관련해 Isaac Sim 예제(omni.isaac.ros2\_bridge 등)를 참고하면 실제 코드를 살펴볼 수 있다.

#### Isaac Sim ROS2 Bridge Extension

NVIDIA가 제공하는 Isaac Sim ROS2 Bridge Extension은 ROS2용 메시지 타입을 자동으로 USD Prim이나 시뮬레이션 데이터와 매핑해주는 편의 기능을 포함한다. 예를 들어 조인트(Joint) 상태를 sensor\_msgs/JointState 메시지로 퍼블리시하거나, geometry\_msgs/Twist를 구독해 로봇 이동을 제어하는 기능 등이 이미 구현되어 있다. 이 Extension을 활성화하면, Python 코드를 최소화해도 기본적인 ROS2 연동 시뮬레이션을 빠르게 띄울 수 있다.

추가적으로 Python 스크립트에서 Bridge Extension의 기능을 호출해 토픽을 등록·제거하거나, 특정 Prim에 대한 ROS2 메시지 맵핑을 설정하는 등 세부 동작을 제어할 수 있다. 단, Isaac Sim 버전마다 이 Bridge Extension이 업데이트되므로, 실제 프로젝트에 적용하려면 버전에 맞는 공식 문서를 반드시 확인해야 한다.

#### Isaac Sim과 TurtleBot3 시뮬레이션 예시

TurtleBot3는 ROS2 교육용으로 자주 사용되는 모바일 로봇 플랫폼이다. Isaac Sim에서도 TurtleBot3 모델 USD를 불러와, Python 스크립트 및 ROS2를 통해 SLAM, 내비게이션 테스트 시나리오를 구성할 수 있다. 예를 들어 Isaac Sim에서 TurtleBot3를 스폰하고, ROS2 Navigation2 스택을 구동해 /cmd\_vel, /map, /scan 토픽 등을 교환하며 자율 주행을 시뮬레이션한다. Python 스크립트 측에서는 레이저 센서(2D LiDAR) 데이터를 시뮬레이션하고, 오도메트리(odometry)를 계산해 nav\_msgs/Odometry로 퍼블리시하면 된다.

아래 예시는 Isaac Sim이 제공하는 레이저 센서 확장과 ROS2 Bridge를 결합한 전형적인 흐름이다.

```python
# 가상의 TurtleBot3에 2D LiDAR 구성
from pxr import UsdGeom
robot_path = "/World/TurtleBot3"
lidar_path = robot_path + "/Lidar"
# 레이저 센서 Prim 생성 후 ROS2 Bridge에서 /scan 토픽으로 퍼블리시
# ...
```

TurtleBot3 SLAM 또는 Nav2 launch 파일을 동작시키면, Isaac Sim에서 발행하는 /scan을 받아 지도를 생성하고, /cmd\_vel로 로봇 이동 명령을 보낸다. Isaac Sim Python 스크립트에서 이 명령을 수신해 물리엔진 상에서 로봇이 이동하게 만든다. 결과적으로 실제 TurtleBot3가 움직이는 것과 유사한 워크플로를 가상 환경에서 재현할 수 있다.

#### Isaac Sim–ROS2 협업의 이점

Isaac Sim과 ROS2를 결합하면, 실제 로봇 소프트웨어를 크게 고치지 않고도 시뮬레이션에서 테스트·디버깅이 가능하다는 장점이 생긴다. 하드웨어 비용이나 위험 없이 복잡한 시나리오(유독 환경, 높은 곳 등)에서 로봇을 검증할 수 있다. 또한 Domain Randomization, 자동화 스크립팅, 대량 데이터셋 생성 등 Isaac Sim 고유 기능을 활용해, 실제 현장에서는 어려운 테스트 환경을 빠르게 구축해볼 수도 있다.

ROS2 Extension과 Python 스크립트를 잘 다루면, Isaac Sim이 강력한 로보틱스 개발 플랫폼으로 진화한다. 이는 엔드 투 엔드(End-to-End) 로보틱스 솔루션을 제작하려는 팀에 있어 매우 중요한 요소다. 실제 로봇 플랫폼과 동일한 노드·토픽·액션·서비스 구조를 Isaac Sim에 그대로 적용해, 시뮬레이션과 실제 동작을 통합 관리할 수 있기 때문이다.

#### 추가 참고사항

Isaac Sim과 ROS2의 세부 연동 방식은 버전 및 사용 환경에 따라 다소 차이가 존재한다. ROS2 Galactic, Humble, Iron 등 배포판마다 Python 패키지 구조나 메시지 타입이 달라질 수 있고, Isaac Sim도 해당 ROS2 버전에 맞춰 업데이트된다. NVIDIA에서 제공하는 ROS2 Extension 예제 코드를 기준으로 학습하고, Python 인터페이스를 적절히 확장하면 대부분의 로봇 시뮬레이션 요구사항을 커버할 수 있다.

#### URDF 임포트와 스크립트 제어

Isaac Sim은 로봇 모델을 USD 포맷으로 직접 작성하는 것 외에도, 기존 ROS/ROS2 생태계에서 널리 사용되는 URDF나 Collada, SDF 같은 포맷을 임포트해 변환할 수 있다. Python 스크립트로 URDF 파일을 읽고 자동 변환하는 파이프라인을 구현하면, 보유 중인 로봇 모델 자산을 Isaac Sim으로 쉽게 옮길 수 있다. URDF 임포트 후에는 USD Stage 상에서 UsdPhysics, UsdGeom, 재질(Material) 정보를 조정하고, Python 로직을 통해 물리 시뮬레이션 파라미터를 세밀하게 셋업할 수 있다.

URDF에서 조인트(Joint) 정보가 어떻게 USD의 Prim 구조와 연결되는지 이해하려면, 변환 규칙에 유의해야 한다. 일반적으로 URDF의 link와 joint가 USD 상에서는 각각 Xform Prim과 Joint Prim(혹은 Rigid Body API)로 나타난다. URDF에서 정의한 Inertial 정보, Collision geometry, Visual geometry는 Isaac Sim에서 pxr.UsdPhysics, pxr.UsdGeom 모듈을 통해 관리된다. Python 스크립트는 변환 과정을 후처리(post-processing)하여 조인트 한계, 마찰, 댐핑 등의 물리 속성을 다시 설정할 수도 있다.

#### Kinematic 솔버와 역운동학(IK)

Isaac Sim에서는 로봇 조인트의 역운동학(Inverse Kinematics)을 직접 계산해주는 별도 엔진을 제공하지 않을 수도 있다. 대신 Python으로 IK 알고리즘(예: Jacobian 기반, Cyclic Coordinate Descent(CCD), FABRIK 등)을 구현하거나, 외부 라이브러리(PyKDL, MoveIt2 IKFast 등)를 연동해 사용할 수 있다. 시뮬레이션 중에 실시간으로 IK를 계산해 조인트 값을 Isaac Sim에 적용하려면, 다음과 같은 흐름이 필요하다.

* 현재 로봇 상태(조인트 각도 $\mathbf{q}*t$)와 목표 end-effector 위치·자세 $\mathbf{x}*\text{goal}$ 계산
* IK 알고리즘으로 $\mathbf{q}\_{t+1}$ 추정
* Python 스크립트로 USD Stage 상의 로봇 조인트 Prim을 업데이트
* Isaac Sim 물리 스텝(또는 kinematic 업데이트) 수행

역동역학(Inverse Dynamics)까지 필요한 경우라면, PhysX 엔진이나 외부 동역학 라이브러리와 상호작용해 토크(Force) 기반 제어를 시도할 수 있다. 단, Isaac Sim에 토크 제어를 적용하려면 물리 API가 허용하는 방식으로 조인트에 힘이나 모멘트를 입력해야 하며, 시뮬레이션 안정성을 위해서는 적절한 시뮬레이션 시간 스텝과 제어 루프를 맞춰야 한다.

#### 협동 로봇(Co-bot) 시나리오 스크립팅

Isaac Sim Python 스크립팅으로 협동 로봇 시나리오를 구성할 수도 있다. 두 개 이상의 로봇팔이 하나의 작업을 함께 처리한다거나, 사람이 조작하는 End-Effector의 위치를 Isaac Sim에서 추적해 로봇과 상호작용하는 모델을 만들 수 있다. 예컨대 인간 팔 모션 캡처 데이터를 UDP로 받아 Isaac Sim에 반영하고, 로봇팔이 그 움직임에 협응(Cooperative Control)하도록 프로그래밍이 가능하다.

이때 로봇팔 A, B의 작업 영역이 중첩될 수 있으므로, Isaac Sim 물리 충돌을 통해 위치 간섭을 감지하거나, Python 코드에서 주기적으로 각 조인트·End-Effector 간 거리를 계산해 충돌 임박 여부를 판단할 수 있다. 안전 제어나 즉각적인 경로 재계획이 필요하다면, ROS2 액션이나 Task Graph 노드로 구현해 둘 수 있다.

#### Isaac Sim과 Isaac Gym의 차이

NVIDIA Isaac Gym은 대규모 병렬 RL 환경에 특화된 시뮬레이터이고, Isaac Sim은 더 정밀하고 시각적으로 사실적인 물리 시뮬레이션 환경을 제공한다. Python 스크립팅 관점에서는 둘 다 유사해 보이지만, Isaac Gym은 주로 GPU 가속 대규모 병렬 시뮬레이션에 초점을 맞추고 있고 Isaac Sim은 실감형 렌더링, 복합 물리 시나리오, ROS2 연동 등을 강화해 로보틱스 및 디지털 트윈 시뮬레이션에 적합하다.

Isaac Gym 코드 일부를 Isaac Sim으로 그대로 가져오려 하면, API 차이로 인해 호환성이 깨질 수 있다. 단, Isaac Sim에서도 Task Graph나 Python 병렬 처리를 통해 RL 시뮬레이션을 구성 가능하므로, 정밀한 그래픽스나 UI를 필요로 하는 RL 프로젝트라면 Isaac Sim 쪽을 선택하기도 한다.

#### Material Graph와 셰이더(Shader) 스크립팅

Isaac Sim은 Omniverse Create나 Omniverse RTX Renderer 기술 위에서 동작하기 때문에, Material Graph나 MDL(Material Definition Language)을 통해 고급 셰이더를 활용할 수 있다. Python 스크립트로 해당 재질 노드에 접근해 파라미터를 변경하면, 실시간으로 뷰포트에 반영된다. 이는 단순 색상 변경이 아닌, 노이즈 텍스처, 노멀 맵, PBR 파라미터 등을 동적으로 수정하여 매우 사실적인 시뮬레이션 환경을 구성하는 데 사용된다.

아래 예시는 MDL 기반 재질(omniPBR)을 스크립팅으로 제어하는 예시 중 일부다.

```python
from pxr import UsdShade

material_prim_path = "/World/Materials/MyPBRMaterial"
material_prim = stage.GetPrimAtPath(material_prim_path)
material = UsdShade.Material(material_prim)
diffuse_color_input = material.GetInput("diffuse_color")
if diffuse_color_input:
    diffuse_color_input.Set((0.2, 0.5, 0.8))  # RGB
```

이렇게 재질 파라미터를 수시로 바꿔주면, Domain Randomization이나 시각 효과를 위한 애니메이션 등에 응용 가능하다.

#### 복합 충돌체(Composite Collider)와 Physics Articulation

복잡한 로봇 모델이나 차량 섀시에는 여러 개의 충돌체(Collider)가 모여서 물리적 형태를 구성한다. Isaac Sim USD Stage에서 각각의 링크에 하나 이상의 Collider를 설정해, 실제 기하학과 최대한 유사한 충돌 모델을 만들 수 있다. Python 스크립트로 Collider를 자동 생성·할당하고, 조인트(Articulation) 구조까지 통합적으로 설정하는 과정을 거치면, 매우 정교한 물리 시뮬레이션이 가능해진다.

Physics Articulation은 PhysX 엔진에서 멀티 조인트 체인을 안정적으로 처리하기 위한 기능이다. Isaac Sim에서 로봇의 각 링크를 Articulation에 속한 Rigid Body로 구성하고, 연결부에 Joint를 지정하면, 물리 시뮬레이션 시 더욱 정확하고 효율적인 계산이 이루어진다. Python 스크립트 예시는 다음과 같이 전개할 수 있다.

```python
from pxr import UsdPhysics

base_link_prim_path = "/World/Robot/BaseLink"
UsdPhysics.ArticulationRootAPI.Apply(stage.GetPrimAtPath(base_link_prim_path))
```

Articulation이 적용된 Link와 Joint는 Isaac Sim 내에서 하나의 유기적 멀티 조인트 시스템으로 취급되므로, 물리 엔진이 서로 다른 로봇 및 환경 객체와 충돌할 때도 안정적으로 동작한다.

#### 이벤트와 상태 머신(State Machine) 설계

Isaac Sim 스크립트가 커지면, 단순 if-else 로직만으로는 복합 시나리오를 관리하기 힘들어진다. 이럴 때는 상태 머신(State Machine) 또는 비헤이비어 트리(Behavior Tree) 개념을 도입해, 로봇이나 시뮬레이션 환경의 여러 상태를 모듈화하고 전이(transition) 조건을 명확히 정의할 수 있다. Python에서 상태 머신을 구현해도 되고, Task Graph 등 시각적 노드 시스템을 확장해도 된다.

예컨대 로봇이 “대기 -> 이동 -> 작업 -> 복귀” 같은 단계를 순차적으로 거치는 시나리오라면, 현재 상태에 따라 다른 이벤트 핸들러를 활성화하거나, 물리 충돌·센서 입력·사용자 입력 등에 따라 다음 상태로 전이하는 구조를 만든다. 이는 Isaac Sim에서 자동화 테스트 스크립트를 작성할 때 유용하다.

#### Python 모듈 임포트와 외부 라이브러리 사용

Isaac Sim은 기본적으로 Conda 환경이나 별도 파이썬 배포 환경에 포함되어 배포되며, 추가 라이브러리를 설치하기 위해서는 pip 또는 conda 명령을 통해 Isaac Sim의 Python 해석기에 인스톨할 수 있다. 스크립트에서 NumPy, SciPy, PyTorch, TensorFlow 등 다양한 외부 라이브러리를 임포트할 수 있으며, Isaac Sim 자체 API와 결합해 폭넓은 응용이 가능하다.

단, Isaac Sim이 사용하는 파이썬 버전과 호환되는 라이브러리를 설치해야 하며, GPU 연산이 필요한 라이브러리(PyTorch, TensorFlow)라면 Isaac Sim이 사용하는 CUDA 버전과 맞춰줘야 한다. 배포판에 따라 NVIDIA가 권장하는 버전이 존재하므로, 공식 문서를 참고하는 것이 좋다.

#### 협업 편집(Collaborative Editing)과 라이브 세션

Omniverse Nucleus 서버와 Isaac Sim을 결합하면, 여러 사용자가 동일한 USD 스테이지를 동시에 편집하고 볼 수 있는 협업 편집 기능을 쓸 수 있다. Python 스크립트로 시뮬레이션 작업을 자동화하는 도중에도, 다른 사용자가 3D 모델이나 재질을 수정하면 그 결과가 거의 실시간으로 동기화된다. 이는 팀원 간 공동 개발 시, Isaac Sim 스크립트 작성자와 3D 아티스트가 즉각적인 피드백을 교환하는 데에 큰 이점을 준다.

다만 라이브 세션(Live Session) 중 대규모 스크립트 수행이 잦으면 네트워크 트래픽이 증가하여 성능이 떨어질 수 있다. 따라서 자주 변경될 가능성이 큰 스테이지 요소는 적절히 레이어 분리하거나, 임시로 라이브 모드를 끄고 배치 작업을 한 뒤 다시 라이브 편집을 활성화하는 방식을 권장한다.

#### Audio2Face나 Machinima와의 연동

Isaac Sim은 로보틱스에 초점을 맞추고 있지만, Omniverse 생태계 안에는 Audio2Face, Machinima, Animation 등 다양한 AI 기반 그래픽·애니메이션 툴이 존재한다. Python 스크립트로 Isaac Sim에서 생성한 캐릭터에 Audio2Face를 적용하면, 음성에 맞춰 얼굴 애니메이션을 만들 수도 있다. 로봇 시뮬레이션과 캐릭터 애니메이션이 공존하는 가상 환경을 만들고, 이를 Machinima 기능으로 캡처해 영상을 제작하는 흐름도 가능하다.

이는 로봇과 사람의 상호작용 시나리오(예: 소셜 로봇)를 시각적으로 풍부하게 표현하고, 프레젠테이션이나 교육 자료로 사용하기에 유익하다. Python 스크립팅으로 Audio2Face의 파라미터(표정, 입모양 등)를 조작해, 특정 시나리오에서 사람 캐릭터가 어떻게 말하고 반응하는지까지 구현 가능하다.

#### 실시간 네트워크 멀티플레이

여러 명이 각자 Isaac Sim 클라이언트를 실행하고, 동일한 가상 공간에서 로봇을 조작하거나 시뮬레이션을 관찰하는 멀티플레이 구조를 만들 수도 있다. Python 스크립트로 사용자 입력이나 로봇 상태를 네트워크로 동기화하며, Omniverse Nucleus 또는 WebSocket 등을 통해 데이터를 교환한다. 예컨대 한 사용자가 로봇을 제어하고, 다른 사용자는 센서 데이터 모니터링 역할을 맡아 협업 테스트를 진행할 수 있다.

이렇듯 Isaac Sim은 단순 데스크톱 환경을 넘어, 클라우드나 멀티 노드 네트워크 환경에서도 확장이 가능하도록 설계되었다. Python 스크립트는 그 확장의 핵심 수단이며, 네트워크 이벤트를 처리해 오브젝트 위치·상태를 업데이트하는 로직을 구현하면 즉시 동시 협업 기능을 지원한다.

#### 최종 정리

여기까지 Isaac Sim Python 스크립팅 기초부터 고급 기능, 그리고 ROS2나 Omniverse 환경과의 연계 방법을 다양하게 살펴보았다. Isaac Sim은 강력하고 유연한 Python API를 제공하므로, 기본적인 객체 생성·제어부터 물리 엔진 및 이벤트 모델 고급 활용, ROS2 통합, AI 모델 접목, 대규모 자동화 시나리오 등 폭넓은 응용이 가능하다. 프로젝트 규모가 커질수록 확장(Extension) 방식으로 구조화하고, 이벤트·스레딩·Task Graph 등을 적절히 조합해 유지보수성과 성능을 모두 확보하는 방식이 중요하다.
