11"""
22Code for kinematics utilities on CPU/GPU
33"""
4+
45from contextlib import contextmanager , redirect_stderr , redirect_stdout
56from os import devnull
67from typing import List
1213 "pytorch_kinematics_ms not installed. Install with pip install pytorch_kinematics_ms"
1314 )
1415import torch
16+ from lxml import etree as ET
1517from sapien .wrapper .pinocchio_model import PinocchioModel
1618
1719from mani_skill .utils import common
@@ -44,12 +46,18 @@ def __init__(
4446 articulation (Articulation): the articulation object
4547 active_joint_indices (torch.Tensor): indices of the active joints that can be controlled
4648 """
49+
50+ # NOTE (arth): urdf path with feasible kinematic chain. may not be same urdf used to
51+ # build the sapien articulation (e.g. sapien articulation may have added joints for
52+ # mobile base which should not be used in IK)
4753 self .urdf_path = urdf_path
4854 self .end_link = articulation .links_map [end_link_name ]
49- self .end_link_idx = articulation .links .index (self .end_link )
50- self .active_joint_indices = active_joint_indices
55+
5156 self .articulation = articulation
5257 self .device = articulation .device
58+
59+ self .active_joint_indices = active_joint_indices
60+
5361 # note that everything past the end-link is ignored. Any joint whose ancestor is self.end_link is ignored
5462 cur_link = self .end_link
5563 active_ancestor_joints : List [ArticulationJoint ] = []
@@ -58,17 +66,25 @@ def __init__(
5866 active_ancestor_joints .append (cur_link .joint )
5967 cur_link = cur_link .joint .parent_link
6068 active_ancestor_joints = active_ancestor_joints [::- 1 ]
61- self .active_ancestor_joints = active_ancestor_joints
6269
63- # initially self.active_joint_indices references active joints that are controlled.
64- # we also make the assumption that the active index is the same across all parallel managed joints
65- self .active_ancestor_joint_idxs = [
66- (x .active_index [0 ]).cpu ().item () for x in self .active_ancestor_joints
67- ]
68- self .controlled_joints_idx_in_qmask = [
69- self .active_ancestor_joint_idxs .index (idx )
70- for idx in self .active_joint_indices
70+ # NOTE (arth): some robots, like Fetch, have dummy joints that can mess with IK solver.
71+ # we assume that the urdf_path provides a valid kinematic chain, and prune joints
72+ # which are in the ManiSkill articulation but not in the kinematic chain
73+ with open (self .urdf_path , "r" ) as f :
74+ urdf_string = f .read ()
75+ xml = ET .fromstring (urdf_string .encode ("utf-8" ))
76+ self ._kinematic_chain_joint_names = set (
77+ node .get ("name" ) for node in xml if node .tag == "joint"
78+ )
79+ self ._kinematic_chain_link_names = set (
80+ node .get ("name" ) for node in xml if node .tag == "link"
81+ )
82+ self .active_ancestor_joints = [
83+ x
84+ for x in active_ancestor_joints
85+ if x .name in self ._kinematic_chain_joint_names
7186 ]
87+
7288 if self .device .type == "cuda" :
7389 self .use_gpu_ik = True
7490 self ._setup_gpu ()
@@ -79,14 +95,47 @@ def __init__(
7995 def _setup_cpu (self ):
8096 """setup the kinematics solvers on the CPU"""
8197 self .use_gpu_ik = False
82- # NOTE (stao): currently using the pinnochio that comes packaged with SAPIEN
83- self .qmask = torch .zeros (
84- self .articulation .max_dof , dtype = bool , device = self .device
98+
99+ with open (self .urdf_path , "r" ) as f :
100+ xml = f .read ()
101+
102+ joint_order = [
103+ j .name
104+ for j in self .articulation .active_joints
105+ if j .name in self ._kinematic_chain_joint_names
106+ ]
107+ link_order = [
108+ l .name
109+ for l in self .articulation .links
110+ if l .name in self ._kinematic_chain_link_names
111+ ]
112+
113+ self .pmodel = PinocchioModel (xml , [0 , 0 , - 9.81 ])
114+ self .pmodel .set_joint_order (joint_order )
115+ self .pmodel .set_link_order (link_order )
116+
117+ controlled_joint_names = [
118+ self .articulation .active_joints [i ].name for i in self .active_joint_indices
119+ ]
120+ self .pmodel_controlled_joint_indices = torch .tensor (
121+ [joint_order .index (cj ) for cj in controlled_joint_names ],
122+ dtype = torch .int ,
123+ device = self .device ,
85124 )
86- self .pmodel : PinocchioModel = self .articulation ._objs [
87- 0
88- ].create_pinocchio_model ()
89- self .qmask [self .active_joint_indices ] = 1
125+
126+ articulation_active_joint_names_to_idx = dict (
127+ (j .name , i ) for i , j in enumerate (self .articulation .active_joints )
128+ )
129+ self .pmodel_active_joint_indices = torch .tensor (
130+ [articulation_active_joint_names_to_idx [jn ] for jn in joint_order ],
131+ dtype = torch .int ,
132+ device = self .device ,
133+ )
134+
135+ # NOTE (arth): pmodel will use urdf_path, set values based on this xml
136+ self .end_link_idx = link_order .index (self .end_link .name )
137+ self .qmask = torch .zeros (len (joint_order ), dtype = bool , device = self .device )
138+ self .qmask [self .pmodel_controlled_joint_indices ] = 1
90139
91140 def _setup_gpu (self ):
92141 """setup the kinematics solvers on the GPU"""
@@ -116,6 +165,16 @@ def suppress_stdout_stderr():
116165 num_retries = 1 ,
117166 )
118167
168+ # initially self.active_joint_indices references active joints that are controlled.
169+ # we also make the assumption that the active index is the same across all parallel managed joints
170+ self .active_ancestor_joint_idxs = [
171+ (x .active_index [0 ]).cpu ().item () for x in self .active_ancestor_joints
172+ ]
173+ self .controlled_joints_idx_in_qmask = [
174+ self .active_ancestor_joint_idxs .index (idx )
175+ for idx in self .active_joint_indices
176+ ]
177+
119178 self .qmask = torch .zeros (
120179 len (self .active_ancestor_joints ), dtype = bool , device = self .device
121180 )
@@ -167,10 +226,15 @@ def compute_ik(
167226 if pos_only :
168227 jacobian = jacobian [:, 0 :3 ]
169228
229+ # NOTE (arth): use only the parts of the jacobian that correspond to the active joints
230+ jacobian = jacobian [:, :, self .qmask ]
231+
170232 # NOTE (stao): this method of IK is from https://mathweb.ucsd.edu/~sbuss/ResearchWeb/ikmethods/iksurvey.pdf by Samuel R. Buss
171233 delta_joint_pos = torch .linalg .pinv (jacobian ) @ action .unsqueeze (- 1 )
172- return q0 + delta_joint_pos .squeeze (- 1 )
234+
235+ return q0 [:, self .qmask ] + delta_joint_pos .squeeze (- 1 )
173236 else :
237+ q0 = q0 [:, self .pmodel_active_joint_indices ]
174238 result , success , error = self .pmodel .compute_inverse_kinematics (
175239 self .end_link_idx ,
176240 target_pose .sp ,
@@ -180,7 +244,7 @@ def compute_ik(
180244 )
181245 if success :
182246 return common .to_tensor (
183- [result [self .active_ancestor_joint_idxs ]], device = self .device
247+ [result [self .pmodel_controlled_joint_indices ]], device = self .device
184248 )
185249 else :
186250 return None
0 commit comments