moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
moveit_configs_builder.py
Go to the documentation of this file.
1"""Simplify loading moveit config parameters.
2
3This module provides builder-pattern based class to simplify loading moveit related parameters found in
4robot_moveit_config package generated by moveit setup assistant.
5
6By default it expects the following structure for the moveit configs package
7
8robot_name_moveit_config/
9 .setup_assistant -> Used to retrieve information about the SRDF file and
10 the URDF file used when generating the package
11 config/
12 kinematics.yaml -> IK solver's parameters
13 joint_limits.yaml -> Overriding position/velocity/acceleration limits from the URDF file
14 moveit_cpp.yaml -> MoveItCpp related parameters
15 *_planning.yaml -> planning pipelines parameters
16 pilz_cartesian_limits.yaml -> Pilz planner parameters
17 moveit_controllers.yaml -> trajectory execution manager's parameters
18 ...
19
20Example:
21 moveit_configs = MoveItConfigsBuilder("robot_name").to_moveit_configs()
22 ...
23 moveit_configs.package_path
24 moveit_configs.robot_description
25 moveit_configs.robot_description_semantic
26 moveit_configs.robot_description_kinematics
27 moveit_configs.planning_pipelines
28 moveit_configs.trajectory_execution
29 moveit_configs.planning_scene_monitor
30 moveit_configs.sensors_3d
31 moveit_configs.move_group_capabilities
32 moveit_configs.joint_limits
33 moveit_configs.moveit_cpp
34 moveit_configs.pilz_cartesian_limits
35 # Or to get all the parameters as a dictionary
36 moveit_configs.to_dict()
37
38Each function in MoveItConfigsBuilder has a file_path as an argument which is used to override the default
39path for the file
40
41Example:
42 moveit_configs = MoveItConfigsBuilder("robot_name")
43 # Relative to robot_name_moveit_configs
44 .robot_description_semantic(Path("my_config") / "my_file.srdf")
45 .to_moveit_configs()
46 # Or
47 moveit_configs = MoveItConfigsBuilder("robot_name")
48 # Absolute path to robot_name_moveit_config
49 .robot_description_semantic(Path.home() / "my_config" / "new_file.srdf")
50 .to_moveit_configs()
51"""
52
53from pathlib import Path
54from typing import Optional, List, Dict
55import logging
56import re
57from dataclasses import dataclass, field
58from ament_index_python.packages import get_package_share_directory
59
60from launch_param_builder import ParameterBuilder, load_yaml, load_xacro
61from launch_param_builder.utils import ParameterBuilderFileNotFoundError
63from launch.some_substitutions_type import SomeSubstitutionsType
64from launch_ros.parameter_descriptions import ParameterValue
65
66moveit_configs_utils_path = Path(get_package_share_directory("moveit_configs_utils"))
67
68
69def get_pattern_matches(folder, pattern):
70 """Given all the files in the folder, find those that match the pattern.
71
72 If there are groups defined, the groups are returned. Otherwise the path to the matches are returned.
73 """
74 matches = []
75 if not folder.exists():
76 return matches
77 for child in folder.iterdir():
78 if not child.is_file():
79 continue
80 m = pattern.search(child.name)
81 if m:
82 groups = m.groups()
83 if groups:
84 matches.append(groups[0])
85 else:
86 matches.append(child)
87 return matches
88
89
90@dataclass(slots=True)
92 """Class containing MoveIt related parameters."""
93
94 # A pathlib Path to the moveit config package
95 package_path: Optional[str] = None
96 # A dictionary that has the contents of the URDF file.
97 robot_description: Dict = field(default_factory=dict)
98 # A dictionary that has the contents of the SRDF file.
99 robot_description_semantic: Dict = field(default_factory=dict)
100 # A dictionary IK solver specific parameters.
101 robot_description_kinematics: Dict = field(default_factory=dict)
102 # A dictionary that contains the planning pipelines parameters.
103 planning_pipelines: Dict = field(default_factory=dict)
104 # A dictionary contains parameters for trajectory execution & moveit controller managers.
105 trajectory_execution: Dict = field(default_factory=dict)
106 # A dictionary that has the planning scene monitor's parameters.
107 planning_scene_monitor: Dict = field(default_factory=dict)
108 # A dictionary that has the sensor 3d configuration parameters.
109 sensors_3d: Dict = field(default_factory=dict)
110 # A dictionary containing move_group's non-default capabilities.
111 move_group_capabilities: Dict = field(default_factory=dict)
112 # A dictionary containing the overridden position/velocity/acceleration limits.
113 joint_limits: Dict = field(default_factory=dict)
114 # A dictionary containing MoveItCpp related parameters.
115 moveit_cpp: Dict = field(default_factory=dict)
116 # A dictionary containing the cartesian limits for the Pilz planner.
117 pilz_cartesian_limits: Dict = field(default_factory=dict)
118
119 def to_dict(self):
120 parameters = {}
127 parameters.update(self.sensors_3dsensors_3d)
128 parameters.update(self.joint_limitsjoint_limits)
129 parameters.update(self.moveit_cppmoveit_cpp)
130 # Update robot_description_planning with pilz cartesian limits
131 if self.pilz_cartesian_limits:
132 parameters["robot_description_planning"].update(
133 self.pilz_cartesian_limits["robot_description_planning"]
134 )
135 return parameters
136
137
138class MoveItConfigsBuilder(ParameterBuilder):
139 __moveit_configs: MoveItConfigs
140 __robot_name: str
141 __urdf_package: Path
142 # Relative path of the URDF file w.r.t. __urdf_package
143 __urdf_file_path: Path
144 # Relative path of the SRDF file w.r.t. robot_name_moveit_config
145 __srdf_file_path: Path
146 # String specify the parameter name that the robot description will be loaded to, it will also be used as a prefix
147 # for "_planning", "_semantic", and "_kinematics"
148 __robot_description: str
149 __config_dir_path = Path("config")
150
151 # Look-up for robot_name_moveit_config package
153 self,
154 robot_name: str,
155 robot_description="robot_description",
156 package_name: Optional[str] = None,
157 ):
158 super().__init__(package_name or (robot_name + "_moveit_config"))
159 self.__moveit_configs__moveit_configs = MoveItConfigs(package_path=self._package_path)
160 self.__robot_name__robot_name = robot_name
161 setup_assistant_file = self._package_path / ".setup_assistant"
162
165 self.__urdf_xacro_args = None
167
168 modified_urdf_path = Path("config") / (self.__robot_name__robot_name + ".urdf.xacro")
169 if (self._package_path / modified_urdf_path).exists():
170 self.__urdf_package__urdf_package = self._package_path
171 self.__urdf_file_path__urdf_file_path = modified_urdf_path
172
173 if setup_assistant_file.exists():
174 setup_assistant_yaml = load_yaml(setup_assistant_file)
175 config = setup_assistant_yaml.get("moveit_setup_assistant_config", {})
176
177 if urdf_config := config.get("urdf", config.get("URDF")):
178 if self.__urdf_package__urdf_package is None:
180 get_package_share_directory(urdf_config["package"])
181 )
182 self.__urdf_file_path__urdf_file_path = Path(urdf_config["relative_path"])
183
184 if xacro_args := urdf_config.get("xacro_args"):
185 self.__urdf_xacro_args = dict(
186 arg.split(":=") for arg in xacro_args.split(" ") if arg
187 )
188
189 if srdf_config := config.get("srdf", config.get("SRDF")):
190 self.__srdf_file_path__srdf_file_path = Path(srdf_config["relative_path"])
191
193 logging.warning(
194 f"\x1b[33;21mCannot infer URDF from `{self._package_path}`. -- using config/{robot_name}.urdf\x1b[0m"
195 )
196 self.__urdf_package__urdf_package = self._package_path
198 self.__robot_name__robot_name + ".urdf"
199 )
200
202 logging.warning(
203 f"\x1b[33;21mCannot infer SRDF from `{self._package_path}`. -- using config/{robot_name}.srdf\x1b[0m"
204 )
206 self.__robot_name__robot_name + ".srdf"
207 )
208
209 self.__robot_description__robot_description = robot_description
210
212 self,
213 file_path: Optional[str] = None,
214 mappings: dict[SomeSubstitutionsType, SomeSubstitutionsType] = None,
215 ):
216 """Load robot description.
217
218 :param file_path: Absolute or relative path to the URDF file (w.r.t. robot_name_moveit_config).
219 :param mappings: mappings to be passed when loading the xacro file.
220 :return: Instance of MoveItConfigsBuilder with robot_description loaded.
221 """
222 if file_path is None:
223 robot_description_file_path = self.__urdf_package__urdf_package / self.__urdf_file_path__urdf_file_path
224 else:
225 robot_description_file_path = self._package_path / file_path
226 if (mappings is None) or all(
227 (isinstance(key, str) and isinstance(value, str))
228 for key, value in mappings.items()
229 ):
230 try:
231 self.__moveit_configs__moveit_configs.robot_description = {
233 robot_description_file_path,
234 mappings=mappings or self.__urdf_xacro_args,
235 )
236 }
237 except ParameterBuilderFileNotFoundError as e:
238 logging.warning(f"\x1b[33;21m{e}\x1b[0m")
239 logging.warning(
240 f"\x1b[33;21mThe robot description will be loaded from /robot_description topic \x1b[0m"
241 )
242
243 else:
244 self.__moveit_configs__moveit_configs.robot_description = {
245 self.__robot_description__robot_description: ParameterValue(
246 Xacro(str(robot_description_file_path), mappings=mappings),
247 value_type=str,
248 )
249 }
250 return self
251
253 self,
254 file_path: Optional[str] = None,
255 mappings: dict[SomeSubstitutionsType, SomeSubstitutionsType] = None,
256 ):
257 """Load semantic robot description.
258
259 :param file_path: Absolute or relative path to the SRDF file (w.r.t. robot_name_moveit_config).
260 :param mappings: mappings to be passed when loading the xacro file.
261 :return: Instance of MoveItConfigsBuilder with robot_description_semantic loaded.
262 """
263
264 if (mappings is None) or all(
265 (isinstance(key, str) and isinstance(value, str))
266 for key, value in mappings.items()
267 ):
268 self.__moveit_configs__moveit_configs.robot_description_semantic = {
270 + "_semantic": load_xacro(
271 self._package_path / (file_path or self.__srdf_file_path__srdf_file_path),
272 mappings=mappings,
273 )
274 }
275 else:
276 self.__moveit_configs__moveit_configs.robot_description_semantic = {
278 + "_semantic": ParameterValue(
279 Xacro(
280 str(self._package_path / (file_path or self.__srdf_file_path__srdf_file_path)),
281 mappings=mappings,
282 ),
283 value_type=str,
284 )
285 }
286 return self
287
288 def robot_description_kinematics(self, file_path: Optional[str] = None):
289 """Load IK solver parameters.
290
291 :param file_path: Absolute or relative path to the kinematics yaml file (w.r.t. robot_name_moveit_config).
292 :return: Instance of MoveItConfigsBuilder with robot_description_kinematics loaded.
293 """
294 self.__moveit_configs__moveit_configs.robot_description_kinematics = {
296 + "_kinematics": load_yaml(
297 self._package_path
298 / (file_path or self.__config_dir_path / "kinematics.yaml")
299 )
300 }
301 return self
302
303 def joint_limits(self, file_path: Optional[str] = None):
304 """Load joint limits overrides.
305
306 :param file_path: Absolute or relative path to the joint limits yaml file (w.r.t. robot_name_moveit_config).
307 :return: Instance of MoveItConfigsBuilder with robot_description_planning loaded.
308 """
309 self.__moveit_configs__moveit_configs.joint_limits = {
311 + "_planning": load_yaml(
312 self._package_path
313 / (file_path or self.__config_dir_path / "joint_limits.yaml")
314 )
315 }
316 return self
317
318 def moveit_cpp(self, file_path: Optional[str] = None):
319 """Load MoveItCpp parameters.
320
321 :param file_path: Absolute or relative path to the MoveItCpp yaml file (w.r.t. robot_name_moveit_config).
322 :return: Instance of MoveItConfigsBuilder with moveit_cpp loaded.
323 """
324 self.__moveit_configs__moveit_configs.moveit_cpp = load_yaml(
325 self._package_path
326 / (file_path or self.__config_dir_path / "moveit_cpp.yaml")
327 )
328 return self
329
331 self,
332 file_path: Optional[str] = None,
333 moveit_manage_controllers: bool = True,
334 ):
335 """Load trajectory execution and moveit controller managers' parameters
336
337 :param file_path: Absolute or relative path to the controllers yaml file (w.r.t. robot_name_moveit_config).
338 :param moveit_manage_controllers: Whether trajectory execution manager is allowed to switch controllers' states.
339 :return: Instance of MoveItConfigsBuilder with trajectory_execution loaded.
340 """
341 self.__moveit_configs__moveit_configs.trajectory_execution = {
342 "moveit_manage_controllers": moveit_manage_controllers,
343 }
344
345 # Find the most likely controller params as needed
346 if file_path is None:
347 config_folder = self._package_path / self.__config_dir_path
348 controller_pattern = re.compile("^(.*)_controllers.yaml$")
349 possible_names = get_pattern_matches(config_folder, controller_pattern)
350 if not possible_names:
351 # Warn the user instead of raising exception
352 logging.warning(
353 "\x1b[33;20mtrajectory_execution: `Parameter file_path is undefined "
354 f"and no matches for {config_folder}/*_controllers.yaml\x1b[0m"
355 )
356 else:
357 chosen_name = None
358 if len(possible_names) == 1:
359 chosen_name = possible_names[0]
360 else:
361 # Try a couple other common names, in order of precedence
362 for name in ["moveit", "moveit2", self.__robot_name__robot_name]:
363 if name in possible_names:
364 chosen_name = name
365 break
366 else:
367 option_str = "\n - ".join(
368 name + "_controllers.yaml" for name in possible_names
369 )
370 raise RuntimeError(
371 "trajectory_execution: "
372 f"Unable to guess which parameter file to load. Options:\n - {option_str}"
373 )
374 file_path = config_folder / (chosen_name + "_controllers.yaml")
375
376 else:
377 file_path = self._package_path / file_path
378
379 if file_path:
380 self.__moveit_configs__moveit_configs.trajectory_execution.update(load_yaml(file_path))
381 return self
382
384 self,
385 publish_planning_scene: bool = True,
386 publish_geometry_updates: bool = True,
387 publish_state_updates: bool = True,
388 publish_transforms_updates: bool = True,
389 publish_robot_description: bool = False,
390 publish_robot_description_semantic: bool = False,
391 ):
392 self.__moveit_configs__moveit_configs.planning_scene_monitor = {
393 # TODO: Fix parameter namespace upstream -- see planning_scene_monitor.cpp:262
394 # "planning_scene_monitor": {
395 "publish_planning_scene": publish_planning_scene,
396 "publish_geometry_updates": publish_geometry_updates,
397 "publish_state_updates": publish_state_updates,
398 "publish_transforms_updates": publish_transforms_updates,
399 "publish_robot_description": publish_robot_description,
400 "publish_robot_description_semantic": publish_robot_description_semantic,
401 # }
402 }
403 return self
404
405 def sensors_3d(self, file_path: Optional[str] = None):
406 """Load sensors_3d parameters.
407
408 :param file_path: Absolute or relative path to the sensors_3d yaml file (w.r.t. robot_name_moveit_config).
409 :return: Instance of MoveItConfigsBuilder with robot_description_planning loaded.
410 """
411 sensors_path = self._package_path / (
412 file_path or self.__config_dir_path / "sensors_3d.yaml"
413 )
414 if sensors_path.exists():
415 sensors_data = load_yaml(sensors_path)
416 # TODO(mikeferguson): remove the second part of this check once
417 # https://github.com/moveit/moveit_resources/pull/141 has made through buildfarm
418 if len(sensors_data["sensors"]) > 0 and sensors_data["sensors"][0]:
419 self.__moveit_configs__moveit_configs.sensors_3d = sensors_data
420 return self
421
423 self,
424 default_planning_pipeline: str = None,
425 pipelines: List[str] = None,
426 load_all: bool = True,
427 ):
428 """Load planning pipelines parameters.
429
430 :param default_planning_pipeline: Name of the default planning pipeline.
431 :param pipelines: List of the planning pipelines to be loaded.
432 :param load_all: Only used if pipelines is None.
433 If true, loads all pipelines defined in config package AND this package.
434 If false, only loads the pipelines defined in config package.
435 :return: Instance of MoveItConfigsBuilder with planning_pipelines loaded.
436 """
437 config_folder = self._package_path / self.__config_dir_path
438 default_folder = moveit_configs_utils_path / "default_configs"
439
440 # If no pipelines are specified, search by filename
441 if pipelines is None:
442 planning_pattern = re.compile("^(.*)_planning.yaml$")
443 pipelines = get_pattern_matches(config_folder, planning_pattern)
444 if load_all:
445 for pipeline in get_pattern_matches(default_folder, planning_pattern):
446 if pipeline not in pipelines:
447 pipelines.append(pipeline)
448
449 # Define default pipeline as needed
450 if not default_planning_pipeline:
451 if "ompl" in pipelines:
452 default_planning_pipeline = "ompl"
453 else:
454 default_planning_pipeline = pipelines[0]
455
456 if default_planning_pipeline not in pipelines:
457 raise RuntimeError(
458 f"default_planning_pipeline: `{default_planning_pipeline}` doesn't name any of the input pipelines "
459 f"`{','.join(pipelines)}`"
460 )
461 self.__moveit_configs__moveit_configs.planning_pipelines = {
462 "planning_pipelines": pipelines,
463 "default_planning_pipeline": default_planning_pipeline,
464 }
465 for pipeline in pipelines:
466 parameter_file = config_folder / (pipeline + "_planning.yaml")
467 if not parameter_file.exists():
468 parameter_file = default_folder / (pipeline + "_planning.yaml")
469 self.__moveit_configs__moveit_configs.planning_pipelines[pipeline] = load_yaml(
470 parameter_file
471 )
472
473 # Special rule to add ompl planner_configs
474 if "ompl" in self.__moveit_configs__moveit_configs.planning_pipelines:
475 ompl_config = self.__moveit_configs__moveit_configs.planning_pipelines["ompl"]
476 if "planner_configs" not in ompl_config:
477 ompl_config.update(load_yaml(default_folder / "ompl_defaults.yaml"))
478
479 return self
480
481 def pilz_cartesian_limits(self, file_path: Optional[str] = None):
482 """Load cartesian limits.
483
484 :param file_path: Absolute or relative path to the cartesian limits file (w.r.t. robot_name_moveit_config).
485 :return: Instance of MoveItConfigsBuilder with pilz_cartesian_limits loaded.
486 """
487 deprecated_path = self._package_path / (
488 self.__config_dir_path / "cartesian_limits.yaml"
489 )
490 if deprecated_path.exists():
491 logging.warning(
492 f"\x1b[33;21mcartesian_limits.yaml is deprecated, please rename to pilz_cartesian_limits.yaml\x1b[0m"
493 )
494
495 self.__moveit_configs__moveit_configs.pilz_cartesian_limits = {
497 + "_planning": load_yaml(
498 self._package_path
499 / (file_path or self.__config_dir_path / "pilz_cartesian_limits.yaml")
500 )
501 }
502 return self
503
505 """Get MoveIt configs from robot_name_moveit_config.
506
507 :return: An MoveItConfigs instance with all parameters loaded.
508 """
509 if not self.__moveit_configs__moveit_configs.robot_description:
510 self.robot_description()
511 if not self.__moveit_configs__moveit_configs.robot_description_semantic:
513 if not self.__moveit_configs__moveit_configs.robot_description_kinematics:
515 if not self.__moveit_configs__moveit_configs.planning_pipelines:
516 self.planning_pipelines()
517 if not self.__moveit_configs__moveit_configs.trajectory_execution:
519 if not self.__moveit_configs__moveit_configs.planning_scene_monitor:
521 if not self.__moveit_configs__moveit_configs.sensors_3d:
522 self.sensors_3d()
523 if not self.__moveit_configs__moveit_configs.joint_limits:
524 self.joint_limits()
525 # TODO(JafarAbdi): We should have a default moveit_cpp.yaml as port of a moveit config package
526 # if not self.__moveit_configs.moveit_cpp:
527 # self.moveit_cpp()
528 if "pilz_industrial_motion_planner" in self.__moveit_configs__moveit_configs.planning_pipelines:
529 if not self.__moveit_configs__moveit_configs.pilz_cartesian_limits:
532
533 def to_dict(self, include_moveit_configs: bool = True):
534 """Get loaded parameters from robot_name_moveit_config as a dictionary.
535
536 :param include_moveit_configs: Whether to include the MoveIt config parameters or
537 only the ones from ParameterBuilder
538 :return: Dictionary with all parameters loaded.
539 """
540 parameters = self._parameters
541 if include_moveit_configs:
542 parameters.update(self.to_moveit_configs().to_dict())
543 return parameters
trajectory_execution(self, Optional[str] file_path=None, bool moveit_manage_controllers=True)
__init__(self, str robot_name, robot_description="robot_description", Optional[str] package_name=None)
robot_description_semantic(self, Optional[str] file_path=None, dict[SomeSubstitutionsType, SomeSubstitutionsType] mappings=None)
planning_pipelines(self, str default_planning_pipeline=None, List[str] pipelines=None, bool load_all=True)
planning_scene_monitor(self, bool publish_planning_scene=True, bool publish_geometry_updates=True, bool publish_state_updates=True, bool publish_transforms_updates=True, bool publish_robot_description=False, bool publish_robot_description_semantic=False)
robot_description(self, Optional[str] file_path=None, dict[SomeSubstitutionsType, SomeSubstitutionsType] mappings=None)