Skip to content

Commit e9b9788

Browse files
authored
Add comprehensive test cases for URDF root link changer (#568)
- Add test for fixed joints without axis elements - Add test for joint/link naming conflicts with mimic joints - Add test for None element handling during XML parsing - Add test for complex kinematic tree root link changes - Ensure robustness against various URDF structures
1 parent 833303d commit e9b9788

File tree

2 files changed

+224
-7
lines changed

2 files changed

+224
-7
lines changed

skrobot/urdf/xml_root_link_changer.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,18 @@ def _reverse_joint_transform(self, joint, prev_joint_xyz, prev_joint_rpy, path_t
318318

319319
# Invert axis direction
320320
axis = joint.find('axis')
321-
axis_xyz_str = axis.get('xyz', '0 0 0')
322-
inv_axis_xyz = [-float(x) for x in axis_xyz_str.split()]
323-
axis.set('xyz', ' '.join(map(str, inv_axis_xyz)))
321+
if axis is not None:
322+
axis_xyz_str = axis.get('xyz', '0 0 0')
323+
inv_axis_xyz = [-float(x) for x in axis_xyz_str.split()]
324+
axis.set('xyz', ' '.join(map(str, inv_axis_xyz)))
324325

325326
# Adjust visual and collision origin
326-
parent_name = joint.find('parent').get('link')
327+
parent_elem = joint.find('parent')
328+
if parent_elem is None:
329+
return
330+
parent_name = parent_elem.get('link')
331+
if parent_name not in self.links:
332+
return
327333
parent = self.links[parent_name]
328334
parent_visual = parent.find('visual')
329335
if parent_visual is not None:
@@ -395,6 +401,8 @@ def _matrix_to_pose(self, T):
395401
return xyz.tolist(), rpy.tolist()
396402

397403
def _get_all_children_links(self, parent_link):
404+
if parent_link is None:
405+
return []
398406
parent_link_name = parent_link.get('link')
399407
for link_name, info in self.joint_tree.items():
400408
if link_name == parent_link_name:
@@ -404,22 +412,35 @@ def _get_all_children_links(self, parent_link):
404412

405413
def _get_all_children_joints(self, parent_joint):
406414
children_joints = []
407-
link_name = parent_joint.find('child').get('link')
415+
child_elem = parent_joint.find('child')
416+
if child_elem is None:
417+
return children_joints
418+
link_name = child_elem.get('link')
419+
if link_name is None:
420+
return children_joints
408421
for joint_name, joint in self.joints.items():
409-
parent_link_name = joint.find('parent').get('link')
422+
parent_elem = joint.find('parent')
423+
if parent_elem is None:
424+
continue
425+
parent_link_name = parent_elem.get('link')
410426
if parent_link_name == link_name:
411427
children_joints.append(joint)
412428
return children_joints
413429

414430
def _get_all_children_joints_of_link(self, link_name):
415431
children_joints = []
416432
for joint_name, joint in self.joints.items():
417-
parent_link_name = joint.find('parent').get('link')
433+
parent_elem = joint.find('parent')
434+
if parent_elem is None:
435+
continue
436+
parent_link_name = parent_elem.get('link')
418437
if parent_link_name == link_name:
419438
children_joints.append(joint)
420439
return children_joints
421440

422441
def _link_is_included_in_path(self, target_link, path):
442+
if target_link is None:
443+
return False
423444
target_link_name = target_link.get('name')
424445
for elem in path:
425446
parent_link_name = elem[0]
@@ -429,6 +450,8 @@ def _link_is_included_in_path(self, target_link, path):
429450
return False
430451

431452
def _joint_is_included_in_path(self, target_joint, path):
453+
if target_joint is None:
454+
return False
432455
target_joint_name = target_joint.get('name')
433456
for elem in path:
434457
joint_name = elem[2]

tests/skrobot_tests/urdf_tests/test_xml_root_link_changer.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,200 @@ def test_find_path_to_link(self):
108108
self.assertIsInstance(item, tuple)
109109
self.assertEqual(len(item), 3)
110110

111+
def test_change_root_link_with_fixed_joints(self):
112+
"""Test root link changing with fixed joints (no axis elements)."""
113+
fixed_joint_urdf_content = '''<?xml version="1.0"?>
114+
<robot name="test_robot">
115+
<link name="world"/>
116+
<link name="base_link"/>
117+
<link name="end_link"/>
118+
119+
<joint name="world_to_base" type="fixed">
120+
<parent link="world"/>
121+
<child link="base_link"/>
122+
<origin xyz="0 0 0" rpy="0 0 0"/>
123+
</joint>
124+
125+
<joint name="base_to_end" type="revolute">
126+
<parent link="base_link"/>
127+
<child link="end_link"/>
128+
<origin xyz="1 0 0" rpy="0 0 0"/>
129+
<axis xyz="0 0 1"/>
130+
<limit lower="-1.57" upper="1.57" effort="10" velocity="1"/>
131+
</joint>
132+
</robot>'''
133+
134+
test_urdf_path = os.path.join(self.temp_dir, "fixed_joint_test.urdf")
135+
with open(test_urdf_path, 'w') as f:
136+
f.write(fixed_joint_urdf_content)
137+
138+
changer = URDFXMLRootLinkChanger(test_urdf_path)
139+
140+
output_path = os.path.join(self.temp_dir, "fixed_joint_changed.urdf")
141+
changer.change_root_link("base_link", output_path)
142+
143+
self.assertTrue(os.path.exists(output_path))
144+
145+
new_changer = URDFXMLRootLinkChanger(output_path)
146+
actual_new_root = new_changer.get_current_root_link()
147+
self.assertEqual(actual_new_root, "base_link")
148+
149+
def test_change_root_link_with_mimic_joints(self):
150+
"""Test root link changing with mimic joints and naming conflicts."""
151+
mimic_joint_urdf_content = '''<?xml version="1.0"?>
152+
<robot name="test_robot">
153+
<link name="world"/>
154+
<link name="base_link"/>
155+
<link name="joint1"/>
156+
<link name="joint2"/>
157+
158+
<joint name="world_to_base_joint" type="fixed">
159+
<parent link="world"/>
160+
<child link="base_link"/>
161+
<origin xyz="0 0 0" rpy="0 0 0"/>
162+
</joint>
163+
164+
<joint name="joint1" type="revolute">
165+
<parent link="base_link"/>
166+
<child link="joint1"/>
167+
<origin xyz="1 0 0" rpy="0 0 0"/>
168+
<axis xyz="0 0 1"/>
169+
<limit lower="-1.57" upper="1.57" effort="10" velocity="1"/>
170+
</joint>
171+
172+
<joint name="joint2_joint" type="revolute">
173+
<parent link="joint1"/>
174+
<child link="joint2"/>
175+
<origin xyz="1 0 0" rpy="0 0 0"/>
176+
<axis xyz="0 0 1"/>
177+
<limit lower="-1.57" upper="1.57" effort="10" velocity="1"/>
178+
<mimic joint="joint1" multiplier="2.0"/>
179+
</joint>
180+
</robot>'''
181+
182+
test_urdf_path = os.path.join(self.temp_dir, "mimic_joint_test.urdf")
183+
with open(test_urdf_path, 'w') as f:
184+
f.write(mimic_joint_urdf_content)
185+
186+
changer = URDFXMLRootLinkChanger(test_urdf_path)
187+
188+
output_path = os.path.join(self.temp_dir, "mimic_joint_changed.urdf")
189+
changer.change_root_link("base_link", output_path)
190+
191+
self.assertTrue(os.path.exists(output_path))
192+
193+
new_changer = URDFXMLRootLinkChanger(output_path)
194+
actual_new_root = new_changer.get_current_root_link()
195+
self.assertEqual(actual_new_root, "base_link")
196+
197+
def test_none_element_handling(self):
198+
"""Test handling of None elements during XML parsing."""
199+
malformed_urdf_content = '''<?xml version="1.0"?>
200+
<robot name="test_robot">
201+
<link name="world"/>
202+
<link name="base_link"/>
203+
204+
<joint name="world_to_base" type="fixed">
205+
<parent link="world"/>
206+
<child link="base_link"/>
207+
<origin xyz="0 0 0" rpy="0 0 0"/>
208+
</joint>
209+
</robot>'''
210+
211+
test_urdf_path = os.path.join(self.temp_dir, "malformed_test.urdf")
212+
with open(test_urdf_path, 'w') as f:
213+
f.write(malformed_urdf_content)
214+
215+
changer = URDFXMLRootLinkChanger(test_urdf_path)
216+
217+
output_path = os.path.join(self.temp_dir, "malformed_changed.urdf")
218+
219+
try:
220+
changer.change_root_link("base_link", output_path)
221+
self.assertTrue(os.path.exists(output_path))
222+
except AttributeError as e:
223+
if "'NoneType' object has no attribute 'get'" in str(e):
224+
self.fail("NoneType error not properly handled: {}".format(e))
225+
else:
226+
raise
227+
228+
def test_complex_kinematic_tree_root_change(self):
229+
"""Test root link change in complex kinematic trees with multiple branches."""
230+
complex_urdf_content = '''<?xml version="1.0"?>
231+
<robot name="complex_robot">
232+
<link name="world"/>
233+
<link name="base_link"/>
234+
<link name="arm1_link"/>
235+
<link name="arm2_link"/>
236+
<link name="gripper_base"/>
237+
<link name="gripper_finger1"/>
238+
<link name="gripper_finger2"/>
239+
240+
<joint name="world_to_base_joint" type="fixed">
241+
<parent link="world"/>
242+
<child link="base_link"/>
243+
<origin xyz="0 0 0" rpy="0 0 0"/>
244+
</joint>
245+
246+
<joint name="base_to_arm1_joint" type="revolute">
247+
<parent link="base_link"/>
248+
<child link="arm1_link"/>
249+
<origin xyz="0 0 0" rpy="0 0 0"/>
250+
<axis xyz="0 0 1"/>
251+
<limit lower="-1.57" upper="1.57" effort="10" velocity="1"/>
252+
</joint>
253+
254+
<joint name="arm1_to_arm2_joint" type="revolute">
255+
<parent link="arm1_link"/>
256+
<child link="arm2_link"/>
257+
<origin xyz="0 0 0" rpy="0 0 0"/>
258+
<axis xyz="0 1 0"/>
259+
<limit lower="-1.57" upper="1.57" effort="10" velocity="1"/>
260+
</joint>
261+
262+
<joint name="arm2_to_gripper_joint" type="fixed">
263+
<parent link="arm2_link"/>
264+
<child link="gripper_base"/>
265+
<origin xyz="0 0 0" rpy="0 0 0"/>
266+
</joint>
267+
268+
<joint name="gripper_finger1_joint" type="revolute">
269+
<parent link="gripper_base"/>
270+
<child link="gripper_finger1"/>
271+
<origin xyz="0 0 0" rpy="0 0 0"/>
272+
<axis xyz="1 0 0"/>
273+
<limit lower="0" upper="0.5" effort="5" velocity="0.5"/>
274+
</joint>
275+
276+
<joint name="gripper_finger2_joint" type="revolute">
277+
<parent link="gripper_base"/>
278+
<child link="gripper_finger2"/>
279+
<origin xyz="0 0 0" rpy="0 0 0"/>
280+
<axis xyz="1 0 0"/>
281+
<limit lower="0" upper="0.5" effort="5" velocity="0.5"/>
282+
<mimic joint="gripper_finger1_joint" multiplier="-1.0"/>
283+
</joint>
284+
</robot>'''
285+
286+
test_urdf_path = os.path.join(self.temp_dir, "complex_test.urdf")
287+
with open(test_urdf_path, 'w') as f:
288+
f.write(complex_urdf_content)
289+
290+
changer = URDFXMLRootLinkChanger(test_urdf_path)
291+
292+
output_path = os.path.join(self.temp_dir, "complex_changed.urdf")
293+
changer.change_root_link("gripper_base", output_path)
294+
295+
self.assertTrue(os.path.exists(output_path))
296+
297+
new_changer = URDFXMLRootLinkChanger(output_path)
298+
actual_new_root = new_changer.get_current_root_link()
299+
self.assertEqual(actual_new_root, "gripper_base")
300+
301+
new_links = new_changer.list_links()
302+
self.assertIn("gripper_finger1", new_links)
303+
self.assertIn("gripper_finger2", new_links)
304+
111305

112306
if __name__ == '__main__':
113307
unittest.main()

0 commit comments

Comments
 (0)