This example demonstrates Cartesian space control by creating a dedicated Franka controller node that processes target pose commands and outputs joint commands using inverse kinematics with PyTorch Kinematics.
cd 02_target_pose_control
dora build target_pose_control_pytorch.yml
dora run target_pose_control_pytorch.yml
You should see:
While the simulation is running, you can send custom poses:
# In another terminal
python3 -c "
import pyarrow as pa
from dora import Node
import time
node = Node()
# Move to position (0.5, 0.2, 0.6) with downward orientation
target_pose = [0.5, 0.2, 0.6, 180.0, 0.0, 90.0] # x, y, z, roll, pitch, yaw
node.send_output(
'target_pose',
pa.array(target_pose, type=pa.float64()),
metadata={'timestamp': time.time()}
)
"
pose_publisher.py)class PosePublisher:
def __init__(self):
# Predefined sequence of target poses [x, y, z, roll, pitch, yaw]
self.target_poses = [
[0.5, 0.5, 0.3, 180.0, 0.0, 90.0], # Position + RPY orientation
[0.6, 0.2, 0.5, 180.0, 0.0, 45.0], # Different orientation
# ... more poses
]
What it does:
target_pose array [x, y, z, roll, pitch, yaw]franka_controller_pytorch.py)Key Components:
class FrankaController:
def __init__(self):
urdf_path = "path to the file/panda.urdf"
with open(urdf_path, 'rb') as f:
urdf_content = f.read()
self.chain = pk.build_serial_chain_from_urdf(urdf_content, "panda_hand")
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.chain = self.chain.to(device=self.device)
What it does:
[x, y, z, roll, pitch, yaw] and current joint positions from MuJoCoControl Algorithm (Differential IK):
def apply_differential_ik_control(self):
# 1. Get current end-effector pose using PyTorch Kinematics FK
current_ee_pos, current_ee_rot = self.get_current_ee_pose(self.current_joint_pos)
# 2. Calculate position and orientation errors
pos_error = self.target_pos - current_ee_pos
rot_error = (desired_rot * current_rot.inv()).as_rotvec()
# 3. Create 6D twist vector [linear_vel, angular_vel]
twist = np.zeros(6)
twist[:3] = self.Kpos * pos_error / self.integration_dt
twist[3:] = self.Kori * rot_error / self.integration_dt
# 4. Compute Jacobian using PyTorch Kinematics
jac = self.compute_jacobian_pytorch(self.current_joint_pos) # (6, 7)
# 5. Solve differential IK with damped least squares
diag = self.damping * np.eye(6)
dq = jac.T @ np.linalg.solve(jac @ jac.T + diag, twist)
# 6. Add nullspace control to prefer home position
N = np.eye(7) - np.linalg.pinv(jac) @ jac # Nullspace projection
dq_null = self.Kn * (self.home_pos - self.current_joint_pos)
dq += N @ dq_null
# 7. Integrate to get new joint positions
new_joints = self.current_joint_pos + dq * self.integration_dt
dora-mujoco)dora-mujoco node): Physics, rendering, joint statefranka_controller_pytorch.py): PyTorch Kinematics for FK/Jacobianclass FrankaController:
def __init__(self):
# Load kinematics model for computation only
self.chain = pk.build_serial_chain_from_urdf(urdf_content, "panda_hand")
self.chain = self.chain.to(device=self.device) # GPU acceleration
def get_current_ee_pose(self, joint_angles):
"""PyTorch Kinematics FK"""
q = torch.tensor(joint_angles, device=self.device, dtype=torch.float32).unsqueeze(0)
tf = self.chain.forward_kinematics(q)
# ... extract position and rotation
Performance Benefits:
Control Benefits:
Differential IK (Current Implementation):
# Compute small joint movements based on end-effector error
dq = jac.T @ np.linalg.solve(jac @ jac.T + diag, twist)
new_joints = current_joints + dq * dt
Analytical IK (Alternative):
# Solve for exact joint configuration to reach target [sometimes exact solution is not available can result in `None return`]
ik_solver = pk.PseudoInverseIK(chain, ...)
solution = ik_solver.solve(target_transform)
Why Differential IK:
chain.forward_kinematics(q)chain.jacobian(q).to(device=device)For real robot deployment:
This example can be extended with:
"PyTorch Kinematics not found"
pip install pytorch-kinematics
"CUDA out of memory"
device = "cpu" in controller initialization"Robot moves erratically"
"Controller slower than expected"
torch.cuda.is_available()