OmniCraft Logo

เริ่มต้นใช้งาน Isaac Sim Core API แบบง่ายที่สุดด้วย Isaac Powerpack

11 min read isaac sim
( อัพเดทล่าสุด: )

ตอนที่ผมลองศึกษา Isaac Sim Core API ครั้งแรกจากคู่มือของ NVIDIA ผมรู้สึกมาตลอดว่ามันค่อนข้างซับซ้อนและนำไปประยุกต์ใช้งานได้ยาก ผมเลยเขียนบล็อกนี้ขึ้นมาเป็นอีกทางเลือกหนึ่ง โดยเลือกวิธีที่ผมชอบและตัวอย่างที่เรียบง่ายขึ้น สำหรับใช้เป็นฐานในการเรียนรู้ feature อื่นๆของ Isaac Sim ได้

เพื่อให้ใช้งานสะดวกขึ้นไปอีก ผมได้พัฒนา command line ชื่อ pow-cli ขึ้นมา ซึ่งเป็นส่วนหนึ่งของ opensource project ของผมเอง สามารถดูได้ที่ Isaac Powerpack GitHub เพื่อแก้ปัญหาต่างๆที่ผมเจอระหว่างการทำงานกับ Isaac Sim

โดยในบล็อกนี้เราจะใช้ pow-cli สำหรับ install, setup project และ รัน Isaac Sim เพื่อลดความยุ่งยากในการเริ่มต้นโปรเจกต์

Franka PickPlace

โจทย์ที่เราจะทำกันในวันนี้ คือการรัน script เพื่อสั่งให้แขนหุ่นยนต์ Franka ใน simulation ไปหยิบจับวัตถุด้วย PickPlaceController เราจะได้เรียนรู้ Isaac Sim Core API 5 อย่าง:

  1. World initialization
  2. สร้างวัตถุ Primitive สามมิติ
  3. Register object ด้วย Scene API
  4. Register callback เพื่อรัน function ใน simulation loop
  5. โหลด Franka Robot และใช้งาน PickPlace Controller

concept ทั้งหมดนี้จะถูกรวบอยู่ใน script ตัวอย่างแค่อันเดียว และรันผ่าน VS Code ด้วย extension ชื่อว่า Isaac Sim VS Code Edition ซึ่ง extension ที่ว่านี้จะช่วยยิง script เข้าไปยัง Isaac Sim ที่กำลังรันอยู่ในเครื่องเรา

สิ่งที่ต้องเตรียมพร้อม

Hardware

ทุกคนจะต้องมีเครื่อง computer ที่ลง Ubuntu 22.04 หรือ 24.04 ไว้เรียบร้อย มีพื้นที่ hard disk เหลือสัก 30GB และมีการ์ดจอ NVIDIA RTX 40 series เป็นต้นไป ที่ติดตั้ง driver ไว้แล้ว ส่วน spec อื่น ๆ ของ hardware เราสามารถตรวจสอบความเข้ากันได้ด้วยคำสั่ง pow sim check ของ pow-cli ซึ่งจะอธิบายวิธีใช้ในหัวข้อถัดไป

Software

ฝั่ง Software เราจะต้องลง 3 อย่างนี้ให้พร้อม

1. Docker

เครื่องมือในการรัน container อันนี้คงไม่ต้องอธิบายเยอะ ทุกคนคงรู้จักกันอยู่แล้ว
ถ้าใครที่ยังไม่ลง สามารถเข้าไปลงได้ที่ลิงก์นี้ https://docs.docker.com/engine/install/ubuntu/

2. UV

เครื่องมือที่ช่วยจัดการ python project แทบทุกอย่างแบบครบวงจร ตั้งแต่การติดตั้ง python version, จัดการ packages, virtual environment รวมถึงช่วยในการ publish python code ของเราขึ้น public registry อย่าง PyPI ด้วย (PyPI คือ server สำหรับแชร์ tool หรือ lib ที่เขียนด้วยภาษา python เพื่อให้คนอื่นสามารถ install ใช้งานด้วย pip ได้)

ใครที่ยังไม่ลง สามารถเข้าไปติดตั้ง UV ได้ตามลิงก์นี้ https://docs.astral.sh/uv/getting-started/installation/

3. VS Code + Isaac Sim VS Code Edition Extension

