1. 机器人就是 Articulation

在 Isaac Sim 中,机器人本质上就是一个 Articulation,也就是一组通过关节(Joint)连接起来的刚体(Rigid Body),把这些东西按照一个特定的拓扑结构组织起来,并且每一个可动的关节都带有一个 Drive,可以接受位置或者速度的控制指令

Robot (Articulation) = Rigid Body 刚体集合
                     + Joint 关节(连接刚体)
                     + Drive 驱动器(接受控制指令)

Isaac Sim 提供了 omni.isaac.core.robots.robot.Robot 这个类来对 Articulation 进行上层封装,它本质上是 Articulation 的一个子类,提供了诸如 get_joint_positions()set_joint_positions()等方法。一般来说我们会直接把一个机器人的 USD 加载进来,然后用 Robot 类去包裹对应的 prim。

类继承关系:
ArticulationView (底层,PhysX 直接交互)
    └── Articulation (中间层,封装了物理视图操作)
         └── Robot (上层,提供友好的 Python API)
              ├── get_joint_positions()
              ├── set_joint_positions()
              ├── apply_action()
              ├── dof_names
              ├── num_dof
              └── ...

这里给出一个demo:

from omni.isaac.core.robots.robot import Robot
from omni.isaac.core.utils.prims import create_prim

# 把机器人 USD 加载到指定的 prim path 下
create_prim(
    prim_path="/World/robot",
    prim_type="Xform",
    usd_path="/path/to/your/robot.usd",
)

robot = Robot(prim_path="/World/robot", name="my_robot")

# 一些求解器相关的参数,影响物理仿真的稳定性
robot.set_solver_position_iteration_count(124)
robot.set_solver_velocity_iteration_count(4)
robot.set_stabilization_threshold(0.005)

# 设置机器人在世界中的位置和姿态
robot.set_world_pose(position, orientation)

需要注意的是:先创建好所有 prim,把 robot 加入 world.scene,调用 world.reset(),然后再 robot.initialize()

原因:initialize() 内部需要创建 ArticulationView ,而 ArticulationView 依赖 PhysX 的物理场景。 world.reset() 才会真正初始化 PhysX 物理世界。如果跳过 reset 直接调用 initialize,底层的 View 会找不到对应的物理句柄

world.scene.add(robot)
world.reset()
robot.initialize()

时间线:

创建 Prim ———→ 加入 Scene ———→ reset() ———→ initialize()
    ✅                             ✅                          ✅                         ✅  

此时只是USD场景       注册到场景    物理引擎初始化       创建PhysX View

树中的一个节点,                             ArticulationView

没有任何物理属性

initialize() 之后,机器人就成功激活了

print(robot.dof_names)          # 每个关节的名字,顺序很重要
print(robot.num_dof)            # 总自由度数
print(robot.get_joint_positions())  # 当前每个关节的角度/位置

有一个非常关键、并且在后面运动规划环节会反复用到的点:dof_names 的顺序就是关节状态向量的顺序。Isaac Sim 内部的关节顺序是按照 USD 的拓扑解析出来的,它和你脑子里想的「关节 1 到关节 7」的顺序不一定一致,更不一定和 cuRobo 期望的顺序一致。

2. 控制机器人

控制机器人最直接的方式有两种:直接设置和力控

  • set_joint_positions()

它会直接把关节设置到指定的位置,绕过物理引擎,相当于瞬移。这在初始化机器人姿态、或者重置场景的时候非常有用,但是它不符合物理,不应该在正常的控制循环中使用:

