Source code for uwlab.devices.se3_keyboard
# Copyright (c) 2024-2025, The UW Lab Project Developers.
# All Rights Reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
from __future__ import annotations
import torch
from typing import TYPE_CHECKING
from isaaclab.devices import Se3Keyboard
from isaaclab.utils.math import matrix_from_quat
if TYPE_CHECKING:
from uwlab.devices import KeyboardCfg
[docs]class Se3Keyboard(Se3Keyboard):
"""A keyboard controller for sending SE(3) commands as absolute poses and binary command (open/close).
This class is designed to provide a keyboard controller for a robotic arm with a gripper.
It uses the Omniverse keyboard interface to listen to keyboard events and map them to robot's
task-space commands. Different from Isaac Lab Se3Keyboard that adds delta command to current robot pose,
this implementation controls a target pose which robot actively tracks. this error corrective property is
advantageous to use when robot is prone to drift or under actuated
The command comprises of two parts:
* absolute pose: a 6D vector of (x, y, z, roll, pitch, yaw) in meters and radians.
* gripper: a binary command to open or close the gripper.
Key bindings:
============================== ================= =================
Description Key (+ve axis) Key (-ve axis)
============================== ================= =================
Toggle gripper (open/close) K
Move along x-axis W S
Move along y-axis A D
Move along z-axis Q E
Rotate along x-axis Z X
Rotate along y-axis T G
Rotate along z-axis C V
============================== ================= =================
.. seealso::
The official documentation for the keyboard interface: `Carb Keyboard Interface <https://docs.omniverse.nvidia.com/dev-guide/latest/programmer_ref/input-devices/keyboard.html>`__.
"""
[docs] def __init__(self, cfg: KeyboardCfg, device="cuda:0"):
"""Initialize the keyboard layer.
Args:
pos_sensitivity: Magnitude of input position command scaling. Defaults to 0.05.
rot_sensitivity: Magnitude of scale input rotation commands scaling. Defaults to 0.5.
"""
super().__init__(cfg.pos_sensitivity, cfg.rot_sensitivity)
self.device = device
self.enable_gripper_command = cfg.enable_gripper_command
self.init_pose = torch.tensor([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]], device=self.device)
self.abs_pose = torch.zeros((1, 6), device=self.device)
"""
Operations
"""
def reset(self):
super().reset()
self.abs_pose = self.init_pose.clone()
[docs] def advance(self):
"""Provides the result from internal target command modified by keyboard event.
Returns:
A tuple containing the absolute pose command and gripper commands.
"""
delta_pose, gripper_command = super().advance()
delta_pose = delta_pose.astype("float32")
# convert to torch
delta_pose = torch.tensor(delta_pose, device=self.device).view(1, -1)
self.abs_pose[:, :3] += delta_pose[:, :3]
self.abs_pose[:, 3:] += delta_pose[:, 3:]
if self.enable_gripper_command:
if gripper_command:
gripper_command = torch.tensor([[1.0]], device=self.device)
else:
gripper_command = torch.tensor([[-1.0]], device=self.device)
return self.abs_pose.clone(), gripper_command
else:
return self.abs_pose.clone()
def apply_local_translation(current_position, local_translation, orientation_quaternion):
# Assuming matrix_from_quat correctly handles batch inputs and outputs a batch of rotation matrices
rotation_matrix = matrix_from_quat(orientation_quaternion) # Expected shape (n, 3, 3)
# Ensure local_translation is correctly shaped for batch matrix multiplication
local_translation = local_translation.unsqueeze(-1) # Shape becomes (n, 3, 1) for matmul
local_translation[:, [1, 2]] = -local_translation[:, [2, 1]]
# Rotate the local translation vector to align with the global frame
global_translation = torch.matmul(rotation_matrix, local_translation).squeeze(-1) # Back to shape (n, 3)
# Apply the translated vector to the object's current position
new_position = current_position + global_translation
return new_position