Study
Isaac · Lab

[Isaac Lab Part 2] Unitree Go2 보행 환경 구성하기

InteractiveSceneCfg와 SimulationContext로 Unitree Go2, terrain, height scanner를 포함한 scene을 구성한다.

unitree-go2isaac-labisaac-siminteractive-scenerobotics

이번 단계에서는 Unitree Go2를 단순히 불러오는 것에서 더 나아가, Isaac Lab의 InteractiveSceneCfgSimulationContext로 보행 학습에 사용할 scene을 구성했다.

1. Interactive Scene 구성

InteractiveSceneCfg는 지형, 로봇, 센서, 조명처럼 시뮬레이션 안에 놓일 요소를 한곳에서 선언한다. 여기서는 빈 지면 위에 Go2를 배치하고, 보행 학습에서 지형 높이 정보를 읽을 수 있도록 height scanner를 붙였다.

  • Terrain: prim path와 type을 지정해 기본 지면을 만든다.
  • Robot: Isaac Lab asset config에서 Unitree Go2 설정을 가져와 관절과 물리 파라미터를 구성한다.
  • Sensor: base 기준으로 ground mesh까지의 높이를 읽는 height scanner를 추가한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.terrains import TerrainImporterCfg
from isaaclab.sensors.ray_caster import RayCasterCfg
from isaaclab.sensors.ray_caster.patterns import GridPatternCfg
from isaaclab.sensors.ray_caster import patterns
from isaaclab.utils import configclass
from isaaclab_assets.robots.unitree import UNITREE_GO2_CFG  # isort:skip
from isaaclab.assets import ArticulationCfg, AssetBaseCfg
import isaaclab.sim as sim_utils

@configclass
class Myscene(InteractiveSceneCfg):
    # 지형 정의
    terrain = TerrainImporterCfg(
        prim_path = "/World/ground",
        terrain_type = "plane",
    )

    # 로봇 정의
    go2: ArticulationCfg = UNITREE_GO2_CFG.replace(prim_path="{ENV_REGEX_NS}/Go2")

    # 센서 정의
    height_scanner = RayCasterCfg(
        prim_path = "{ENV_REGEX_NS}/Go2/base",
        update_period = 0.02,
        offset=RayCasterCfg.OffsetCfg(pos=(0.0, 0.0, 20.0)),
        ray_alignment="yaw",
        pattern_cfg=patterns.GridPatternCfg(resolution=0.1, size=[1.6, 1.0]), # pattern_cfg
        debug_vis=True,
        mesh_prim_paths=["/World/ground"],
    )

    # 조명 정의
    light = AssetBaseCfg(
        prim_path = "/World/light",
        spawn = sim_utils.DistantLightCfg(intensity=1000.0),
    )

2. SimulationContext 실행

Hydra로 config를 불러온 뒤 SimulationContext를 만들고, 위에서 정의한 scene config를 이용해 실제 scene을 생성한다. 이후 scene["go2"]처럼 이름으로 robot entity에 접근할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import os
import hydra
import rclpy
import torch
import time
import math
import argparse
from isaaclab.app import AppLauncher
# add argparse arguments
parser = argparse.ArgumentParser(description="Unitree go2 ros2 setup")

# append AppLauncher cli args
AppLauncher.add_app_launcher_args(parser)
# parse the arguments
args_cli = parser.parse_args()

# launch omniverse app
app_launcher = AppLauncher(args_cli)
simulation_app = app_launcher.app

"""Rest everything follows."""

import torch
import isaaclab.sim as sim_utils
from isaaclab.scene import InteractiveScene, InteractiveSceneCfg
from isaaclab.sim import SimulationContext

from go2.go2_env import Myscene


FILE_PATH = os.path.join(os.path.dirname(__file__), "config")
@hydra.main(config_path=FILE_PATH, config_name="sim", version_base=None)
def run_simulator(cfg):
    sim_cfg = sim_utils.SimulationCfg(device=args_cli.device)
    sim = SimulationContext(sim_cfg)
    sim.set_camera_view([2.5, 0.0, 4.0], [0.0, 0.0, 2.0])
    scene_cfg = Myscene(num_envs=cfg.num_envs, env_spacing=2.0)
    scene = InteractiveScene(scene_cfg)
    # play the simulator
    sim.reset()
    print("[INFO]: simulation started")
    sim_dt = sim.get_physics_dt()
    count = 0
    robot = scene["go2"]
    while simulation_app.is_running():
        if count % 300 == 0:
            count = 0
            root_state = robot.data.default_root_state.clone()
            root_state[:,:3] += scene.env_origins
            robot.write_root_pose_to_sim(root_state[:,:7])
            robot.write_root_velocity_to_sim(root_state[:,7:])
            joint_pos, joint_vel = robot.data.default_joint_pos.clone(), robot.data.default_joint_vel.clone()
            joint_pos += torch.rand_like(joint_pos) * 0.1
            robot.write_joint_state_to_sim(joint_pos, joint_vel)
            scene.reset()
            print("[INFO]: Resetting robot scene")

        joint_pos_target = torch.randn_like(robot.data.joint_pos) * 0.1
        # apply action to the robot
        robot.set_joint_position_target(joint_pos_target)
        # -- write data to sim
        scene.write_data_to_sim()
        # Perform step
        sim.step()
        # Increment counter
        count += 1
        # Update buffers
        scene.update(sim_dt)
    simulation_app.close()

if __name__ == "__main__":
    run_simulator()

실험에서는 일정 step마다 base pose와 joint state를 reset해 scene이 반복적으로 초기화되는지 확인했다. 이 과정에서 joint에 임의 값이 들어가 관절이 뒤틀려 보이지만, scene 구성과 reset 흐름이 정상적으로 연결된 것은 확인할 수 있었다.

랜덤 joint reset이 적용된 Go2 자세

실행 결과 미리보기

즉 Part 2의 목적은 예쁜 자세를 만드는 것이 아니라, 이후 RL 환경에서 사용할 scene 구성 요소가 Isaac Lab 안에서 실제 entity로 생성되는지 검증하는 데 있다.