ลง VS Code ให้เรียบร้อย และติดตั้ง Isaac Sim VS Code Edition Extension ซึ่งทำหน้าที่เป็นตัวช่วยในการเขียนโปรแกรม Isaac Sim โดยจะมีทั้ง feature สำหรับรัน script โดยตรงจาก VS Code และ template code ต่าง ๆ

วิธีการติดตั้ง ให้กดเข้าไปที่เมนู Extensions จากแท็บด้านซ้ายมือ จากนั้นพิมพ์ชื่อค้นหาในช่องด้านบน และกด Install

Isaac Sim VS Code Edition Extension in VS Code Marketplace

เมื่อลงเสร็จแล้ว เราจะเจอเมนู Isaac Sim VS Code Edition อยู่ที่แท็บด้านซ้ายมือ

Isaac Sim VS Code Edition menu in VS Code Tab

Clone Project และ Install Isaac Sim

ทำการ clone pow-orin-starter repository ลงมาในเครื่อง โดย project นี้จะเป็น template ที่มีโครงสร้าง folder ต่าง ๆ และ config พร้อมสำหรับการเริ่มใช้งาน Isaac Sim ด้วย pow-cli

bash
git clone https://github.com/isaac-powerpack/pow-orin-starter.git

เมื่อ clone เสร็จแล้ว ให้เข้าไปรันคำสั่งด้านล่าง เพื่อสร้าง virtual environment พร้อมลง packages ที่จำเป็น รวมถึงติดตั้ง Isaac Sim และทำการ activate virtual environment

bash
UV_HTTP_TIMEOUT=300 uv sync --extra sim
source ./.venv/bin/activate

ขั้นตอน install อาจใช้เวลาประมาณ 5–10 นาที หรือเร็วกว่านั้นขึ้นอยู่กับความเร็วอินเทอร์เน็ต เมื่อลงและ activate เรียบร้อยแล้ว ให้ทำการเช็กว่า hardware ของเรามีทรัพยากรเพียงพอหรือไม่ ด้วยคำสั่งด้านล่าง

bash
# ถ้ามี prompt ขึ้นมา ให้พิมพ์ Yes เพื่อยอมรับข้อตกลงการใช้งานของ NVIDIA
pow sim check

หลังจากนั้น เราจะเห็นหน้าต่างตามรูป

pow sim check output

ถ้าทุกช่องเป็นสีเขียวและสีส้ม หมายความว่า computer ของเราสามารถรัน Isaac Sim ได้ปกติ แต่ถ้ามีช่องใดช่องหนึ่งเป็นสีแดง แสดงว่า hardware ในส่วนนั้นยังไม่รองรับ

Setup Project ด้วย Pow Cli

เมื่อเช็คแล้วว่า hardware ของเรารัน Isaac Sim ได้ ต่อไปให้เราเปิดไฟล์ pow.toml ขึ้นมา ไฟล์นี้ คือ config file ของ pow-cli มีไว้ใช้ตั้งค่าเวลาเรา initialize project และ รัน Isaac Sim app

toml
[sim]
version = "5.1.0"
# add paths to additional extension folders
ext_folders = ["./sim/exts"]

[sim.ros]
enable_ros = true
isaacsim_ros_ws = "~/.pow/IsaacSim-ros_workspaces"
# support: humble, jazzy
ros_distro = "humble"

[[sim.profiles]]
name = "default"
cpu_performance_mode = false
headless = false
extensions = ["isaacsim.code_editor.vscode", "pow.workcell.camera"]
raw_args = ["--/renderer/raytracingMotion/enabled=false"]
open_scene_path = ""

[[sim.profiles]]
name = "perf"
cpu_performance_mode = true

เนื่องจาก Tutorial นี้ ยังไม่มีการใช้งาน ROS2 เราเลยจะปิดการใช้งานมันไปก่อน ให้เราไปที่ config section [sim.ros] และทำการเซ็ตค่า enable_ros ให้เป็น false เพื่อปิดการใช้งาน ตามตัวอย่างด้านล่าง

toml
[sim.ros]
enable_ros = false
isaacsim_ros_ws = "~/.pow/IsaacSim-ros_workspaces"
# support: humble, jazzy
ros_distro = "humble"

การปิด ROS2 ทำให้เราลดเวลาตอน build Isaac Sim ROS workspace ไปเยอะเลย ทำให้กระบวนการ initialize เสร็จเร็วขึ้น หากภายหลังต้องการเปิดใช้ เราค่อยกลับมา enable ได้

