使用任务空间控制器#
此前的教程使用关节空间控制器控制机器人。但在许多场景中,任务空间控制更直观。例如,遥操作机器人时,直接指定期望的末端执行器位姿,比指定各关节的位置更方便。
本教程介绍如何用任务空间控制器控制机器人,使用 controllers.DifferentialIKController 跟踪期望的末端执行器位姿指令。
本教程使用 Isaac Sim PhysX,需要安装 Isaac Sim。控制器选择和控制循环的说明见 控制器 。
代码#
该教程对应于 scripts/tutorials/05_controllers 目录中的 run_diff_ik.py 脚本。
run_diff_ik.py 的代码
1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""
7This script demonstrates how to use the differential inverse kinematics controller with the simulator.
8
9The differential IK controller can be configured in different modes. It uses the Jacobians computed by
10PhysX. This helps perform parallelized computation of the inverse kinematics.
11
12The Franka high-PD preset uses the same USD and effort limits as the base preset, with stiffer gains
13for tracking IK targets. Other tasks can retain their calibrated actuator gains.
14
15.. code-block:: bash
16
17 # Usage
18 uv run python scripts/tutorials/05_controllers/run_diff_ik.py
19
20"""
21
22"""Parse the command-line arguments first."""
23
24import argparse
25from typing import TYPE_CHECKING
26
27from isaaclab.app import add_launcher_args, launch_simulation
28
29# add argparse arguments
30parser = argparse.ArgumentParser(description="Tutorial on using the differential IK controller.")
31parser.add_argument("--robot", type=str, default="franka_panda", help="Name of the robot.")
32parser.add_argument("--num_envs", type=int, default=128, help="Number of environments to spawn.")
33# append simulation launcher cli args
34add_launcher_args(parser)
35# parse the arguments
36args_cli = parser.parse_args()
37
38"""Rest everything follows."""
39
40import torch
41
42import isaaclab.sim as sim_utils
43from isaaclab.assets import AssetBaseCfg
44from isaaclab.controllers import DifferentialIKController, DifferentialIKControllerCfg
45from isaaclab.managers import SceneEntityCfg
46from isaaclab.markers import VisualizationMarkers
47from isaaclab.markers.config import FRAME_MARKER_CFG
48from isaaclab.scene import InteractiveSceneCfg
49from isaaclab.utils import clone, configclass, instantiate, replace
50from isaaclab.utils.assets import ISAAC_NUCLEUS_DIR
51from isaaclab.utils.math import subtract_frame_transforms
52
53if TYPE_CHECKING:
54 from isaaclab.scene import InteractiveScene
55
56##
57# Pre-defined configs
58##
59from isaaclab_assets import FRANKA_PANDA_HIGH_PD_CFG, UR10_CFG # isort:skip
60
61
62@configclass
63class TableTopSceneCfg(InteractiveSceneCfg):
64 """Configuration for a cart-pole scene."""
65
66 # ground plane
67 ground = AssetBaseCfg(
68 prim_path="/World/defaultGroundPlane",
69 spawn=sim_utils.GroundPlaneCfg(),
70 init_state=AssetBaseCfg.InitialStateCfg(pos=(0.0, 0.0, -1.05)),
71 )
72
73 # lights
74 dome_light = AssetBaseCfg(
75 prim_path="/World/Light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
76 )
77
78 # mount
79 table = AssetBaseCfg(
80 prim_path="{ENV_REGEX_NS}/Table",
81 spawn=sim_utils.UsdFileCfg(
82 usd_path=f"{ISAAC_NUCLEUS_DIR}/Props/Mounts/Stand/stand_instanceable.usd", scale=(2.0, 2.0, 2.0)
83 ),
84 )
85
86 # articulation
87 if args_cli.robot == "franka_panda":
88 robot = replace(FRANKA_PANDA_HIGH_PD_CFG, prim_path="{ENV_REGEX_NS}/Robot")
89 robot.spawn.variants["Physics"] = "physx"
90 elif args_cli.robot == "ur10":
91 robot = replace(UR10_CFG, prim_path="{ENV_REGEX_NS}/Robot")
92 else:
93 raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
94
95
96def run_simulator(sim: sim_utils.SimulationContext, scene: "InteractiveScene"):
97 """Runs the simulation loop."""
98 # Extract scene entities
99 # note: we only do this here for readability.
100 robot = scene["robot"]
101
102 # Create controller
103 diff_ik_cfg = DifferentialIKControllerCfg(command_type="pose", use_relative_mode=False, ik_method="dls")
104 diff_ik_controller = DifferentialIKController(diff_ik_cfg, num_envs=scene.num_envs, device=sim.device)
105
106 # Markers
107 frame_marker_cfg = clone(FRAME_MARKER_CFG)
108 frame_marker_cfg.markers["frame"].scale = (0.1, 0.1, 0.1)
109 ee_marker = VisualizationMarkers(replace(frame_marker_cfg, prim_path="/Visuals/ee_current"))
110 goal_marker = VisualizationMarkers(replace(frame_marker_cfg, prim_path="/Visuals/ee_goal"))
111
112 # Define goals for the arm (x,y,z,qx,qy,qz,qw)
113 ee_goals = [
114 [0.5, 0.5, 0.7, 0, 0.707, 0, 0.707],
115 [0.5, -0.4, 0.6, 0.707, 0, 0, 0.707],
116 [0.5, 0, 0.5, 1.0, 0.0, 0.0, 0.0],
117 ]
118 ee_goals = torch.tensor(ee_goals, device=sim.device)
119 # Track the given command
120 current_goal_idx = 0
121 # Create buffers to store actions
122 ik_commands = torch.zeros(scene.num_envs, diff_ik_controller.action_dim, device=robot.device)
123 ik_commands[:] = ee_goals[current_goal_idx]
124
125 # Specify robot-specific parameters
126 if args_cli.robot == "franka_panda":
127 robot_entity_cfg = SceneEntityCfg("robot", joint_names=["panda_joint.*"], body_names=["panda_hand"])
128 elif args_cli.robot == "ur10":
129 robot_entity_cfg = SceneEntityCfg("robot", joint_names=[".*"], body_names=["ee_link"])
130 else:
131 raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
132 # Resolving the scene entities
133 robot_entity_cfg.resolve(scene)
134 # Obtain the frame index of the end-effector
135 # For a fixed base robot, the frame index is one less than the body index. This is because
136 # the root body is not included in the returned Jacobians.
137 if robot.is_fixed_base:
138 ee_jacobi_idx = robot_entity_cfg.body_ids[0] - 1
139 else:
140 ee_jacobi_idx = robot_entity_cfg.body_ids[0]
141
142 # Define simulation stepping
143 sim_dt = sim.get_physics_dt()
144 count = 0
145 # Simulation loop
146 while sim.is_running():
147 # reset
148 if count % 150 == 0:
149 # reset time
150 count = 0
151 # reset joint state
152 joint_pos = robot.data.default_joint_pos.torch.clone()
153 joint_vel = robot.data.default_joint_vel.torch.clone()
154 robot.write_joint_position_to_sim_index(position=joint_pos)
155 robot.write_joint_velocity_to_sim_index(velocity=joint_vel)
156 robot.reset()
157 # reset actions
158 ik_commands[:] = ee_goals[current_goal_idx]
159 joint_pos_des = joint_pos[:, robot_entity_cfg.joint_ids].clone()
160 # reset controller
161 diff_ik_controller.reset()
162 diff_ik_controller.set_command(ik_commands)
163 # change goal
164 current_goal_idx = (current_goal_idx + 1) % len(ee_goals)
165 else:
166 # obtain quantities from simulation. The Jacobian DoF axis prepends
167 # ``num_base_dofs`` floating-base columns (0 for fixed-base, 6 for
168 # floating-base); shift the actuated-joint ids accordingly.
169 jacobi_joint_ids = [j + robot.num_base_dofs for j in robot_entity_cfg.joint_ids]
170 jacobian = robot.data.body_link_jacobian_w.torch[:, ee_jacobi_idx, :, jacobi_joint_ids]
171 ee_pose_w = robot.data.body_pose_w.torch[:, robot_entity_cfg.body_ids[0]]
172 root_pose_w = robot.data.root_pose_w.torch
173 joint_pos = robot.data.joint_pos.torch[:, robot_entity_cfg.joint_ids]
174 # compute frame in root frame
175 ee_pos_b, ee_quat_b = subtract_frame_transforms(
176 root_pose_w[:, 0:3], root_pose_w[:, 3:7], ee_pose_w[:, 0:3], ee_pose_w[:, 3:7]
177 )
178 # compute the joint commands
179 joint_pos_des = diff_ik_controller.compute(ee_pos_b, ee_quat_b, jacobian, joint_pos)
180
181 # apply actions
182 robot.set_joint_position_target_index(target=joint_pos_des, joint_ids=robot_entity_cfg.joint_ids)
183 scene.write_data_to_sim()
184 # perform step
185 sim.step()
186 # update sim-time
187 count += 1
188 # update buffers
189 scene.update(sim_dt)
190
191 # obtain quantities from simulation
192 ee_pose_w = robot.data.body_state_w.torch[:, robot_entity_cfg.body_ids[0], 0:7]
193 # update marker positions
194 ee_marker.visualize(ee_pose_w[:, 0:3], ee_pose_w[:, 3:7])
195 goal_marker.visualize(ik_commands[:, 0:3] + scene.env_origins, ik_commands[:, 3:7])
196
197
198def main():
199 """Main function."""
200 # Configure the simulation
201 sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device)
202 # Launch the simulator runtime that the configuration needs
203 with launch_simulation(sim_cfg, args_cli):
204 # Initialize the simulation context
205 sim = sim_utils.SimulationContext(sim_cfg)
206 # Set main camera
207 sim.set_camera_view([2.5, 2.5, 2.5], [0.0, 0.0, 0.0])
208 # Design scene
209 scene_cfg = TableTopSceneCfg(num_envs=args_cli.num_envs, env_spacing=2.0)
210 scene = instantiate(scene_cfg)
211 # Play the simulator
212 sim.reset()
213 # Now we are ready!
214 print("[INFO]: Setup complete...")
215 # Run the simulator
216 run_simulator(sim, scene)
217
218
219if __name__ == "__main__":
220 # run the main function
221 main()
代码解释#
使用任务空间控制器时,必须确保输入量采用正确的坐标系。多个并行环境实例位于同一个仿真世界坐标系中,但通常希望各环境有自己的局部坐标系。可通过 scene.InteractiveScene.env_origins 获取各环境的原点。
API 使用以下符号表示坐标系:
仿真世界坐标系(记为
w):整个仿真的坐标系。环境局部坐标系(记为
e):各环境自身的坐标系。机器人基座坐标系(记为
b):机器人基座连杆的坐标系。
资产实例不感知环境的局部坐标系,返回的状态采用仿真世界坐标系。因此,需要将这些量转换到环境局部坐标系;对于这里的位置量,减去环境原点即可。
创建一个 IK 控制器#
DifferentialIKController 计算机器人达到期望末端位姿所需的关节位置。实现采用 PyTorch 批量运算,支持阻尼最小二乘法、伪逆法等逆运动学求解器,可通过 ik_method 选择。控制器既支持相对位姿指令,也支持绝对位姿指令。
本教程使用阻尼最小二乘法计算期望关节位置,并采用绝对位姿指令模式跟踪末端执行器的目标位姿。
# Create controller
diff_ik_cfg = DifferentialIKControllerCfg(command_type="pose", use_relative_mode=False, ik_method="dls")
diff_ik_controller = DifferentialIKController(diff_ik_cfg, num_envs=scene.num_envs, device=sim.device)
获取机器人的关节和刚体索引#
IK 控制器只负责计算,需要用户提供机器人的关节位置、当前末端执行器位姿和雅可比矩阵。
assets.ArticulationData.joint_pos 提供所有关节的位置,但这里仅需机械臂关节的数据,不需要夹爪关节。同样, assets.ArticulationData.body_state_w 提供所有刚体的状态,而这里只需末端执行器的状态。因此,需要通过索引从数组中取出所需数据。
关节体类提供 find_joints() 和 find_bodies() 方法,根据关节和刚体名称返回对应索引。
虽然可以直接调用这些方法获取索引,但推荐使用 SceneEntityCfg 解析索引。API 的多个部分都使用该类从场景实体中提取信息。它内部会调用上述方法获取索引,并额外检查所提供名称是否有效,因此更加稳妥。
# Specify robot-specific parameters
if args_cli.robot == "franka_panda":
robot_entity_cfg = SceneEntityCfg("robot", joint_names=["panda_joint.*"], body_names=["panda_hand"])
elif args_cli.robot == "ur10":
robot_entity_cfg = SceneEntityCfg("robot", joint_names=[".*"], body_names=["ee_link"])
else:
raise ValueError(f"Robot {args_cli.robot} is not supported. Valid: franka_panda, ur10")
# Resolving the scene entities
robot_entity_cfg.resolve(scene)
# Obtain the frame index of the end-effector
# For a fixed base robot, the frame index is one less than the body index. This is because
# the root body is not included in the returned Jacobians.
if robot.is_fixed_base:
ee_jacobi_idx = robot_entity_cfg.body_ids[0] - 1
else:
ee_jacobi_idx = robot_entity_cfg.body_ids[0]
计算机器人命令#
IK 控制器将设置目标指令与计算目标关节位置分为两个操作,以便用户以不同于机器人控制频率的频率运行 IK 计算。
set_command() 接收批量数组形式的期望末端执行器位姿,位姿采用机器人基座坐标系。
# reset controller
diff_ik_controller.reset()
diff_ik_controller.set_command(ik_commands)
随后,通过 compute() 计算期望关节位置。输入包括基座坐标系下的当前末端执行器位姿、雅可比矩阵和当前关节位置。雅可比矩阵从机器人数据中读取,其数值由物理引擎计算。
# obtain quantities from simulation. The Jacobian DoF axis prepends
# ``num_base_dofs`` floating-base columns (0 for fixed-base, 6 for
# floating-base); shift the actuated-joint ids accordingly.
jacobi_joint_ids = [j + robot.num_base_dofs for j in robot_entity_cfg.joint_ids]
jacobian = robot.data.body_link_jacobian_w.torch[:, ee_jacobi_idx, :, jacobi_joint_ids]
ee_pose_w = robot.data.body_pose_w.torch[:, robot_entity_cfg.body_ids[0]]
root_pose_w = robot.data.root_pose_w.torch
joint_pos = robot.data.joint_pos.torch[:, robot_entity_cfg.joint_ids]
# compute frame in root frame
ee_pos_b, ee_quat_b = subtract_frame_transforms(
root_pose_w[:, 0:3], root_pose_w[:, 3:7], ee_pose_w[:, 0:3], ee_pose_w[:, 3:7]
)
# compute the joint commands
joint_pos_des = diff_ik_controller.compute(ee_pos_b, ee_quat_b, jacobian, joint_pos)
与此前教程一样,将计算得到的关节位置目标应用到机器人即可。
# apply actions
robot.set_joint_position_target_index(target=joint_pos_des, joint_ids=robot_entity_cfg.joint_ids)
scene.write_data_to_sim()
代码执行#
运行脚本,查看结果:
uv run isaaclab -p scripts/tutorials/05_controllers/run_diff_ik.py --robot franka_panda --num_envs 128 --viz kit
./isaaclab.sh -p scripts/tutorials/05_controllers/run_diff_ik.py --robot franka_panda --num_envs 128 --viz kit
脚本会启动包含 128 个机器人的仿真,由 IK 控制器控制机器人。坐标系标记会显示当前和目标末端执行器位姿。脚本每隔 150 个仿真步重置机器人并更换目标,循环使用脚本中指定的位姿。
在终端按 Ctrl+C 可停止仿真。