@@ -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
112306if __name__ == '__main__' :
113307 unittest .main ()
0 commit comments