Skip to content

Commit 7bb512f

Browse files
committed
cleaned up the URDFParser API
1 parent c8b8a86 commit 7bb512f

File tree

7 files changed

+77
-54
lines changed

7 files changed

+77
-54
lines changed

roboreg/differentiable/robot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def from_urdf_parser(
3737
from roboreg.io import apply_mesh_origins, load_meshes, simplify_meshes
3838

3939
# parse data from URDF
40-
mesh_paths = urdf_parser.ros_package_mesh_paths(
40+
mesh_paths = urdf_parser.mesh_paths_from_ros_registry(
4141
root_link_name=root_link_name,
4242
end_link_name=end_link_name,
4343
collision=collision,

roboreg/io/parsers.py

Lines changed: 67 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from pathlib import Path
32
from 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.")

test/differentiable/test_kinematics.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def test_torch_kinematics_on_mesh(
5757
meshes = TorchMeshContainer(
5858
meshes=apply_mesh_origins(
5959
meshes=load_meshes(
60-
urdf_parser.ros_package_mesh_paths(
60+
urdf_parser.mesh_paths_from_ros_registry(
6161
root_link_name=root_link_name, end_link_name=end_link_name
6262
)
6363
),
@@ -120,7 +120,7 @@ def test_diff_kinematics() -> None:
120120
meshes = TorchMeshContainer(
121121
meshes=apply_mesh_origins(
122122
meshes=load_meshes(
123-
urdf_parser.ros_package_mesh_paths(
123+
urdf_parser.mesh_paths_from_ros_registry(
124124
root_link_name=root_link_name, end_link_name=end_link_name
125125
)
126126
),

test/differentiable/test_rendering.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def __init__(
6868

6969
# instantiate meshes
7070
self.meshes = rrd.TorchMeshContainer(
71-
mesh_paths=self.urdf_parser.ros_package_mesh_paths(
71+
mesh_paths=self.urdf_parser.mesh_paths_from_ros_registry(
7272
self.root_link_name, self.end_link_name
7373
),
7474
batch_size=self.batch_size,
@@ -269,7 +269,7 @@ def test_multi_config_single_view_rendering() -> None:
269269

270270
# overwrite meshes with batch size
271271
test_rendering.meshes = rrd.TorchMeshContainer(
272-
mesh_paths=test_rendering.urdf_parser.ros_package_mesh_paths(
272+
mesh_paths=test_rendering.urdf_parser.mesh_paths_from_ros_registry(
273273
test_rendering.root_link_name, test_rendering.end_link_name
274274
),
275275
batch_size=q.shape[0],
@@ -331,7 +331,7 @@ def test_multi_config_single_view_pose_optimization() -> None:
331331

332332
# overwrite meshes with batch size
333333
test_rendering.meshes = rrd.TorchMeshContainer(
334-
mesh_paths=test_rendering.urdf_parser.ros_package_mesh_paths(
334+
mesh_paths=test_rendering.urdf_parser.mesh_paths_from_ros_registry(
335335
test_rendering.root_link_name, test_rendering.end_link_name
336336
),
337337
batch_size=q.shape[0],

test/io/test_parsers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def test_urdf_parser() -> None:
1818
urdf_parser = URDFParser.from_ros_xacro("lbr_description", "urdf/med7/med7.xacro")
1919
print(urdf_parser.chain_link_names("lbr_link_0", "lbr_link_ee"))
2020
print(urdf_parser.raw_mesh_paths("lbr_link_0", "lbr_link_ee"))
21-
print(urdf_parser.ros_package_mesh_paths("lbr_link_0", "lbr_link_ee"))
21+
print(urdf_parser.mesh_paths_from_ros_registry("lbr_link_0", "lbr_link_ee"))
2222
print(urdf_parser.mesh_origins("lbr_link_0", "lbr_link_ee"))
2323
print(urdf_parser.link_names_with_meshes(collision=True))
2424
print(urdf_parser.link_names_with_meshes(collision=False))

test/test_hydra_icp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ def test_hydra_icp():
143143
meshes = TorchMeshContainer(
144144
meshes=apply_mesh_origins(
145145
meshes=load_meshes(
146-
urdf_parser.ros_package_mesh_paths(
146+
urdf_parser.mesh_paths_from_ros_registry(
147147
root_link_name=root_link_name, end_link_name=end_link_name
148148
)
149149
),
@@ -274,7 +274,7 @@ def test_hydra_robust_icp() -> None:
274274
meshes = TorchMeshContainer(
275275
meshes=apply_mesh_origins(
276276
meshes=load_meshes(
277-
urdf_parser.ros_package_mesh_paths(
277+
urdf_parser.mesh_paths_from_ros_registry(
278278
root_link_name=root_link_name, end_link_name=end_link_name
279279
)
280280
),

test/util/test_viz.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def test_visualize_robot():
9393
meshes = rrd.TorchMeshContainer(
9494
meshes=apply_mesh_origins(
9595
meshes=load_meshes(
96-
urdf_parser.ros_package_mesh_paths(
96+
urdf_parser.mesh_paths_from_ros_registry(
9797
root_link_name=root_link_name, end_link_name=end_link_name
9898
)
9999
),

0 commit comments

Comments
 (0)