Skip to content

Commit a55de6b

Browse files
authored
Merge pull request #430 from zswang666/feature/user_defined_ik_chain
[feature] support custom ik chain
2 parents 3aaf87b + 5ef465f commit a55de6b

File tree

2 files changed

+144
-11
lines changed

2 files changed

+144
-11
lines changed

examples/rigid/ik_custom_chain.py

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import argparse
2+
3+
import numpy as np
4+
5+
import genesis as gs
6+
7+
8+
def main():
9+
parser = argparse.ArgumentParser()
10+
parser.add_argument("-v", "--vis", action="store_true", default=False)
11+
args = parser.parse_args()
12+
13+
########################## init ##########################
14+
gs.init(seed=0, precision="32", logging_level="debug")
15+
16+
########################## create a scene ##########################
17+
scene = gs.Scene(
18+
viewer_options=gs.options.ViewerOptions(
19+
camera_pos=(2.5, 0.0, 1.5),
20+
camera_lookat=(0.0, 0.0, 0.5),
21+
camera_fov=40,
22+
),
23+
show_viewer=args.vis,
24+
rigid_options=gs.options.RigidOptions(
25+
gravity=(0, 0, 0),
26+
enable_collision=False,
27+
enable_joint_limit=False,
28+
),
29+
)
30+
31+
target_1 = scene.add_entity(
32+
gs.morphs.Mesh(
33+
file="meshes/axis.obj",
34+
scale=0.05,
35+
),
36+
surface=gs.surfaces.Default(color=(1, 0.5, 0.5, 1)),
37+
)
38+
39+
########################## entities ##########################
40+
robot = scene.add_entity(
41+
morph=gs.morphs.URDF(
42+
scale=1.0,
43+
file="urdf/shadow_hand/shadow_hand.urdf",
44+
),
45+
surface=gs.surfaces.Reflective(color=(0.4, 0.4, 0.4)),
46+
)
47+
48+
########################## build ##########################
49+
scene.build()
50+
scene.reset()
51+
52+
target_quat = np.array([1, 0, 0, 0])
53+
index_finger_distal = robot.get_link("index_finger_distal")
54+
55+
dofs_idx_local = []
56+
for v in robot.joints:
57+
if v.name in [
58+
"wrist_joint",
59+
"index_finger_joint1",
60+
"index_finger_join2",
61+
"index_finger_joint3",
62+
]:
63+
dof_idx_local_v = v.dof_idx_local
64+
if isinstance(dof_idx_local_v, list):
65+
dofs_idx_local.extend(dof_idx_local_v)
66+
else:
67+
assert isinstance(dof_idx_local_v, int)
68+
dofs_idx_local.append(dof_idx_local_v)
69+
70+
center = np.array([0.033, -0.01, 0.42])
71+
r1 = 0.05
72+
73+
for i in range(2000):
74+
index_finger_pos = center + np.array([0, np.cos(i / 90 * np.pi) - 1.0, np.sin(i / 90 * np.pi) - 1.0]) * r1
75+
76+
target_1.set_qpos(np.concatenate([index_finger_pos, target_quat]))
77+
78+
qpos = robot.inverse_kinematics_multilink(
79+
links=[index_finger_distal], # IK targets
80+
poss=[index_finger_pos],
81+
dofs_idx_local=dofs_idx_local, # IK wrt these dofs
82+
)
83+
84+
robot.set_qpos(qpos)
85+
scene.step()
86+
87+
88+
if __name__ == "__main__":
89+
main()

genesis/engine/entities/rigid_entity/rigid_entity.py

