카테고리 없음

우분투 22.04LTS에서 IsaacSim 6.0.0 Ros2 Humble 사용하기

Wood Pecker 2026. 6. 25. 15:23

1. 개요

    우분투 운영체제 환경  isaacsim 6.0.0에서 ros2를 사용하여 로봇 암(ur10e gripper)을 제어 하고자 한다. 


2. 제어 UI 프로그램 (terminal #1)

    첫번째 터미널에서 다음과 같이 입력하고 아래 코드를 입력한다.
    >  source  /opt/ros/humble/setup.bash

    >  python  external_ui_controller.py

# external_ui_controller.py
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import JointState
import tkinter as tk

class RobotUIController(Node):
    def __init__(self):
        super().__init__('robot_ui_controller')
        # /joint_commands 라는 토픽으로 JointState 메시지를 발행
        self.publisher_ = self.create_publisher(JointState, '/joint_commands', 10)

        self.dof_names = [
            'shoulder_pan_joint', 'shoulder_lift_joint', 'elbow_joint',
            'wrist_1_joint', 'wrist_2_joint', 'wrist_3_joint', 'finger_joint'
        ]
        self.num_dofs = len(self.dof_names)
        self.dof_targets = [0.0] * self.num_dofs

        self.setup_ui()

    def setup_ui(self):
        self.root = tk.Tk()
        self.root.title("ROS 2 Robot Joint Controller")
        self.root.geometry("450x350")

        self.labels = []
        for i in range(self.num_dofs):
            frame = tk.Frame(self.root)
            frame.pack(pady=5)

            # Joint Name
            tk.Label(frame, text=f"[{i}] {self.dof_names[i]}", width=25, anchor='w').pack(side=tk.LEFT)

            # Value Label
            val_lbl = tk.Label(frame, text="0.00", width=5)
            val_lbl.pack(side=tk.LEFT, padx=10)
            self.labels.append(val_lbl)

            # Buttons
            tk.Button(frame, text="Up (+0.1)", command=lambda idx=i: self.update_joint(idx, 0.1)).pack(side=tk.LEFT,
                                                                                                       padx=2)
            tk.Button(frame, text="Down (-0.1)", command=lambda idx=i: self.update_joint(idx, -0.1)).pack(side=tk.LEFT,
                                                                                                          padx=2)

    def update_joint(self, idx, delta):
        self.dof_targets[idx] += delta

        # Limit 관절 가동 범위
        if 0 <= idx <= 5:
            self.dof_targets[idx] = max(-3.14, min(3.14, self.dof_targets[idx]))
        elif idx == 6:
            self.dof_targets[idx] = max(0.0, min(0.7, self.dof_targets[idx]))

        self.dof_targets[idx] = round(self.dof_targets[idx], 2)
        self.labels[idx].config(text=f"{self.dof_targets[idx]:.2f}")

        # ROS 2 메시지 발행
        self.publish_state()

    def publish_state(self):
        msg = JointState()
        msg.name = self.dof_names
        msg.position = self.dof_targets
        self.publisher_.publish(msg)
        self.get_logger().info(f'Published commands: {self.dof_targets}')


def main(args=None):
    rclpy.init(args=args)
    controller = RobotUIController()

    # Tkinter 메인 루프와 ROS 2 스핀을 함께 처리
    while rclpy.ok():
        rclpy.spin_once(controller, timeout_sec=0.01)
        controller.root.update()

    controller.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

  

 다음과 같은 GUI환경에서 로봇을 제어하는 ROS2 메시지를 보낸다.

 

3. IsaacSim 프로그램( terminal #2)

  >  export ROS_DISTRO=humble
  >  export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
     아래 명령어는 IsaacSim의 설치 위치에 따라 LD_LIBRARY_PATH를 올바르게 설정하여야 한다. 

  >  export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/lee/isaacsim/exts/isaacsim.ros2.core/humble/lib

  > ~/isaacsim/python.sh ros2based_robot_control.py    #<-- 파이썬은  isaacsim/python.sh를 사용한다.

# ros2based_robot_control.py 
import argparse
from isaacsim import SimulationApp

parser = argparse.ArgumentParser()
parser.add_argument("--test", action="store_true")
parser.add_argument("--headless", action="store_true")
args, _ = parser.parse_known_args()

# 1. 시뮬레이션 앱 생성 (순정 상태)
simulation_app = SimulationApp({"headless": args.headless, "hide_ui": False})

# 2. 최신 버전에 맞춘 ROS 2 Bridge 익스텐션 활성화
from isaacsim.core.experimental.utils.app import enable_extension
enable_extension("isaacsim.ros2.bridge")  # <-- omni.isaac.ros2_bridge에서 변경

# 3. 익스텐션 적용 및 환경 변수 로드를 위한 시뮬레이터 틱
simulation_app.update()

# 4. 이제 정상적으로 rclpy 임포트 가능
import rclpy
from rclpy.node import Node

# 확인용 출력
print("ROS 2 Bridge 및 rclpy 로드 성공!")

from sensor_msgs.msg import JointState

# ----------------------------------------------------------------------
# Isaac Sim imports
# ----------------------------------------------------------------------
import isaacsim.core.experimental.utils.stage as stage_utils
import isaacsim.core.experimental.utils.app as app_utils  
import omni.kit.app

from isaacsim.core.experimental.objects import DomeLight, GroundPlane
from isaacsim.core.experimental.prims import Articulation
from isaacsim.core.simulation_manager import SimulationManager
from isaacsim.core.utils.viewports import set_camera_view
from isaacsim.storage.native import get_assets_root_path_async

_HOLD_STEPS: int = 120


# ----------------------------------------------------------------------
# ROS2 Subscriber
# ----------------------------------------------------------------------
class RobotJointSubscriber(Node):
    """Receive JointState commands from ROS2"""

    def __init__(self):
        super().__init__("isaac_sim_robot_subscriber")

        self.subscription = self.create_subscription(
            JointState,
            "/joint_commands",
            self.listener_callback,
            10,
        )

        self.latest_positions = None

        self.get_logger().info(
            "ROS2 Subscriber initialized. Listening to '/joint_commands'..."
        )

    def listener_callback(self, msg):
        self.latest_positions = list(msg.position)


# ----------------------------------------------------------------------
# Scene Setup
# ----------------------------------------------------------------------
async def setup_scene() -> Articulation:
    assets_root_path = await get_assets_root_path_async()

    stage_utils.add_reference_to_stage(
        usd_path=(
            assets_root_path
            + "/Isaac/Samples/Rigging/Manipulator/"
              "configure_manipulator/ur10e/ur/ur_gripper.usd"
        ),
        path="/World/ur",
    )

    GroundPlane("/World/GroundPlane")
    DomeLight("/World/DomeLight").set_intensities(1000)

    await omni.kit.app.get_app().next_update_async()

    set_camera_view(
        eye=[1.5, 1.5, 1.0],
        target=[0.0, 0.0, 0.5],
        camera_prim_path="/OmniverseKit_Persp",
    )

    robot = Articulation("/World/ur")

    await omni.kit.app.get_app().next_update_async()

    return robot


# ----------------------------------------------------------------------
# Main
# ----------------------------------------------------------------------
def main(args: argparse.Namespace, app: SimulationApp) -> None:
    SimulationManager.setup_simulation(dt=1.0 / 60.0)

    robot = app.run_coroutine(setup_scene())

    app.update()

    if args.headless:
        print(
            "Headless mode: simulation is paused. "
            "Press Play in the livestream UI to begin."
        )

        while app.is_running() and not app_utils.is_playing():
            app.update()
    else:
        app_utils.play()
        app.update()

    print(f"Robot DOFs: {robot.dof_names}")

    # ------------------------------------------------------------------
    # ROS2 Initialization
    # ------------------------------------------------------------------
    rclpy.init()

    subscriber_node = RobotJointSubscriber()

    num_dofs = 7
    dof_indices = list(range(num_dofs))

    frame_count = 0

    # ------------------------------------------------------------------
    # Main Simulation Loop
    # ------------------------------------------------------------------
    while app.is_running():
        app.update()

        # Process ROS2 callbacks
        rclpy.spin_once(
            subscriber_node,
            timeout_sec=0.0,
        )

        # Apply incoming joint positions
        if subscriber_node.latest_positions is not None:
            target_positions = subscriber_node.latest_positions[:num_dofs]

            robot.set_dof_position_targets(
                target_positions,
                dof_indices=dof_indices,
            )

        frame_count += 1

        if args.test and frame_count >= (_HOLD_STEPS * 2):
            break

    # ------------------------------------------------------------------
    # Cleanup
    # ------------------------------------------------------------------
    subscriber_node.destroy_node()

    rclpy.shutdown()


# ----------------------------------------------------------------------
# Entry Point
# ----------------------------------------------------------------------
if __name__ == "__main__":
    try:
        main(args, simulation_app)

    except KeyboardInterrupt:
        print("\nExiting...")

    except Exception:
        import traceback

        traceback.print_exc()

    finally:
        simulation_app.close()

 

Asset은  6.0.0 버전으로 다운로드를 받았으며  파일 용량이 커서 별도 외장하드에 저장하고 링크로 연결하였다.  
독립적인 GUI 프로그램에서  로봇을 제어하는 ROS2 메시지를 isaacsim에 보내고 로봇을 제어할 수 있다. 

 

반응형