moveit2
The MoveIt Motion Planning Framework for ROS 2.
moveit_configs_builder.py
Go to the documentation of this file.
1 """Simplify loading moveit config parameters.
2 
3 This module provides builder-pattern based class to simplify loading moveit related parameters found in
4 robot_moveit_config package generated by moveit setup assistant.
5 
6 By default it expects the following structure for the moveit configs package
7 
8 robot_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 
20 Example:
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 
38 Each function in MoveItConfigsBuilder has a file_path as an argument which is used to override the default
39 path for the file
40 
41 Example:
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 
53 from pathlib import Path
54 from typing import Optional, List, Dict
55 import logging
56 import re
57 from dataclasses import dataclass, field
58 from ament_index_python.packages import get_package_share_directory
59 
60 from launch_param_builder import ParameterBuilder, load_yaml, load_xacro
61 from launch_param_builder.utils import ParameterBuilderFileNotFoundError
62 from moveit_configs_utils.substitutions import Xacro
63 from launch.some_substitutions_type import SomeSubstitutionsType
64 from launch_ros.parameter_descriptions import ParameterValue
65 
66 moveit_configs_utils_path = Path(get_package_share_directory("moveit_configs_utils"))
67 
68 
69 def 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 = {}
121  parameters.update(self.robot_description)
122  parameters.update(self.robot_description_semantic)
123  parameters.update(self.robot_description_kinematics)
124  parameters.update(self.planning_pipelines)
125  parameters.update(self.trajectory_execution)
126  parameters.update(self.planning_scene_monitor)
127  parameters.update(self.sensors_3d)
128  parameters.update(self.joint_limits)
129  parameters.update(self.moveit_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 
138 class 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
152  def __init__(
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 
163  self.__urdf_package__urdf_package = None
164  self.__urdf_file_path__urdf_file_path = None
165  self.__urdf_xacro_args__urdf_xacro_args = None
166  self.__srdf_file_path__srdf_file_path = 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  urdf_config = config.get("urdf", config.get("URDF"))
177  if urdf_config and self.__urdf_package__urdf_package is None:
178  self.__urdf_package__urdf_package = Path(
179  get_package_share_directory(urdf_config["package"])
180  )
181  self.__urdf_file_path__urdf_file_path = Path(urdf_config["relative_path"])
182 
183  if (xacro_args := urdf_config.get("xacro_args")) is not None:
184  self.__urdf_xacro_args__urdf_xacro_args = dict(
185  arg.split(":=") for arg in xacro_args.split(" ") if arg
186  )
187 
188  srdf_config = config.get("srdf", config.get("SRDF"))
189  if srdf_config:
190  self.__srdf_file_path__srdf_file_path = Path(srdf_config["relative_path"])
191 
192  if not self.__urdf_package__urdf_package or not self.__urdf_file_path__urdf_file_path:
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
197  self.__urdf_file_path__urdf_file_path = self.__config_dir_path__config_dir_path / (
198  self.__robot_name__robot_name + ".urdf"
199  )
200 
201  if not self.__srdf_file_path__srdf_file_path:
202  logging.warning(
203  f"\x1b[33;21mCannot infer SRDF from `{self._package_path}`. -- using config/{robot_name}.srdf\x1b[0m"
204  )
205  self.__srdf_file_path__srdf_file_path = self.__config_dir_path__config_dir_path / (
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 = {
232  self.__robot_description__robot_description: load_xacro(
233  robot_description_file_path,
234  mappings=mappings or self.__urdf_xacro_args__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 = {
269  self.__robot_description__robot_description
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 = {
277  self.__robot_description__robot_description
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 = {
295  self.__robot_description__robot_description
296  + "_kinematics": load_yaml(
297  self._package_path
298  / (file_path or self.__config_dir_path__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 = {
310  self.__robot_description__robot_description
311  + "_planning": load_yaml(
312  self._package_path
313  / (file_path or self.__config_dir_path__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__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__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__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/ros-planning/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__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__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 = {
496  self.__robot_description__robot_description
497  + "_planning": load_yaml(
498  self._package_path
499  / (file_path or self.__config_dir_path__config_dir_path / "pilz_cartesian_limits.yaml")
500  )
501  }
502  return self
503 
504  def to_moveit_configs(self):
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_descriptionrobot_description()
511  if not self.__moveit_configs__moveit_configs.robot_description_semantic:
512  self.robot_description_semanticrobot_description_semantic()
513  if not self.__moveit_configs__moveit_configs.robot_description_kinematics:
514  self.robot_description_kinematicsrobot_description_kinematics()
515  if not self.__moveit_configs__moveit_configs.planning_pipelines:
516  self.planning_pipelinesplanning_pipelines()
517  if not self.__moveit_configs__moveit_configs.trajectory_execution:
518  self.trajectory_executiontrajectory_execution()
519  if not self.__moveit_configs__moveit_configs.planning_scene_monitor:
520  self.planning_scene_monitorplanning_scene_monitor()
521  if not self.__moveit_configs__moveit_configs.sensors_3d:
522  self.sensors_3dsensors_3d()
523  if not self.__moveit_configs__moveit_configs.joint_limits:
524  self.joint_limitsjoint_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:
530  self.pilz_cartesian_limitspilz_cartesian_limits()
531  return self.__moveit_configs__moveit_configs
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_configsto_moveit_configs().to_dict())
543  return parameters
def __init__(self, str robot_name, robot_description="robot_description", Optional[str] package_name=None)
def 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)
def trajectory_execution(self, Optional[str] file_path=None, bool moveit_manage_controllers=True)
def robot_description(self, Optional[str] file_path=None, dict[SomeSubstitutionsType, SomeSubstitutionsType] mappings=None)
def planning_pipelines(self, str default_planning_pipeline=None, List[str] pipelines=None, bool load_all=True)
def robot_description_semantic(self, Optional[str] file_path=None, dict[SomeSubstitutionsType, SomeSubstitutionsType] mappings=None)
def load_yaml(package_name, file_path)
void update(moveit::core::RobotState *self, bool force, std::string &category)
Definition: robot_state.cpp:47