ต่อไปให้รันคำสั่งด้านล่างเพื่อ initialize project ซึ่งจะทำครั้งแรกครั้งเดียว ภายหลังไม่ต้องแล้ว

bash
pow sim init

พิมพ์ y เพื่อยืนยันการ overwrite vscode settings ซึ่งตรงนี้ต้องทำทุกครั้งที่เรา clone pow-orin-starter ลงมาใหม่ เพื่อให้ code completion ทำงาน สาเหตุเนื่องจาก กระบวนการภายในของ Isaac Sim lib เอง

pow sim init output

เมื่อ initialize เสร็จ ให้รันคำสั่งต่อไป เพื่อเปิดโปรแกรม Isaac Sim

bash
pow sim run

คำสั่ง pow sim run จะเข้าไป อ่าน config ตรงส่วน default profile แล้วทำการรัน Isaac Sim ตามที่เราตั้งค่าไว้ตามนี้

toml
[[sim.profiles]]
name = "default"
cpu_performance_mode = false
headless = false
extensions = ["isaacsim.code_editor.vscode", "pow.workcell.camera"]
raw_args = ["--/renderer/raytracingMotion/enabled=false"]
open_scene_path = ""

คำอธิบาย config ต่างๆ ใน default profile อันนี้ จะอยู่ใน Appendix A ส่วนท้ายของ blog นะครับ เผื่อใครต้องการเข้าใจรายละเอียดทั้งหมด ก็สามารถเข้าไปอ่านได้

ส่วนใครที่ต้องการรัน Isaac Sim ในโหมด cpu performance ให้รันคำสั่งนี้แทน จะทำให้การทำงานโดยรวมของ Isaac Sim เร็วขึ้น

bash
pow sim run -p perf

ตัวมันจะรัน Isaac Sim จาก profile ที่ชื่อว่า perf ด้านล่าง

toml
[[sim.profiles]]
name = "perf"
cpu_performance_mode = true

การสั่งเปิดโหมด Performance จะต้องรันโปรแกรมในรูปแบบของ superuser (sudo) ทำให้เราต้องกรอกรหัส user ของเราก่อนรัน

ถ้าใครต้องการเพิ่ม profile settings เพื่อ customize ตามการใช้งานของตัวเอง ก็ทำได้ง่ายๆเลย แค่เพิ่มส่วน [[sim.profiles]] ต่อเข้าไปในไฟล์ pow.toml ซึ่งทุก profile จะทำการ extend จาก default profile และ overwrite ค่าอะไรก็ตามใน default เมื่อเราระบุ ค่า config นั้นๆลงไป

รัน Pickplace Script ด้วย Isaac Sim VSCode Extension

เปิดไฟล์ sim/examples/franka-pickplace.py ใน pow-orin-starter ขึ้นมาใน VSCode เราจะเห็น script ตามนี้

python
import asyncio

import carb
import numpy as np
from isaacsim.core.api import World
from isaacsim.core.api.objects import DynamicCuboid
from isaacsim.robot.manipulators.examples.franka import Franka
from isaacsim.robot.manipulators.examples.franka.controllers import PickPlaceController


async def run():
  # Initialize World
  world = World.instance()
  if world is None:
      carb.log_info("Creating new world instance")
      world = World(stage_units_in_meters=1.0)
      await world.initialize_simulation_context_async()
      await world.reset_async()
  else:
      # clear all existing tasks and callbacks
      carb.log_info("World exists. Reseting world")
      world.clear_all_callbacks()
      world.scene.clear()
      world.reset()
      carb.log_info("World reset done")

  # Setup scene
  world.scene.add_default_ground_plane()
  franka = Franka(prim_path="/World/Franka_01", name="franka")
  franka.initialize()
  cube = DynamicCuboid(
      prim_path="/World/random_cube",
      name="cube",
      position=np.array([0.5, 0.3, 0.15]),
      scale=np.array([0.0515, 0.0515, 0.0515]),
      color=np.array([0, 0, 1.0]),
  )
  world.scene.add(franka)
  world.scene.add(cube)

  # Run Pick & Place

  controller = PickPlaceController(
      name="pick_place_controller",
      gripper=franka.gripper,
      robot_articulation=franka,
  )

  franka.gripper.set_joint_positions(franka.gripper.joint_opened_positions)

  def physic_step(dt):
      cube_pos, _ = world.scene.get_object("cube").get_world_pose()
      goal_pos = np.array([0.5, -0.3, 0.0515 / 2.0])
      current_robot_joints = franka.get_joint_positions()
      actions = controller.forward(
          picking_position=cube_pos,
          placing_position=goal_pos,
          current_joint_positions=current_robot_joints,
      )
      franka.apply_action(actions)
      if controller.is_done():
          carb.log_info("Pick and place done")
          world.remove_physics_callback("franka_step")
          world.pause()

  world.add_physics_callback("franka_step", physic_step)
  await world.play_async()


