1- import os
21from pathlib import Path
32from typing import Dict , List , Tuple , Union
43
@@ -17,8 +16,8 @@ def __init__(self, urdf: str) -> None:
1716 self ._robot = urdf_parser_py .urdf .Robot .from_xml_string (urdf )
1817
1918 @classmethod
20- def from_file (cls , path : Union [Path , str ]) -> None :
21- r"""Instantiate URDF parser path to URDF file.
19+ def from_urdf_file (cls , path : Union [Path , str ]) -> None :
20+ r"""Instantiate URDF parser via path to URDF file.
2221
2322 Args:
2423 path (Union[Path, str]): Path to URDF file.
@@ -35,37 +34,53 @@ def from_file(cls, path: Union[Path, str]) -> None:
3534 return cls (urdf = urdf )
3635
3736 @classmethod
38- def from_ros_xacro (cls , ros_package : str , xacro_path : str ) -> None :
37+ def from_ros_xacro (cls , ros_package : str , xacro_path : Union [ Path , str ] ) -> None :
3938 r"""Instantiate URDF parser from ROS xacro file.
4039
4140 Args:
4241 ros_package (str): Internally finds the path to ros_package.
43- xacro_path (str): Path to xacro file relative to ros_package.
42+ xacro_path (Union[Path, str] ): Path to xacro file relative to ros_package.
4443 """
44+ xacro_path = Path (xacro_path )
45+ if not xacro_path .suffix == ".xacro" :
46+ raise ValueError (f"Xacro file { xacro_path } must have .xacro extension." )
4547 return cls (
4648 urdf = cls ._urdf_from_ros_xacro (
4749 ros_package = ros_package , xacro_path = xacro_path
4850 )
4951 )
5052
5153 @staticmethod
52- def _urdf_from_ros_xacro ( ros_package : str , xacro_path : str ) -> str :
53- r"""Convert ROS xacro file to URDF .
54+ def resolve_uris_via_ros_registry ( uris : Dict [ str , str ] ) -> Dict [ str , Path ] :
55+ r"""Resolve the URI-style package:// prefix using the ament_index_python ROS registry .
5456
5557 Args:
56- ros_package (str): Internally finds the path to ros_package.
57- xacro_path (str): Path to xacro file relative to ros_package.
58+ uris (Dict[str,str]): Dictionary of link names and URI-style mesh paths, prefixed with package://.
5859
5960 Returns:
60- str: URDF string .
61+ Dict[ str,Path]: Dictionary of link names and absolute mesh paths .
6162 """
62-
63- import xacro
6463 from ament_index_python import get_package_share_directory
6564
66- return xacro .process (
67- os .path .join (get_package_share_directory (ros_package ), xacro_path )
68- )
65+ mesh_paths = {}
66+ for link_name in uris .keys ():
67+ uri = uris [link_name ]
68+ if uri .startswith ("package://" ):
69+ mesh_path = Path (uri .removeprefix ("package://" ))
70+ if len (mesh_path .parts ) < 2 :
71+ raise ValueError (
72+ f"Invalid package path { mesh_path } for link { link_name } ."
73+ )
74+ mesh_paths [link_name ] = Path (
75+ get_package_share_directory (mesh_path .parts [0 ])
76+ ) / Path (* mesh_path .parts [1 :])
77+ else :
78+ raise ValueError ("Case unhandled." )
79+ if len (mesh_paths ) != len (uris ):
80+ raise RuntimeError ("Some mesh paths could not be resolved." )
81+ if not all ([path .exists () for path in mesh_paths .values ()]):
82+ raise FileNotFoundError ("Some resolved mesh paths do not exist." )
83+ return mesh_paths
6984
7085 def chain_link_names (self , root_link_name : str , end_link_name : str ) -> List [str ]:
7186 r"""Get link names in chain from root to end link.
@@ -110,68 +125,56 @@ def link_names_with_meshes(self, collision: bool = False) -> List[str]:
110125 links .remove (link )
111126 return links
112127
113- def raw_mesh_paths (
128+ def mesh_uris (
114129 self , root_link_name : str , end_link_name : str , collision : bool = False
115130 ) -> Dict [str , str ]:
116- r"""Get the raw mesh paths as specified in URDF.
131+ r"""Get the mesh paths as specified in URDF. These paths may be relative or have a package:// prefix .
117132
118133 Args:
119134 root_link_name (str): Root link name.
120135 end_link_name (str): End link name.
121136 collision (bool): If True, get collision mesh paths, else visual mesh paths.
122137
123138 Returns:
124- Dict[str,str]: Dictionary of link names and raw mesh paths .
139+ Dict[str,str]: Dictionary of link names and mesh URIs .
125140 """
126141 link_names = self .chain_link_names (
127142 root_link_name = root_link_name , end_link_name = end_link_name
128143 )
129- raw_mesh_paths = {}
144+ paths = {}
130145 # lookup paths
131146 for link_name in link_names :
132147 link : urdf_parser_py .urdf .Link = self ._robot .link_map [link_name ]
133148 if collision :
134149 if link .collision is None :
135150 continue
136- raw_mesh_paths [link_name ] = link .collision .geometry .filename
151+ paths [link_name ] = Path ( link .collision .geometry .filename )
137152 else :
138153 if link .visual is None :
139154 continue
140- raw_mesh_paths [link_name ] = link .visual .geometry .filename
141- return raw_mesh_paths
155+ paths [link_name ] = Path ( link .visual .geometry .filename )
156+ return paths
142157
143- def ros_package_mesh_paths (
158+ def mesh_paths_from_ros_registry (
144159 self , root_link_name : str , end_link_name : str , collision : bool = False
145- ) -> Dict [str , str ]:
146- r"""Get the absolute mesh paths by resolving package within ROS.
160+ ) -> Dict [str , Path ]:
161+ r"""Get the absolute mesh paths by resolving the package:// prefix using ROS ament_index_python .
147162
148163 Args:
149164 root_link_name (str): Root link name.
150165 end_link_name (str): End link name.
151166 collision (bool): If True, get collision mesh paths, else visual mesh paths.
152167
153168 Returns:
154- Dict[str,str ]: Dictionary of link names and absolute mesh paths.
169+ Dict[str,Path ]: Dictionary of link names and absolute mesh paths.
155170 """
156- raw_mesh_paths = self .raw_mesh_paths (
157- root_link_name = root_link_name ,
158- end_link_name = end_link_name ,
159- collision = collision ,
171+ return URDFParser .resolve_uris_via_ros_registry (
172+ uris = self .mesh_uris (
173+ root_link_name = root_link_name ,
174+ end_link_name = end_link_name ,
175+ collision = collision ,
176+ )
160177 )
161- from ament_index_python import get_package_share_directory
162-
163- ros_package_mesh_paths = {}
164- for link_name in raw_mesh_paths .keys ():
165- raw_mesh_path = raw_mesh_paths [link_name ]
166- if raw_mesh_path .startswith ("package://" ):
167- raw_mesh_path = raw_mesh_path .replace ("package://" , "" )
168- package , relative_mesh_path = raw_mesh_path .split ("/" , 1 )
169- ros_package_mesh_paths [link_name ] = os .path .join (
170- get_package_share_directory (package ), relative_mesh_path
171- )
172- else :
173- raise ValueError ("Case unhandled." )
174- return ros_package_mesh_paths
175178
176179 def mesh_origins (
177180 self , root_link_name : str , end_link_name : str , collision : bool = False
@@ -209,6 +212,26 @@ def mesh_origins(
209212 mesh_origins [link_name ] = origin
210213 return mesh_origins
211214
215+ @staticmethod
216+ def _urdf_from_ros_xacro (ros_package : str , xacro_path : Path ) -> str :
217+ r"""Convert ROS xacro file to URDF.
218+
219+ Args:
220+ ros_package (str): Internally finds the path to ros_package.
221+ xacro_path (Path): Path to xacro file relative to ros_package.
222+
223+ Returns:
224+ str: URDF string.
225+ """
226+
227+ import xacro
228+ from ament_index_python import get_package_share_directory
229+
230+ path = Path (get_package_share_directory (ros_package )) / xacro_path
231+ if not path .exists ():
232+ raise FileNotFoundError (f"Xacro file { path } does not exist." )
233+ return xacro .process (path )
234+
212235 def _verify_links_in_chain (self , root_link_name : str , end_link_name : str ) -> None :
213236 if not self ._robot :
214237 raise RuntimeError ("Robot not initialized." )
0 commit comments