Joint Limit Tables
Example script: examples/differential_wrist_joint_limit_demo.py
This guide explains Joint Limit Tables, a feature for handling dynamic joint limits where one joint’s valid range depends on another joint’s current angle.
What are Joint Limit Tables?
In standard robot models, each joint has fixed minimum and maximum angle limits. However, some mechanical designs have coupled joint constraints where moving one joint changes the valid range of another joint.
Joint limit tables visualize the valid region (blue) where the dependent joint can move based on the target joint’s angle. The green dot shows the current position is within limits.
Common examples include:
Differential wrist mechanisms: The roll and pitch joints share tendons/gears
Cable-driven robots: Cable routing creates coupled constraints
Parallel link mechanisms: Kinematic coupling between joints
The Joint Limit Table feature allows you to define these dynamic relationships.
Basic Concept
A Joint Limit Table defines:
Target Joint: The joint whose angle determines the limits
Dependent Joint: The joint whose limits change based on the target
Sample Angles: Discrete angles of the target joint
Min/Max Angles: The dependent joint’s limits at each sample angle
Target Joint Angle: -90° -45° 0° 45° 90°
| | | | |
Dependent Min: -10° -30° -50° -30° -10°
Dependent Max: 10° 30° 50° 30° 10°
The limits are linearly interpolated between sample points.
DifferentialWristSample Robot
scikit-robot includes a sample robot with a differential wrist mechanism:
from skrobot.models import DifferentialWristSample
# Load robot with joint limit tables enabled (default)
robot = DifferentialWristSample(use_joint_limit_table=True)
# The wrist joints have coupled constraints
wrist_y = robot.WRIST_JOINT_Y # Pitch
wrist_r = robot.WRIST_JOINT_R # Roll
# Check current dynamic limits
print(f"WRIST_R min: {wrist_r.min_angle}") # Changes with WRIST_Y
print(f"WRIST_R max: {wrist_r.max_angle}") # Changes with WRIST_Y
Defining Joint Limit Tables
Joint limit tables are defined in YAML format:
joint_limit_tables:
# WRIST_JOINT_Y affects WRIST_JOINT_R limits
- target_joint: WRIST_JOINT_Y
dependent_joint: WRIST_JOINT_R
target_min_angle: -90 # degrees
target_max_angle: 90 # degrees
min_angles: [-10, -20, -50, -20, -10] # at each sample point
max_angles: [10, 20, 50, 20, 10] # at each sample point
# WRIST_JOINT_R affects WRIST_JOINT_Y limits (bidirectional)
- target_joint: WRIST_JOINT_R
dependent_joint: WRIST_JOINT_Y
target_min_angle: -90
target_max_angle: 90
min_angles: [-15, -25, -45, -25, -15]
max_angles: [15, 25, 45, 25, 15]
Apply the tables to a robot:
import yaml
from skrobot.model import create_joint_limit_table
with open('joint_limits.yaml', 'r') as f:
config = yaml.safe_load(f)
for table_config in config['joint_limit_tables']:
create_joint_limit_table(
robot,
table_config['target_joint'],
table_config['dependent_joint'],
table_config['target_min_angle'],
table_config['target_max_angle'],
table_config['min_angles'],
table_config['max_angles'],
)
Using Joint Limit Tables with IK
Joint limit tables are automatically respected by the IK solver:
from skrobot.models import DifferentialWristSample
robot = DifferentialWristSample(use_joint_limit_table=True)
robot.reset_manip_pose()
# IK will respect dynamic joint limits
target_coords = robot.end_coords.copy_worldcoords()
target_coords.translate([0.05, 0, 0])
robot.inverse_kinematics(
target_coords,
link_list=robot.arm.link_list,
move_target=robot.end_coords,
)
# The solution will satisfy coupled constraints
print(f"WRIST_Y: {robot.WRIST_JOINT_Y.joint_angle()}")
print(f"WRIST_R: {robot.WRIST_JOINT_R.joint_angle()}")
Batch IK with Dynamic Limits
For batch IK, dynamic limits are also supported:
import numpy as np
from skrobot.coordinates import Coordinates
from skrobot.models import DifferentialWristSample
robot = DifferentialWristSample(use_joint_limit_table=True)
robot.reset_manip_pose()
# Create multiple target poses
base_pos = robot.end_coords.worldpos()
target_coords = []
for i in range(100):
offset = np.random.uniform(-0.05, 0.05, size=3)
c = Coordinates(pos=base_pos + offset, rot=robot.end_coords.worldrot())
target_coords.append(c)
# Solve batch IK (automatically handles dynamic limits)
solutions, success_flags, _ = robot.batch_inverse_kinematics(
target_coords,
link_list=robot.arm.link_list,
move_target=robot.end_coords,
stop=50,
attempts_per_pose=3,
)
print(f"Success rate: {sum(success_flags)}/{len(success_flags)}")
# All successful solutions satisfy dynamic constraints
Visualization Demo
The demo script visualizes joint limit tables in real-time:
python examples/differential_wrist_joint_limit_demo.py
Move the joint sliders or drag the end-effector gizmo to see how the valid region changes dynamically.
Key Concepts
Dynamic min_angle and max_angle
When a joint has a limit table, its min_angle and max_angle properties
become dynamic:
# Without limit table: fixed values
print(joint.min_angle) # Always returns same value
# With limit table: depends on target joint
robot.WRIST_JOINT_Y.joint_angle(0)
print(robot.WRIST_JOINT_R.min_angle) # e.g., -50 degrees
robot.WRIST_JOINT_Y.joint_angle(1.0)
print(robot.WRIST_JOINT_R.min_angle) # e.g., -30 degrees
Bidirectional Constraints
In differential mechanisms, constraints are often bidirectional:
Moving WRIST_Y changes WRIST_R’s limits
Moving WRIST_R changes WRIST_Y’s limits
Both directions should be defined for proper constraint handling.
JointLimitTable Class
Access the underlying table object:
table = robot.WRIST_JOINT_R.joint_min_max_table
# Get limits at a specific target angle
min_val = table.min_angle_function(target_angle)
max_val = table.max_angle_function(target_angle)
# Access raw data
print(table.sample_angles) # Target joint sample points
print(table.min_angles) # Min limits at each sample
print(table.max_angles) # Max limits at each sample
API Reference
Creating Joint Limit Tables
- skrobot.model.create_joint_limit_table(robot, target_joint, dependent_joint, target_min_angle, target_max_angle, min_angles, max_angles, use_degrees=True)[source]
Create and apply a joint limit table from arrays.
This is the recommended way to create joint limit tables. It accepts numpy arrays or lists directly.
- Parameters:
robot (RobotModel) – The robot model to apply the joint limit table to.
target_joint (Joint or str) – The joint whose angle determines the limits. Can be a Joint object or a joint name string.
dependent_joint (Joint or str) – The joint whose limits depend on the target joint. Can be a Joint object or a joint name string.
target_min_angle (float) – Minimum angle of the target joint.
target_max_angle (float) – Maximum angle of the target joint.
min_angles (array-like) – Array of minimum angles for the dependent joint at each sample point.
max_angles (array-like) – Array of maximum angles for the dependent joint at each sample point.
use_degrees (bool) – If True (default), angles are in degrees. If False, angles are in radians.
- Returns:
The created joint limit table object.
- Return type:
- Raises:
ValueError – If joints are not found or array lengths don’t match.
Examples
>>> # Using degrees (default) >>> table = create_joint_limit_table( ... robot, ... target_joint='RARM_JOINT5', ... dependent_joint='RARM_JOINT6', ... target_min_angle=-90, ... target_max_angle=90, ... min_angles=[-10, -15, -20, -15, -10], ... max_angles=[10, 15, 20, 15, 10] ... )
>>> # Using radians >>> import numpy as np >>> table = create_joint_limit_table( ... robot, ... target_joint='JOINT5', ... dependent_joint='JOINT6', ... target_min_angle=-np.pi/2, ... target_max_angle=np.pi/2, ... min_angles=np.deg2rad([-10, -15, -20]), ... max_angles=np.deg2rad([10, 15, 20]), ... use_degrees=False ... )
JointLimitTable Class
- class skrobot.model.JointLimitTable(target_joint, target_min_angle, target_max_angle, min_angles, max_angles)[source]
Dynamic joint limit table based on another joint’s angle.
This class represents a joint limit table where joint B’s min/max angles depend on joint A’s current angle. The table uses linear interpolation between the discrete angle samples.
- Parameters:
target_joint (Joint) – The joint whose angle determines the limits (joint A).
target_min_angle (float) – Minimum angle of the target joint (in radians).
target_max_angle (float) – Maximum angle of the target joint (in radians).
min_angles (numpy.ndarray) – Array of minimum angles for the dependent joint at each sample point.
max_angles (numpy.ndarray) – Array of maximum angles for the dependent joint at each sample point.
Examples
>>> table = JointLimitTable(target_joint, -np.pi/2, np.pi/2, min_arr, max_arr) >>> current_min = table.min_angle_function(target_joint.joint_angle()) >>> current_max = table.max_angle_function(target_joint.joint_angle())
- get_data_for_differentiable()[source]
Get data for differentiable IK solvers.
- Returns:
Dictionary with keys: - ‘sample_angles’: Array of sample angles for interpolation - ‘min_angles’: Array of min limits at each sample - ‘max_angles’: Array of max limits at each sample
- Return type:
Source Code
Full source: https://github.com/iory/scikit-robot/blob/main/examples/differential_wrist_joint_limit_demo.py