asyncio.ensure_future(run())

เราจะเริ่มจากการรันตัวอย่างกันก่อน หลังจากนั้นผมจะอธิบาย code ของ script แบบละเอียดอีกทีในหัวข้อถัดไป

ทำการรัน script ด้วยการกดไปที่เมนู Isaac Sim VSCode Edition > Run Command

Isaac Sim VSCode Extension Run Command

ถ้าเซ็ตอัพถูกต้อง จะเห็นผลลัพธ์ตามนี้

วิธีการรันจาก VSCode แบบนี้ สามารถเอามาประยุกต์ใช้กับ script แบบอื่นๆได้ทั้งหมด ซึ่งเป็นวิธีที่สะดวกกว่าการเปิดเมนู Script Editor ใน Isaac Sim มาก แต่เดิมนั้นเราต้องทำการ copy/paste code จากใน vscode เข้าไปใน Isaac Sim เอง และทำซ้ำๆเมื่อเราแก้ code

อธิบาย Code การทำงาน

ก่อนอื่น ถ้าใครยังไม่เข้าใจ concept ของ World API ใน Isaac Sim ให้ทุกคนแวะไปอ่าน blog ที่ผมเขียนขึ้นมาก่อนหน้า อธิบาย World — API พระเจ้าใน Nvidia Isaac Sim เนื้อหาสั้นๆ อ่านไม่เกิน 2 นาทีจบ

เมื่อทุกคนเข้าใจ World API ตรงกันแล้ว เรามาเริ่มดู code กันเลย ตัวโครงสร้างของ Script นี้ ประกอบไปด้วย 4 ส่วนหลัก:

  1. run()
  2. Initialize World
  3. Setup Scene
  4. Controller Logic

Run()

เป็นจุดตั้งต้นในการเขียน script ของเรา โดยทั่วไปเราจะใช้ pattern นี้ ในการเขียน script เสมอ เราจะทำการเขียน code การทำงานใดๆก็ตาม ลงไปใน function run()

python
import asyncio

async def run():
    #...add your code here...

asyncio.ensure_future(run())

run() ถูกระบุให้เป็น Asynchronous Function สังเกตจาก async keyword ด้านหน้า ซึ่งการจะใช้งานมัน เราต้อง import lib asyncio ลงมาด้วย และครอบfunction run() ด้วย asyncio.ensure_future เพื่อเรียก function แบบ Asynchronous ทำงาน

สาเหตุที่ต้องใช้ async เนื่องจาก เวลาเรารัน script แบบนี้ จะเป็นการส่ง script ของเราเข้าไปแทรกอยู่ใน Isaac Sim Simulation Loop ที่เป็นตัวทำงานหลักของ Isaac Sim app ถ้าหาก script ของเรามี code ที่กินเวลาการทำงานมาก ตัว function อาจจะไป block การทำงานของ Simulation Loop ได้ ส่งผลให้ Isaac Sim app ค้าง

ดังนั้นการรัน function ในรูปแบบ Asynchronous จึงช่วยการันตีเบื้องต้นว่า script ของเรา จะไม่ไป block การทำงานของ simulation loop

Initialize World

เพื่อที่จะ setup ฉาก และ ใช้งาน callback ในการสั่งการแขนหุ่นยนต์ด้วย PickPlaceController เราจำเป็นต้องทำการดึง World object เข้ามาด้วยคำสั่งนี้:

python
world = World.instance()

เราสามารถเรียกใช้งาน feature ทั้งหมดของ World API ผ่านทางตัวแปร world

แต่ด้วยความที่ World ถูก implement ด้วยรูปแบบ Singleton Pattern ดังนั้นค่าที่อยู่ใน World จะถูกแชร์ใช้งานร่วมกันกับ scripts และ extensions ทั้งหมด ของ Isaac Sim App ที่รันอยู่ในปัจจุบัน