+55-11
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,7 @@ def inverse_kinematics(
738738
pos_mask=[True, True, True],
739739
rot_mask=[True, True, True],
740740
max_step_size=0.5,
741+
dofs_idx_local=None,
741742
return_error=False,
742743
):
743744
"""
@@ -771,6 +772,8 @@ def inverse_kinematics(
771772
Mask for rotation axis alignment. Defaults to [True, True, True]. E.g.: If you only want the link's Z-axis to be aligned with the Z-axis in the given quat, you can set it to [False, False, True].
772773
max_step_size : float, optional
773774
Maximum step size in q space for each IK solver step. Defaults to 0.5.
775+
dofs_idx_local : None | array_like, optional
776+
The indices of the dofs to set. If None, all dofs will be set. Note that here this uses the local `q_idx`, not the scene-level one. Defaults to None. This is used to specify which dofs the IK is applied to.
774777
return_error : bool, optional
775778
Whether to return the final errorqpos. Defaults to False.
776779
@@ -803,6 +806,7 @@ def inverse_kinematics(
803806
pos_mask=pos_mask,
804807
rot_mask=rot_mask,
805808
max_step_size=max_step_size,
809+
dofs_idx_local=dofs_idx_local,
806810
return_error=return_error,
807811
)
808812

@@ -830,6 +834,7 @@ def inverse_kinematics_multilink(
830834
pos_mask=[True, True, True],
831835
rot_mask=[True, True, True],
832836
max_step_size=0.5,
837+
dofs_idx_local=None,
833838
return_error=False,
834839
):
835840
"""
@@ -863,6 +868,8 @@ def inverse_kinematics_multilink(
863868
Mask for rotation axis alignment. Defaults to [True, True, True]. E.g.: If you only want the link's Z-axis to be aligned with the Z-axis in the given quat, you can set it to [False, False, True].
864869
max_step_size : float, optional
865870
Maximum step size in q space for each IK solver step. Defaults to 0.5.
871+
dofs_idx_local : None | array_like, optional
872+
The indices of the dofs to set. If None, all dofs will be set. Note that here this uses the local `q_idx`, not the scene-level one. Defaults to None. This is used to specify which dofs the IK is applied to.
866873
return_error : bool, optional
867874
Whether to return the final errorqpos. Defaults to False.
868875
@@ -949,11 +956,33 @@ def inverse_kinematics_multilink(
949956
[self._solver._process_dim(torch.as_tensor(quat, dtype=gs.tc_float, device=gs.device)) for quat in quats]
950957
)
951958

959+
dofs_idx = self._get_dofs_idx_local(dofs_idx_local)
960+
n_dofs = len(dofs_idx)
961+
if n_dofs == 0:
962+
gs.raise_exception("Target dofs not provided.")
963+
links_idx_by_dofs = []
964+
for v in self.links:
965+
links_idx_by_dof_at_v = v.joint.dof_idx_local
966+
if links_idx_by_dof_at_v is None:
967+
link_relevant = False
968+
elif isinstance(links_idx_by_dof_at_v, list):
969+
link_relevant = any([vv in dofs_idx for vv in links_idx_by_dof_at_v])
970+
else:
971+
link_relevant = links_idx_by_dof_at_v in dofs_idx
972+
if link_relevant:
973+
links_idx_by_dofs.append(v.idx_local) # converted to global later
974+
links_idx_by_dofs = self._get_ls_idx(links_idx_by_dofs)
975+
n_links_by_dofs = len(links_idx_by_dofs)
976+
952977
self._kernel_inverse_kinematics(
953978
links_idx,
954979
poss,
955980
quats,
956981
n_links,
982+
dofs_idx,
983+
n_dofs,
984+
links_idx_by_dofs,
985+
n_links_by_dofs,
957986
custom_init_qpos,
958987
init_qpos,
959988
max_samples,
@@ -990,6 +1019,10 @@ def _kernel_inverse_kinematics(
9901019
poss: ti.types.ndarray(),
9911020
quats: ti.types.ndarray(),
9921021
n_links: ti.i32,
1022+
dofs_idx: ti.types.ndarray(),
1023+
n_dofs: ti.i32,
1024+
links_idx_by_dofs: ti.types.ndarray(),
1025+
n_links_by_dofs: ti.i32,
9931026
custom_init_qpos: ti.i32,
9941027
init_qpos: ti.types.ndarray(),
9951028
max_samples: ti.i32,
@@ -1063,30 +1096,40 @@ def _kernel_inverse_kinematics(
10631096
for i_ee in range(n_links):
10641097
# update jacobian for ee link
10651098
i_l_ee = links_idx[i_ee]
1066-
self._func_get_jacobian(i_l_ee, i_b, pos_mask, rot_mask)
1099+
self._func_get_jacobian(
1100+
i_l_ee, i_b, pos_mask, rot_mask
1101+
) # NOTE: we still compute jacobian for all dofs as we haven't found a clean way to implement this
10671102

1068-
# copy to multi-link jacobian
1069-
for i_error, i_dof in ti.ndrange(6, self.n_dofs):
1103+
# copy to multi-link jacobian (only for the effective n_dofs instead of self.n_dofs)
1104+
for i_error, i_dof in ti.ndrange(6, n_dofs):
10701105
i_row = i_ee * 6 + i_error
1071-
self._IK_jacobian[i_row, i_dof, i_b] = self._jacobian[i_error, i_dof, i_b]
1106+
i_dof_ = dofs_idx[i_dof]
1107+
self._IK_jacobian[i_row, i_dof, i_b] = self._jacobian[i_error, i_dof_, i_b]
10721108

1073-
# compute dq = jac.T @ inverse(jac @ jac.T + diag) @ error
1074-
lu.mat_transpose(self._IK_jacobian, self._IK_jacobian_T, n_error_dims, self.n_dofs, i_b)
1109+
# compute dq = jac.T @ inverse(jac @ jac.T + diag) @ error (only for the effective n_dofs instead of self.n_dofs)
1110+
lu.mat_transpose(self._IK_jacobian, self._IK_jacobian_T, n_error_dims, n_dofs, i_b)
10751111
lu.mat_mul(
10761112
self._IK_jacobian,
10771113
self._IK_jacobian_T,
10781114
self._IK_mat,
10791115
n_error_dims,
1080-
self.n_dofs,
1116+
n_dofs,
10811117
n_error_dims,
10821118
i_b,
10831119
)
10841120
lu.mat_add_eye(self._IK_mat, damping**2, n_error_dims, i_b)
10851121
lu.mat_inverse(self._IK_mat, self._IK_L, self._IK_U, self._IK_y, self._IK_inv, n_error_dims, i_b)
10861122
lu.mat_mul_vec(self._IK_inv, self._IK_err_pose, self._IK_vec, n_error_dims, n_error_dims, i_b)
1087-
lu.mat_mul_vec(
1088-
self._IK_jacobian_T, self._IK_vec, self._IK_delta_qpos, self.n_dofs, n_error_dims, i_b
1089-
)
1123+
1124+
for i in range(self.n_dofs): # IK_delta_qpos = IK_jacobian_T @ IK_vec
1125+
self._IK_delta_qpos[i, i_b] = 0
1126+
for i in range(n_dofs):
1127+
for j in range(n_error_dims):
1128+
i_ = dofs_idx[
1129+
i
1130+
] # NOTE: IK_delta_qpos uses the original indexing instead of the effective n_dofs
1131+
self._IK_delta_qpos[i_, i_b] += self._IK_jacobian_T[i, j, i_b] * self._IK_vec[j, i_b]
1132+
10901133
for i in range(self.n_dofs):
10911134
self._IK_delta_qpos[i, i_b] = ti.math.clamp(
10921135
self._IK_delta_qpos[i, i_b], -max_step_size, max_step_size
@@ -1159,7 +1202,8 @@ def _kernel_inverse_kinematics(
11591202

11601203
# Resample init q
11611204
if respect_joint_limit and i_sample < max_samples - 1:
1162-
for i_l in range(self.link_start, self.link_end):
1205+
for _i_l in range(n_links_by_dofs):
1206+
i_l = links_idx_by_dofs[_i_l]
11631207
I_l = [i_l, i_b] if ti.static(self.solver._options.batch_links_info) else i_l
11641208
l_info = self._solver.links_info[I_l]
11651209
I_dof_start = (

0 commit comments

Comments
 (0)