3D transformations for Python with comprehensive rotation representations, coordinate conversions, and visualization tools
—
Loading and managing robot kinematic chains from URDF (Unified Robot Description Format) files with joint control, collision detection, and 3D visualization capabilities.
Extended TransformManager with URDF loading and joint management capabilities.
class UrdfTransformManager(TransformManager):
def __init__(self, strict_check=True, check=True):
"""Initialize URDF-capable transformation manager."""
def load_urdf(self, urdf_xml, mesh_path=None, package_dir=None):
"""
Load robot description from URDF file.
Parameters:
- urdf_xml: str - URDF XML content or file path
- mesh_path: str, optional - Path to mesh files
- package_dir: str, optional - ROS package directory
"""
def add_joint(self, joint_name, from_frame, to_frame, child2parent, axis, limits=(float("-inf"), float("inf")), joint_type="revolute"):
"""
Add joint to robot model.
Parameters:
- joint_name: str - Joint identifier
- from_frame: str - Parent frame name
- to_frame: str - Child frame name
- child2parent: array, shape (4, 4) - Joint transformation
- axis: array, shape (3,) - Joint rotation axis
- limits: tuple - Joint angle limits (min, max)
- joint_type: str - Joint type ("revolute", "prismatic", "fixed")
"""
def set_joint(self, joint_name, value):
"""
Set joint position.
Parameters:
- joint_name: str - Joint identifier
- value: float - Joint angle in radians (or position for prismatic)
"""
def get_joint_limits(self, joint_name):
"""
Get joint limits.
Parameters:
- joint_name: str - Joint identifier
Returns:
- limits: tuple - (min_limit, max_limit)
"""
def plot_visuals(self, frame, ax=None, ax_s=1, wireframe=False, convex_hull_of_mesh=True, alpha=0.3):
"""Plot visual geometries of robot in specified frame."""
def plot_collision_objects(self, frame, ax=None, ax_s=1, wireframe=True, convex_hull_of_mesh=True, alpha=1.0):
"""Plot collision geometries of robot in specified frame."""Classes representing different geometric shapes in URDF files.
class Box(Geometry):
def __init__(self, frame, mesh_path, package_dir, color):
"""Box geometry primitive."""
# Attributes:
# size: np.ndarray, shape (3,) - Box dimensions [width, height, depth]
class Sphere(Geometry):
def __init__(self, frame, mesh_path, package_dir, color):
"""Sphere geometry primitive."""
# Attributes:
# radius: float - Sphere radius
class Cylinder(Geometry):
def __init__(self, frame, mesh_path, package_dir, color):
"""Cylinder geometry primitive."""
# Attributes:
# radius: float - Cylinder radius
# length: float - Cylinder height
class Mesh(Geometry):
def __init__(self, frame, mesh_path, package_dir, color):
"""Mesh geometry from file."""
# Attributes:
# filename: str - Path to mesh file
# scale: np.ndarray, shape (3,) - Scaling factors [x, y, z]Utility functions for parsing URDF XML content.
def parse_urdf(urdf_xml, mesh_path=None, package_dir=None, strict_check=True):
"""
Parse URDF XML into structured data.
Parameters:
- urdf_xml: str - URDF XML content
- mesh_path: str, optional - Path to mesh files
- package_dir: str, optional - ROS package directory
- strict_check: bool - Enable strict validation
Returns:
- robot_name: str - Robot name from URDF
- links: list of Link - Parsed link objects
- joints: list of Joint - Parsed joint objects
"""
def initialize_urdf_transform_manager(tm, robot_name, links, joints):
"""
Initialize TransformManager from parsed URDF data.
Parameters:
- tm: UrdfTransformManager - Transform manager instance
- robot_name: str - Robot name
- links: list of Link - Link objects
- joints: list of Joint - Joint objects
"""class UrdfException(Exception):
"""Exception raised during URDF parsing or processing."""from pytransform3d.urdf import UrdfTransformManager
# Create URDF manager
utm = UrdfTransformManager()
# Load robot from URDF file
with open("robot.urdf", "r") as f:
urdf_content = f.read()
utm.load_urdf(urdf_content, mesh_path="./meshes/")
# Print available frames
print("Robot frames:", utm.nodes)
# Set joint positions
utm.set_joint("shoulder_joint", 0.5) # 0.5 radians
utm.set_joint("elbow_joint", -1.2) # -1.2 radians
# Get end effector pose
base_to_end = utm.get_transform("base_link", "end_effector")
print(f"End effector position: {base_to_end[:3, 3]}")from pytransform3d.urdf import UrdfTransformManager
import numpy as np
utm = UrdfTransformManager()
utm.load_urdf(urdf_content)
# Get joint limits
shoulder_limits = utm.get_joint_limits("shoulder_joint")
print(f"Shoulder joint limits: {shoulder_limits}")
# Set joint within limits
joint_angle = np.clip(1.5, shoulder_limits[0], shoulder_limits[1])
utm.set_joint("shoulder_joint", joint_angle)
# Animate joint motion
angles = np.linspace(shoulder_limits[0], shoulder_limits[1], 50)
end_positions = []
for angle in angles:
utm.set_joint("shoulder_joint", angle)
T = utm.get_transform("base_link", "end_effector")
end_positions.append(T[:3, 3])
end_positions = np.array(end_positions)
print(f"End effector trajectory: {end_positions.shape}")import matplotlib.pyplot as plt
from pytransform3d.urdf import UrdfTransformManager
from pytransform3d.plot_utils import make_3d_axis
# Load robot
utm = UrdfTransformManager()
utm.load_urdf(urdf_content, mesh_path="./meshes/")
# Set robot configuration
utm.set_joint("base_joint", 0.0)
utm.set_joint("shoulder_joint", 0.5)
utm.set_joint("elbow_joint", -1.0)
utm.set_joint("wrist_joint", 0.3)
# Plot robot
ax = make_3d_axis(ax_s=2)
# Plot coordinate frames
utm.plot_frames_in("base_link", ax=ax, s=0.1)
# Plot visual geometries
utm.plot_visuals("base_link", ax=ax, alpha=0.7)
# Plot collision objects (wireframe)
utm.plot_collision_objects("base_link", ax=ax, wireframe=True, alpha=0.3)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()import numpy as np
import matplotlib.pyplot as plt
from pytransform3d.urdf import UrdfTransformManager
utm = UrdfTransformManager()
utm.load_urdf(urdf_content)
# Define multiple configurations
configurations = [
{"shoulder_joint": 0.0, "elbow_joint": 0.0},
{"shoulder_joint": 0.5, "elbow_joint": -1.0},
{"shoulder_joint": 1.0, "elbow_joint": -1.5},
{"shoulder_joint": -0.5, "elbow_joint": 0.8},
]
# Plot workspace
fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(111, projection='3d')
colors = ['red', 'green', 'blue', 'orange']
for i, config in enumerate(configurations):
# Set configuration
for joint_name, angle in config.items():
utm.set_joint(joint_name, angle)
# Get end effector position
T = utm.get_transform("base_link", "end_effector")
pos = T[:3, 3]
# Plot end effector
ax.scatter(*pos, c=colors[i], s=100, label=f'Config {i+1}')
# Plot robot frames (smaller)
utm.plot_frames_in("base_link", ax=ax, s=0.05)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.legend()
plt.show()import numpy as np
from pytransform3d.urdf import UrdfTransformManager
utm = UrdfTransformManager()
utm.load_urdf(urdf_content)
# Sample joint space
n_samples = 1000
joint_names = ["shoulder_joint", "elbow_joint", "wrist_joint"]
# Get joint limits
limits = {}
for joint_name in joint_names:
limits[joint_name] = utm.get_joint_limits(joint_name)
# Generate random configurations
workspace_points = []
for _ in range(n_samples):
config = {}
for joint_name in joint_names:
low, high = limits[joint_name]
if np.isfinite(low) and np.isfinite(high):
angle = np.random.uniform(low, high)
else:
angle = np.random.uniform(-np.pi, np.pi)
config[joint_name] = angle
utm.set_joint(joint_name, angle)
# Get end effector position
T = utm.get_transform("base_link", "end_effector")
workspace_points.append(T[:3, 3])
workspace_points = np.array(workspace_points)
# Analyze workspace
print(f"Workspace bounds:")
print(f" X: {workspace_points[:, 0].min():.3f} to {workspace_points[:, 0].max():.3f}")
print(f" Y: {workspace_points[:, 1].min():.3f} to {workspace_points[:, 1].max():.3f}")
print(f" Z: {workspace_points[:, 2].min():.3f} to {workspace_points[:, 2].max():.3f}")
# Plot workspace
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(workspace_points[:, 0], workspace_points[:, 1], workspace_points[:, 2],
alpha=0.1, s=1)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()Install with Tessl CLI
npx tessl i tessl/pypi-pytransform3d