นั่นคือ มันมีโอกาสที่ code ส่วนอื่น หรือแม้กระทั่ง script เดิมของเรา ได้สร้าง World ขึ้นมาแล้ว เพราะฉะนั้น เราจึงต้องเช็คด้วยว่า world instance ในตัวแปร world ถูกสร้างขึ้นมาแล้วรึยัง “ถ้ายัง” ให้เราสร้างมันขึ้นมาใหม่ แต่ถ้ามันถูก “สร้างขึ้นมาแล้ว” ก็ให้เราทำการ reset ทุกอย่างใน world ด้วย รวมถึงเคลียร์ฉากและ callbacks ต่างๆ เพื่อแก้ปัญหาเรื่อง memory leak ในกรณีที่เรารัน script เดิมของเราซ้ำอีกครั้ง

code ของส่วน Initialize World จะมีหน้าตาตามนี้

python
async def run():
  world = World.instance()
  if world is None:
      carb.log_info("Creating new world instance")
      world = World(stage_units_in_meters=1.0)
      await world.initialize_simulation_context_async()
      await world.reset_async()
  else:
      # clear all existing tasks and callback
      carb.log_info("World exists. Reseting world")
      world.clear_all_callbacks()
      world.scene.clear()
      world.reset()
      carb.log_info("World reset done")
  
  ...

มีข้อควรระวังสักหน่อย ถ้าโปรแกรมของเรามีการเขียน Isaac Sim extension ใช้เอง หรือ มี code ส่วนอื่นที่รันอยู่ก่อนหน้า script ของเรา กระบวนการเคลียร์ฉาก,callbacks และ reset ใน script ของเรา จะทำการล้าง code ส่วนนั้นด้วยเช่นกัน

แต่ว่าในกรณี franka-pickplace.py script ของเราอันนี้ จะไม่มีปัญหาอะไร เพราะเรารันอยู่อันเดียว

Setup Scene

ต่อไปจะเป็นส่วนของการ setup ฉาก ซึ่งเราจะใช้งาน Scene API ที่ช่วยในการจัดการวัตถุในฉากของเรา สามารถเรียกใช้งานมันได้ง่ายๆ ผ่านทาง world.scene

ส่วนนี้จะเริ่มด้วยการสร้างพื้นให้กับหุ่นยนต์ขึ้นมาก่อน พร้อมกับระบุกฏทางฟิสิกส์ในฉากให้ด้วย ทั้งสองอันนี้รวมอยู่ในคำสั่งเดียวเลย

python
world.scene.add_default_ground_plane()

จากนั้นจึง load แขนหุ่นยนต์ Franka เข้ามาในฉาก ด้วยคำสั่งนี้:

python
franka = Franka(prim_path="/World/Franka_01", name="franka")
franka.initialize()

ตั้ง prim_path ไว้เป็น /World/Franka_01 และตั้งชื่อไว้ว่า franka

ใน Isaac Sim จะเรียกวัตถุ 3มิติ และ ส่วนประกอบต่างๆในฉาก ว่า prim ซึ่งเราจะบริหารจัดการมันในส่วนที่เรียกว่า Stage Panel สังเกตได้จาก เมื่อเราโหลดแขนหุ่นยนต์ด้วยคำสั่งที่ว่าแล้ว prim path /World/Franka_01 จะโผล่ขึ้นมาใน Stage Panel ด้านขวามือตำแหน่งที่ไฮไลท์ในรูป

Franka in Stage

ต่อไปตัว code จะสั่งให้สร้างลูกบาศก์ 3 มิติ ด้วยคำสั่ง DynamicCuboid เราต้องระบุ prim_path และ name เหมือนกับตอนสร้างหุ่นยนต์ Franka

python
cube = DynamicCuboid(
  prim_path="/World/random_cube",
  name="cube",
  position=np.array([0.5, 0.3, 0.15]),
  scale=np.array([0.0515, 0.0515, 0.0515]),
  color=np.array([0, 0, 1.0]),
)

มี arguments เพิ่มเติมที่น่าสนใจตามนี้

  • position ตำแหน่งในหน่วยเมตร ระบุในแกน [x,y,z]
  • scale ขนาดในหน่วยเมตร ระบุในแกน [x,y,z]
  • color ค่าสีของลูกบาศก์ ในตัวอย่างเซ็ตไว้เป็นสีน้ำเงิน ระบุเป็น list [red, green, blue]

