Skip to content

Commit 16614d5

Browse files
authored
test: pinoccio kinematics (#232)
- add test for the pinocchio forward and inverse kinematics - fixes mjcf issue with panda robot
1 parent bbc1237 commit 16614d5

File tree

5 files changed

+118
-22
lines changed

5 files changed

+118
-22
lines changed

.clang-tidy

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Checks: 'clang-diagnostic-*,clang-analyzer-*,bug-prone-*,-clang-diagnostic-ignored-optimization-argument'
33
WarningsAsErrors: ''
44
HeaderFilterRegex: 'src/*.h'
5-
AnalyzeTemporaryDtors: false
65
FormatStyle: google
76
CheckOptions:
87
- key: llvm-else-after-return.WarnOnConditionVariables

assets/panda/mjcf/panda_0.xml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
<mujoco model="panda">
2+
<default>
3+
<default class="panda">
4+
<material specular="0.5" shininess="0.25"/>
5+
<joint armature="0.1" damping="1" axis="0 0 1" range="-2.8973 2.8973"/>
6+
<general dyntype="none" biastype="affine" ctrlrange="-2.8973 2.8973" forcerange="-87 87"/>
7+
<default class="finger">
8+
<joint axis="0 1 0" type="slide" range="0 0.04"/>
9+
</default>
10+
11+
<default class="panda/visual">
12+
<geom type="mesh" contype="0" conaffinity="0" group="2"/>
13+
</default>
14+
<default class="panda/collision">
15+
<geom type="mesh" group="3"/>
16+
</default>
17+
<site size="0.001" rgba="0.5 0.5 0.5 0.3" group="4"/>
18+
</default>
19+
</default>
220
<worldbody>
321
<light name="top_0" pos="0 0 2" mode="trackcom"/>
422
<body name="link0_0" childclass="panda">
@@ -91,9 +109,9 @@
91109
<geom mesh="link7_6" material="dark_grey" class="panda/visual"/>
92110
<geom mesh="link7_7" material="white" class="panda/visual"/>
93111
<geom mesh="link7_c" class="panda/collision"/>
94-
<body name="attachment_0" pos="0 0 0.107" quat="0.3826834 0 0 0.9238795">
95-
<site name="attachment_site_0"/>
96-
</body>
112+
<site name="attachment_site_0" pos="0 0 0.107" quat="0.3826834 0 0 0.9238795" />
113+
<!-- <site name="attachment_site_0"/>
114+
</body> -->
97115
</body>
98116
</body>
99117
</body>

assets/panda/mjcf/panda_common.xml

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,6 @@
33

44
<option integrator="implicitfast" impratio="10"/>
55

6-
<default>
7-
<default class="panda">
8-
<material specular="0.5" shininess="0.25"/>
9-
<joint armature="0.1" damping="1" axis="0 0 1" range="-2.8973 2.8973"/>
10-
<general dyntype="none" biastype="affine" ctrlrange="-2.8973 2.8973" forcerange="-87 87"/>
11-
<default class="finger">
12-
<joint axis="0 1 0" type="slide" range="0 0.04"/>
13-
</default>
14-
15-
<default class="panda/visual">
16-
<geom type="mesh" contype="0" conaffinity="0" group="2"/>
17-
</default>
18-
<default class="panda/collision">
19-
<geom type="mesh" group="3"/>
20-
</default>
21-
<site size="0.001" rgba="0.5 0.5 0.5 0.3" group="4"/>
22-
</default>
23-
</default>
246

257
<asset>
268
<material class="panda" name="white" rgba="1 1 1 1"/>

assets/panda/mjcf/panda_unnamed.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,22 @@
11
<mujoco model="panda">
2+
<default>
3+
<default class="panda">
4+
<material specular="0.5" shininess="0.25"/>
5+
<joint armature="0.1" damping="1" axis="0 0 1" range="-2.8973 2.8973"/>
6+
<general dyntype="none" biastype="affine" ctrlrange="-2.8973 2.8973" forcerange="-87 87"/>
7+
<default class="finger">
8+
<joint axis="0 1 0" type="slide" range="0 0.04"/>
9+
</default>
10+
11+
<default class="panda/visual">
12+
<geom type="mesh" contype="0" conaffinity="0" group="2"/>
13+
</default>
14+
<default class="panda/collision">
15+
<geom type="mesh" group="3"/>
16+
</default>
17+
<site size="0.001" rgba="0.5 0.5 0.5 0.3" group="4"/>
18+
</default>
19+
</default>
220
<worldbody>
321
<light name="top" pos="0 0 2" mode="trackcom"/>
422
<body name="link0" childclass="panda">

python/tests/test_kinematics.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import numpy as np
2+
import pytest
3+
4+
import rcs
5+
from rcs import common
6+
7+
# Map robot types to their end-effector frame names
8+
ROBOT_FRAME_IDS = {
9+
common.RobotType.FR3: "attachment_site_0",
10+
common.RobotType.Panda: "attachment_site_0",
11+
common.RobotType.XArm7: "attachment_site",
12+
common.RobotType.SO101: "gripper",
13+
common.RobotType.UR5e: "attachment_site",
14+
}
15+
16+
17+
# only for scene that have empty_world in key
18+
@pytest.mark.parametrize("scene_name", [k for k in rcs.scenes if "empty_world" in k])
19+
def test_kinematics_identity(scene_name):
20+
scene = rcs.scenes[scene_name]
21+
22+
# Determine model path and type
23+
model_path = scene.mjcf_robot
24+
25+
# Get frame ID
26+
if scene.robot_type not in ROBOT_FRAME_IDS:
27+
pytest.skip(f"Frame ID not defined for robot type {scene.robot_type}")
28+
29+
frame_id = ROBOT_FRAME_IDS[scene.robot_type]
30+
31+
# Initialize Pinocchio interface
32+
try:
33+
pin = common.Pin(model_path, frame_id, False)
34+
except Exception as e:
35+
pytest.fail(f"Failed to initialize Pin for {scene_name}: {e}")
36+
37+
# Get home configuration
38+
try:
39+
meta_config = common.robots_meta_config(scene.robot_type)
40+
q_home = meta_config.q_home
41+
except Exception as e:
42+
pytest.fail(f"Failed to get meta config for {scene_name}: {e}")
43+
44+
# Test 1: FK at home
45+
# Identity pose (no TCP offset)
46+
tcp_offset = common.Pose()
47+
48+
pose_home = pin.forward(q_home, tcp_offset)
49+
assert isinstance(pose_home, common.Pose)
50+
51+
# Test 2: IK at home pose should return a solution (ideally close to q_home, but IK is redundant)
52+
# We use q_home as initial guess
53+
q_sol = pin.inverse(pose_home, q_home, tcp_offset)
54+
55+
assert q_sol is not None, "IK failed for home pose"
56+
57+
# Verify the solution with FK
58+
pose_sol = pin.forward(q_sol, tcp_offset)
59+
60+
# Check if pose_sol is close to pose_home
61+
assert pose_sol.is_close(
62+
pose_home, eps_r=1e-4, eps_t=1e-4
63+
), f"FK(IK(pose)) does not match pose.\nOriginal: {pose_home}\nResult: {pose_sol}"
64+
65+
# Test 3: Perturbed configuration
66+
# Add small noise to q_home to test non-trivial pose
67+
# Ensure we stay within limits if possible, but for small noise it should be fine
68+
np.random.seed(42)
69+
q_perturbed = q_home + np.random.uniform(-0.1, 0.1, size=q_home.shape)
70+
71+
pose_perturbed = pin.forward(q_perturbed, tcp_offset) # type: ignore
72+
q_sol_perturbed = pin.inverse(pose_perturbed, q_home, tcp_offset) # Use q_home as seed
73+
74+
assert q_sol_perturbed is not None, "IK failed for perturbed pose"
75+
76+
pose_sol_perturbed = pin.forward(q_sol_perturbed, tcp_offset)
77+
assert pose_sol_perturbed.is_close(
78+
pose_perturbed, eps_r=1e-3, eps_t=1e-3
79+
), f"FK(IK(perturbed_pose)) does not match.\nOriginal: {pose_perturbed}\nResult: {pose_sol_perturbed}"

0 commit comments

Comments
 (0)