# 直接把 Franka 摆到一个默认的初始姿态(7 个臂关节 + 若干夹爪关节)
robot.set_joint_positions(
    [0.0, -0.785, 0.0, -2.356, 0.0, 1.57079, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
)

值得一提的是,对于我们使用的 Franka RoboTiq 资产来说,其包含一个 RoboTiq 夹爪,包含了六个自由度,当然,实际上这个夹爪的不同自由度之间存在约束,如 Mimic Joint 等,因此实际上在控制一个 Joint 的时候其他的都会随之运动。

  • Drive 控制关节-----set_joint_position_targets

让物理引擎在每一步仿真中根据 stiffness 和 damping 把关节朝着目标驱动过去。在 Isaac Sim 中,这一般通过 ArticulationView 来做

# robot_view 即 robot._articulation_view
robot_view = robot._articulation_view

# 给指定的关节设置位置目标,物理引擎会逐步驱动过去
robot_view.set_joint_position_targets(
    target_joint_positions,
    joint_indices=[0, 1, 2, 3, 4, 5, 6],  # 只控制 7 个臂关节
)
world.step(render=False)   # 推进物理;

Drive 类型:
├── Position Drive (位置驱动)  → 给目标角度,关节自动运动过去
│   stiffness 高 → 像弹簧,紧紧拉向目标位置
│   damping 高   → 像阻尼器,减少振荡

├── Velocity Drive (速度驱动)  → 给目标速度,关节以此速度运动

└── Effort Drive (力矩驱动)    → 直接给力/力矩

joint_indices 这个参数用来指定我们要控制哪些关节,这一点非常实用,因为一个机器人往往把「手臂」和「夹爪」放在同一个 Articulation 里,但是它们的控制逻辑是分开的:手臂用curobo运动规划给出的轨迹来驱动,而夹爪只需要在「张开」和「闭合」两个状态之间切换

以 Franka + Robotiq 夹爪为例,臂有 7 个自由度,夹爪有 6 个自由度(Robotiq 2F-85 是个多连杆的结构),我们可以这样定义张开和闭合:

arm_dof_num = 7
gripper_open = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
gripper_close = [0.7853, 0.7853, -0.7853, -0.7853, -0.7853, -0.7853]

# 把规划得到的臂部轨迹和夹爪状态拼成一个完整的 action
def make_action(arm_joints, grasp: bool):
    return np.concatenate([
        arm_joints[:arm_dof_num],
        gripper_close if grasp else gripper_open,
    ])

3. Mimic 关节

Robotiq 夹爪(以及很多真实夹爪)的机械结构中, 多个关节之间存在运动耦合关系。

Mimic 关节 ,即从动关节,其运动是主动关节运动的 固定比例函数 ,不需要独立驱动。

Robotiq 2F-85 夹爪结构:

  主动关节 (finger_joint)          从动关节 (mimic joints)
  ┌──────────────┐              ┌───────────────────-──┐
  │   电机驱动    │ ──约束关系──→  │  inner_finger_joint  │
  │              │              │  outer_knuckle_joint │
  │  你控制的这个 │               │  ... (3-4个从动关节)   │
  └──────────────┘              └────────────────────-─┘
       
       当 finger_joint 转动 θ 度时
       → inner_finger 自动转动 f(θ) 度
       → outer_knuckle 自动转动 g(θ) 度
       (由机械连杆结构决定的比例关系)

如何控制Mimic 关节?

  • 驱动关节设置较小的 stiffness 和 damping,并且从动关节的 max force 直接设为 0

from genmanip.utils.usd_utils import (
    set_drive_damping_and_stiffness,
    set_drive_max_force,
)

# 1. # 主动关节:给一个比较软的驱动,通过调节 damping 和 stiffness 可以改变夹爪的闭合速度等
for joint in active_joints:
    set_drive_damping_and_stiffness(joint_path, damping=0.01, stiffness=0.1)
    set_drive_max_force(joint_path, 10000.0)

# 2. 从动关节:max force 设为 0,纯粹被带动
for joint in passive_joints:
    set_drive_max_force(joint_path, 0.0)   # max_force=0 = 不产生任何驱动力!
  • 只控制第一个 驱动关节即可,但要求 USD 中 Mimic 约束已经正确配置好
# 其余关节完全由 mimic 约束自动跟随
gripper_dof_num = 1   # 只需要控制1个DOF
gripper_open = [0.0]
gripper_close = [1]

# 控制时只需要设置1个值:
robot.set_joint_positions(arm_positions + [gripper_value])

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