สุดท้ายจะการ add หุ่นยนต์และลูกบาศก์ เข้าไปยัง scene

python
world.scene.add(franka)
world.scene.add(cube)

การ add ไปยัง scene ช่วยให้เราสามารถ เคลียร์วัตถุได้ง่าย แล้วก็ทำให้ code ส่วนอื่นๆ ไม่จำกัดแค่ใน script ของเรา สามารถเรียกค้นหาวัตถุในฉากที่เรา add ไว้ใน world.scene ได้ด้วย โดยใช้ API นี้

python
world.scene.get_object("franka")

code ในส่วน Setup Scene รวมทั้งหมดจะมีหน้าตาตามนี้

python
async def run():
  ...
  world.scene.add_default_ground_plane()
  franka = Franka(prim_path="/World/Franka_01", name="franka")
  franka.initialize()
  cube = DynamicCuboid(
      prim_path="/World/random_cube",
      name="cube",
      position=np.array([0.5, 0.3, 0.15]),
      scale=np.array([0.0515, 0.0515, 0.0515]),
      color=np.array([0, 0, 1.0]),
  )
  world.scene.add(franka)
  world.scene.add(cube)
  ...

Controller Logic

เป็นส่วนสุดท้ายแล้วที่ต้องใส่ใน run() ทำหน้าที่สั่งการให้แขนหุ่นยนต์ Franka ใน simulation เคลื่อนที่ไปหยิบลูกบาศก์(cube) จากจุดหนึ่งไปยังอีกจุด

code ส่วนนี้เริ่มต้นด้วยการประกาศใช้งาน PickPlaceController ตั้งชื่อ controller และทำการใส่ค่า arguments gripper (ส่วนมือของหุ่นยนต์) และ robot_articulation (หุ่นยนต์ Franka) เข้าไป

python
controller = PickPlaceController(
  name="pick_place_controller",
  gripper=franka.gripper,
  robot_articulation=franka,
)

ทำการสั่ง griper ของหุ่นยนต์ ให้กางออกจนสุด เพื่อ init state

python
franka.gripper.set_joint_positions(franka.gripper.joint_opened_positions)

ขั้นตอนต่อไป คือการสร้าง actions ด้วย PickplaceController

หลักการทำงานพื้นฐานของ Controller นี้ จะค่อยๆอัพเดทตัวเองไปทีละนิด ในแต่ละเสี้ยววินาที เพื่อส่งคำสั่ง actions ให้หุ่นยนต์ Franka ทำงาน เราเลยต้องทำการเขียนกระบวนการทำงานนี้ด้วยสิ่งที่เรียกว่า physics callback function ที่เราจะทำการ register ใส่ลงไปให้กับ Isaac Sim Simulation Loop เพื่อให้รัน code เรา ทุกๆ 0.017 วินาที หรือ 60 ครั้งต่อวินาที (60Hz)

code ส่วนนี้จะ implement ใน function ชื่อ physic_step:

python
def physic_step(dt):
  cube_pos, _ = world.scene.get_object("cube").get_world_pose()
  goal_pos = np.array([0.5, -0.3, 0.0515 / 2.0])
  current_robot_joints = franka.get_joint_positions()
  actions = controller.forward(
      picking_position=cube_pos,
      placing_position=goal_pos,
      current_joint_positions=current_robot_joints,
  )
  franka.apply_action(actions)
  if controller.is_done():
      carb.log_info("Pick and place done")
      world.remove_physics_callback("franka_step")
      world.pause()

ใน function นี้ เราจะเริ่มด้วยการดึงข้อมูลของ cube ใน scene ขึ้นมา เพื่อหาตำแหน่ง position ของมัน และนำมาเก็บไว้ใน cube_pos ตามคำสั่งด้านล่าง

python
cube_pos, _ = world.scene.get_object("cube").get_world_pose()

get_world_pose() จะส่งค่ากลับมา 2 ค่าคือ position และ rotation ของวัตถุ เทียบกับ world frame แต่เราเลือกหยิบใช้งานมาแค่ position

