@@ -980,7 +980,44 @@ def inverse_kinematics(
980980 check_collision = None ,
981981 additional_jacobi = None ,
982982 additional_vel = None ,
983+ translation_tolerance = None ,
984+ rotation_tolerance = None ,
983985 ** kwargs ):
986+ """Solve inverse kinematics to reach target coordinates.
987+
988+ Parameters
989+ ----------
990+ target_coords : skrobot.coordinates.Coordinates or list
991+ Target coordinate(s) for the end-effector(s).
992+ stop : int
993+ Maximum number of iterations.
994+ link_list : list
995+ List of links to use for IK.
996+ move_target : skrobot.coordinates.Coordinates or list
997+ End-effector coordinate frame(s).
998+ revert_if_fail : bool
999+ If True, revert to initial joint angles on failure.
1000+ rotation_axis : bool, str, or list
1001+ Which rotation axes to constrain. True=all, False=none,
1002+ 'x'/'y'/'z'=single axis, 'xy'/'yz'/'zx'=two axes.
1003+ translation_axis : bool, str, or list
1004+ Which translation axes to constrain. Same format as rotation_axis.
1005+ translation_tolerance : list or None
1006+ Per-axis position tolerance from target as [x_tol, y_tol, z_tol]
1007+ in meters. If error on an axis is within tolerance, it's treated
1008+ as reached. Use None for no tolerance on an axis.
1009+ rotation_tolerance : list or None
1010+ Per-axis rotation tolerance from target as
1011+ [roll_tol, pitch_tol, yaw_tol] in radians. If error on an axis
1012+ is within tolerance, it's treated as reached.
1013+ **kwargs
1014+ Additional keyword arguments passed to inverse_kinematics_loop.
1015+
1016+ Returns
1017+ -------
1018+ numpy.ndarray or False
1019+ Joint angle vector if successful, False otherwise.
1020+ """
9841021 additional_jacobi = additional_jacobi or []
9851022 additional_vel = additional_vel or []
9861023 target_coords = listify (target_coords )
@@ -1079,11 +1116,37 @@ def inverse_kinematics(
10791116 dif_pos = list (map (lambda mv , tc , trans_axis :
10801117 mv .difference_position (
10811118 tc , translation_axis = trans_axis ),
1082- move_target , target_coords , translation_axis ))
1119+ move_target , target_coords ,
1120+ translation_axis ))
10831121 dif_rot = list (map (lambda mv , tc , rot_axis :
10841122 mv .difference_rotation (
10851123 tc , rotation_axis = rot_axis ),
1086- move_target , target_coords , rotation_axis ))
1124+ move_target , target_coords ,
1125+ rotation_axis ))
1126+
1127+ # Apply translation tolerance in target_coords' local frame
1128+ # Note: tol > 0 check ensures 0.0 means "no special tolerance"
1129+ if translation_tolerance is not None :
1130+ for i , (mt , tc ) in enumerate (zip (move_target , target_coords )):
1131+ # Calculate error in target_coords' local frame
1132+ world_error = tc .worldpos () - mt .worldpos ()
1133+ local_error = tc .worldrot ().T @ world_error
1134+ for axis_idx , tol in enumerate (translation_tolerance ):
1135+ if tol is not None and tol > 0 :
1136+ if abs (local_error [axis_idx ]) <= tol :
1137+ # Zero out the corresponding dif_pos component
1138+ # dif_pos is in move_target's frame, so transform
1139+ target_axis_in_mt = mt .worldrot ().T @ tc .worldrot ()[:, axis_idx ]
1140+ dif_pos [i ] -= (dif_pos [i ] @ target_axis_in_mt ) * target_axis_in_mt
1141+
1142+ # Apply rotation tolerance: if error is within tolerance,
1143+ # treat as reached (set dif to 0)
1144+ if rotation_tolerance is not None :
1145+ for i , dr in enumerate (dif_rot ):
1146+ for axis_idx , tol in enumerate (rotation_tolerance ):
1147+ if tol is not None and tol > 0 and abs (dr [axis_idx ]) <= tol :
1148+ dif_rot [i ][axis_idx ] = 0.0
1149+
10871150 if loop == 1 and self .ik_convergence_check (
10881151 dif_pos , dif_rot , rotation_axis , translation_axis ,
10891152 thre , rthre , centroid_thre , target_centroid_pos ,
@@ -2434,6 +2497,8 @@ def batch_inverse_kinematics(
24342497 alpha = 1.0 ,
24352498 attempts_per_pose = 1 ,
24362499 random_initial_range = 0.7 ,
2500+ translation_tolerance = None ,
2501+ rotation_tolerance = None ,
24372502 ** kwargs ):
24382503 """Solve batch inverse kinematics for multiple target poses.
24392504
@@ -2474,6 +2539,14 @@ def batch_inverse_kinematics(
24742539 Number of attempts with different random initial poses per target (default: 1)
24752540 random_initial_range : float
24762541 Range for random initial poses as fraction of joint limits (0.0-1.0, default: 0.7)
2542+ translation_tolerance : list or None
2543+ Per-axis position tolerance from target as [x_tol, y_tol, z_tol]
2544+ in meters. If error on an axis is within tolerance, it's treated
2545+ as reached. Use None for no tolerance on an axis.
2546+ rotation_tolerance : list or None
2547+ Per-axis rotation tolerance from target as
2548+ [roll_tol, pitch_tol, yaw_tol] in radians. If error on an axis
2549+ is within tolerance, it's treated as reached.
24772550 **kwargs : dict
24782551 Additional keyword arguments
24792552
@@ -2506,12 +2579,14 @@ def batch_inverse_kinematics(
25062579 return self ._batch_inverse_kinematics_impl (
25072580 target_coords , move_target , link_list ,
25082581 rotation_axis , translation_axis , stop , thre , rthre ,
2509- initial_angles , alpha , attempts_per_pose , random_initial_range , ** kwargs )
2582+ initial_angles , alpha , attempts_per_pose , random_initial_range ,
2583+ translation_tolerance , rotation_tolerance , ** kwargs )
25102584
25112585 def _batch_inverse_kinematics_impl (
25122586 self , target_coords , move_target , link_list ,
25132587 rotation_axis , translation_axis , stop , thre , rthre ,
2514- initial_angles , alpha , attempts_per_pose , random_initial_range , ** kwargs ):
2588+ initial_angles , alpha , attempts_per_pose , random_initial_range ,
2589+ translation_tolerance , rotation_tolerance , ** kwargs ):
25152590 """Internal implementation of batch inverse kinematics."""
25162591 # Auto-adjust initial_angles based on attempts_per_pose
25172592 if isinstance (initial_angles , str ) and initial_angles == "current" and attempts_per_pose > 1 :
@@ -2635,7 +2710,8 @@ def _batch_inverse_kinematics_impl(
26352710 stop , thre , rthre , alpha , base_to_source ,
26362711 rotation_axis , translation_axis , actual_joint_list ,
26372712 attempts_per_pose , random_initial_range ,
2638- move_target_offset_pos , move_target_offset_rot
2713+ move_target_offset_pos , move_target_offset_rot ,
2714+ translation_tolerance , rotation_tolerance
26392715 )
26402716
26412717 full_solutions = []
@@ -2655,7 +2731,8 @@ def _solve_batch_ik(
26552731 stop , thre , rthre , alpha , base_to_source ,
26562732 rotation_axis , translation_axis , joint_list_without_fixed ,
26572733 attempts_per_pose , random_initial_range ,
2658- move_target_offset_pos = None , move_target_offset_rot = None ):
2734+ move_target_offset_pos = None , move_target_offset_rot = None ,
2735+ translation_tolerance = None , rotation_tolerance = None ):
26592736 """Batch IK solver using batch expansion for multiple attempts."""
26602737
26612738 n_poses = target_poses_xyz_qwxyz .shape [0 ]
@@ -2704,7 +2781,8 @@ def _solve_batch_ik(
27042781 stop , thre , rthre , alpha , base_to_source ,
27052782 expanded_rotation_axis , expanded_translation_axis ,
27062783 joint_list_without_fixed , min_angles , max_angles ,
2707- move_target_offset_pos , move_target_offset_rot
2784+ move_target_offset_pos , move_target_offset_rot ,
2785+ translation_tolerance , rotation_tolerance
27082786 )
27092787
27102788 solutions = []
@@ -2740,7 +2818,8 @@ def _solve_batch_ik_internal(
27402818 stop , thre , rthre , alpha , base_to_source ,
27412819 rotation_axis , translation_axis ,
27422820 joint_list_without_fixed , min_angles , max_angles ,
2743- move_target_offset_pos = None , move_target_offset_rot = None ):
2821+ move_target_offset_pos = None , move_target_offset_rot = None ,
2822+ translation_tolerance = None , rotation_tolerance = None ):
27442823 """Internal batch IK solver."""
27452824 # Use integrated batch IK functions
27462825
@@ -2781,7 +2860,9 @@ def _solve_batch_ik_internal(
27812860 joint_list_without_fixed , min_angles , max_angles ,
27822861 joint_list_for_fk = None , # Will be derived from link_list in the function
27832862 move_target_offset_pos = move_target_offset_pos ,
2784- move_target_offset_rot = move_target_offset_rot
2863+ move_target_offset_rot = move_target_offset_rot ,
2864+ translation_tolerance = translation_tolerance ,
2865+ rotation_tolerance = rotation_tolerance
27852866 )
27862867
27872868 # Update current angles first
@@ -2805,8 +2886,12 @@ def _solve_batch_ik_internal(
28052886 pose_errors = np .zeros ((len (unsolved_indices ), 6 ))
28062887 translation_errors = target_poses_unsolved [:, :3 ] - current_poses_updated [:, :3 ]
28072888 pose_errors [:, :3 ] = translation_errors
2808- # Rotation errors (simplified - assume no rotation constraint for now)
2809- pose_errors [:, 3 :] = 0
2889+ # Calculate rotation errors using quaternion difference
2890+ current_quat_inv = quaternion_inverse (current_poses_updated [:, 3 :])
2891+ rotation_error_quat = quaternion_multiply (
2892+ target_poses_unsolved [:, 3 :], current_quat_inv )
2893+ rotation_error_rpy = quaternion2rpy (rotation_error_quat )[0 ][:, ::- 1 ]
2894+ pose_errors [:, 3 :] = rotation_error_rpy
28102895
28112896 # Check convergence with respect to constraints
28122897 converged = np .zeros (len (unsolved_indices ), dtype = bool )
@@ -2857,6 +2942,31 @@ def _solve_batch_ik_internal(
28572942 if 'z' not in rot_axis .lower ():
28582943 constrained_pose_error [5 ] = 0
28592944
2945+ # Apply translation tolerance in target's local frame
2946+ # Note: tol > 0 check ensures 0.0 means "no special tolerance"
2947+ if translation_tolerance is not None :
2948+ # Get target rotation matrix
2949+ target_rot = quaternion2matrix (target_poses_unsolved [i , 3 :])
2950+ # Calculate error in target's local frame
2951+ world_error = target_poses_unsolved [i , :3 ] - current_poses_updated [i , :3 ]
2952+ local_error = target_rot .T @ world_error
2953+ for axis_idx , tol in enumerate (translation_tolerance ):
2954+ if tol is not None and tol > 0 :
2955+ if abs (local_error [axis_idx ]) <= tol :
2956+ # Zero out this axis in constrained_pose_error
2957+ # by projecting out the target axis component
2958+ target_axis = target_rot [:, axis_idx ]
2959+ proj = (constrained_pose_error [:3 ] @ target_axis )
2960+ constrained_pose_error [:3 ] -= proj * target_axis
2961+
2962+ # Apply rotation tolerance: if error is within tolerance,
2963+ # treat as reached (set to 0)
2964+ if rotation_tolerance is not None :
2965+ for axis_idx , tol in enumerate (rotation_tolerance ):
2966+ if tol is not None and tol > 0 :
2967+ if abs (constrained_pose_error [3 + axis_idx ]) <= tol :
2968+ constrained_pose_error [3 + axis_idx ] = 0.0
2969+
28602970 # Check convergence only for active constraints
28612971 # Note: pose_errors is in [translation, rotation] order after reordering
28622972 position_error = np .linalg .norm (constrained_pose_error [:3 ])
@@ -2998,7 +3108,8 @@ def _batch_ik_step_with_constraints(
29983108 rotation_axis_list , translation_axis_list ,
29993109 actual_joint_list , min_angles , max_angles ,
30003110 joint_list_for_fk = None ,
3001- move_target_offset_pos = None , move_target_offset_rot = None ):
3111+ move_target_offset_pos = None , move_target_offset_rot = None ,
3112+ translation_tolerance = None , rotation_tolerance = None ):
30023113 """Batch IK step with per-problem axis constraints.
30033114
30043115 Args:
@@ -3140,6 +3251,32 @@ def _batch_ik_step_with_constraints(
31403251 pose_errors [:, 3 :] = rotation_error_rpy
31413252 pose_errors = pose_errors [:, [3 , 4 , 5 , 0 , 1 , 2 ]]
31423253
3254+ # Apply tolerance in target's local frame
3255+ # pose_errors is now in [rot, rot, rot, trans, trans, trans] order
3256+ # Note: tol > 0 check ensures 0.0 means "no special tolerance"
3257+ if translation_tolerance is not None :
3258+ for b in range (batch_size ):
3259+ # Get target rotation matrix
3260+ target_rot = quaternion2matrix (target_poses [b , 3 :])
3261+ # Calculate error in target's local frame
3262+ world_error = target_poses [b , :3 ] - current_poses [b , :3 ]
3263+ local_error = target_rot .T @ world_error
3264+ for axis_idx , tol in enumerate (translation_tolerance ):
3265+ if tol is not None and tol > 0 :
3266+ if abs (local_error [axis_idx ]) <= tol :
3267+ # Zero out this axis by projecting out the target axis
3268+ target_axis = target_rot [:, axis_idx ]
3269+ # Translation is at indices 3, 4, 5 after reordering
3270+ proj = pose_errors [b , 3 :6 ] @ target_axis
3271+ pose_errors [b , 3 :6 ] -= proj * target_axis
3272+
3273+ if rotation_tolerance is not None :
3274+ for axis_idx , tol in enumerate (rotation_tolerance ):
3275+ if tol is not None and tol > 0 :
3276+ # Rotation is at indices 0, 1, 2 after reordering
3277+ mask = np .abs (pose_errors [:, axis_idx ]) <= tol
3278+ pose_errors [mask , axis_idx ] = 0.0
3279+
31433280 # Apply constraints and calculate delta for each problem
31443281 joint_angle_deltas = np .zeros ((batch_size , ndof ))
31453282
0 commit comments