ต่อไปจะเป็นการสร้าง actions จาก PickplaceController เพื่อสังให้หุ่นยนต์ทำงาน โดยตัวมันมันต้องการค่า 3 อย่าง ดังต่อไปนี้

  1. picking_position ตำแหน่งของวัตถุที่จะหยิบ
  2. placing_position ตำแหน่งที่จะวางวัตถุ
  3. current_robot_joints ตำแหน่งข้อต่อปัจจุบันทั้งหมดของหุ่นยนต์
python
current_robot_joints = franka.get_joint_positions()
actions = controller.forward(
  picking_position=cube_pos,
  placing_position=goal_pos,
  current_joint_positions=current_robot_joints,
)

จากนั้นทำการสั่ง execute ให้หุ่นยนต์ขยับไปหาเป้าหมายทีละนิดในทุกๆ 0.017 วินาทีด้วย คำสั่ง apply_action

python
franka.apply_action(actions)

และเพื่อดูว่า controller ทำงานเสร็จรึยัง เราเลยต้องใส่ code ส่วนนี้ลงไปด้วย

python
if controller.is_done():
  carb.log_info("Pick and place done")
  world.remove_physics_callback("franka_step")
  world.pause()

controller.is_done() จะคอยเช็คว่า Controller ทำงานสำเร็จไหม ถ้าสำเร็จ จะสั่งให้ลบ callback นี้ออกจาก world ทันที แล้วก็จะหยุดการทำงานของ simulation ด้วยคำสั่ง world.pause() เพื่อปิดโปรแกรม

สุดท้าย เราต้องไม่ลืม register physic_step() ไปยัง world ด้วยคำสั่ง add_physics_callback ด้วย และทำการสั่ง world.play_async() ให้ simulation รัน ไม่อย่างนั้น code ส่วน physic callback นี้จะไม่ทำงาน

python
world.add_physics_callback("franka_step", physic_step)
await world.play_async()

โน๊ตไว้ด้วยว่า ชื่อของ callback franka_step จะเป็นชื่ออะไรก็ได้ แต่ตอนสั่งลบ callback จะต้องระบุชื่อให้ตรงกัน ไม่อย่างนั้นจะเจอ memory leak เพราะ callback ไม่ถูกลบ

สรุป code ทั้งหมดในส่วนนี้

python
async def run():
...
  controller = PickPlaceController(
      name="pick_place_controller",
      gripper=franka.gripper,
      robot_articulation=franka,
  )

  franka.gripper.set_joint_positions(franka.gripper.joint_opened_positions)

  def physic_step(dt):
      cube_pos, _ = world.scene.get_object("cube").get_world_pose()
      goal_pos = np.array([0.5, -0.3, 0.0515 / 2.0])
      current_robot_joints = franka.get_joint_positions()
      actions = controller.forward(
          picking_position=cube_pos,
          placing_position=goal_pos,
          current_joint_positions=current_robot_joints,
      )
      franka.apply_action(actions)
      if controller.is_done():
          carb.log_info("Pick and place done")
          world.remove_physics_callback("franka_step")
          world.pause()

  world.add_physics_callback("franka_step", physic_step)
  await world.play_async()

ปิดท้าย

ในบล็อกนี้ ทุกคนได้เรียนรู้พื้นฐานของ Isaac Sim Core API ตั้งแต่การโหลดและสร้างวัตถุ การใช้งาน Controller สำหรับการหยิบจับด้วยแขนหุ่นยนต์ Franka ไปจนถึงการใช้งาน World API ทั้งในส่วนของ scene management และ callback

นอกจากนี้ ยังได้เรียนรู้การรันสคริปต์ผ่าน VS Code และการใช้งาน Isaac Powerpack pow-cli ซึ่งช่วยให้การติดตั้งและจัดการโปรเจกต์ Isaac Sim ทำได้ง่ายขึ้น

Isaac Powerpack เป็นโปรเจค Opensource ที่ผมพัฒนาขึ้นมาเอง ถ้าทุกคนรู้สึกว่ามันเป็นประโยชน์ ฝากกด Star ให้กับตัวโปรเจคหน่อยนะครับ ตามช่องทางนี้เลย isaac-powerpack เพื่อเป็นกำลังใจให้กับนักพัฒนาและติดตามการอัพเดทของโปรเจคครับ 🙏 💖 ✨

หวังว่าบล็อกนี้จะช่วยให้ทุกคนสามารถนำไปต่อยอด ทดลองเล่นฟีเจอร์อื่น ๆ ของ Isaac Sim ได้อย่างอิสระและสนุกมากขึ้นครับ