moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
create_ikfast_moveit_plugin.py
Go to the documentation of this file.
1#! /usr/bin/env python3
2from __future__ import print_function
3
4"""
5IKFast Plugin Generator for MoveIt
6
7Creates a kinematics plugin using the output of IKFast from OpenRAVE.
8This plugin and the move_group node can be used as a general
9kinematics service, from within the moveit planning environment, or in
10your own ROS node.
11
12Author: Dave Coleman, PickNik Inc.
13 Michael Lautman, PickNik Inc.
14 Based heavily on the arm_kinematic_tools package by Jeremy Zoss, SwRI
15 and the arm_navigation plugin generator by David Butterworth, KAIST
16
17Date: March 2013
18
19"""
20"""
21Copyright (c) 2013, Jeremy Zoss, SwRI
22All rights reserved.
23
24Redistribution and use in source and binary forms, with or without
25modification, are permitted provided that the following conditions are met:
26
27* Redistributions of source code must retain the above copyright
28notice, this list of conditions and the following disclaimer.
29* Redistributions in binary form must reproduce the above copyright
30notice, this list of conditions and the following disclaimer in the
31documentation and/or other materials provided with the distribution.
32* Neither the name of the Willow Garage, Inc. nor the names of its
33contributors may be used to endorse or promote products derived from
34this software without specific prior written permission.
35
36THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
37AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
38IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
39ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
40IABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
41CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
42SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
43INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
44CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
46POSSIBILITY OF SUCH DAMAGE.
47"""
48
49import re
50import os
51import yaml
52from lxml import etree
53from getpass import getuser
54import shutil
55import argparse
56
57try:
58 from ament_index_python.packages import (
59 get_package_share_directory,
60 PackageNotFoundError,
61 )
62except ImportError:
63 print(
64 "Failed to import ament_index_python. No ROS2 environment available? Trying without."
65 )
66
67 # define stubs
68 class PackageNotFoundError(Exception):
69 pass
70
71 def get_package_share_directory(pkg_name):
72 raise PackageNotFoundError
73
74
75# Package containing this file
76plugin_gen_pkg = "moveit_kinematics"
77# Allowed search modes, see SEARCH_MODE enum in template file
78search_modes = ["OPTIMIZE_MAX_JOINT", "OPTIMIZE_FREE_JOINT"]
79
80
82 parser = argparse.ArgumentParser(
83 description="Generate an IKFast MoveIt kinematic plugin"
84 )
85 parser.add_argument("robot_name", help="The name of your robot")
86 parser.add_argument(
87 "planning_group_name",
88 help="The name of the planning group for which your IKFast solution was generated",
89 )
90 parser.add_argument(
91 "ikfast_plugin_pkg",
92 help="The name of the MoveIt IKFast Kinematics Plugin to be created/updated",
93 )
94 parser.add_argument(
95 "base_link_name",
96 help="The name of the base link that was used when generating your IKFast solution",
97 )
98 parser.add_argument(
99 "eef_link_name",
100 help="The name of the end effector link that was used when generating your IKFast solution",
101 )
102 parser.add_argument(
103 "ikfast_output_path",
104 help="The full path to the analytic IK solution output by IKFast",
105 )
106 parser.add_argument(
107 "--search_mode",
108 default=search_modes[0],
109 help="The search mode used to solve IK for robots with more than 6DOF",
110 )
111 parser.add_argument(
112 "--srdf_filename", help="The name of your robot. Defaults to <robot_name>.srdf"
113 )
114 parser.add_argument(
115 "--robot_name_in_srdf",
116 help="The name of your robot as defined in the srdf. Defaults to <robot_name>",
117 )
118 parser.add_argument(
119 "--moveit_config_pkg",
120 help="The robot moveit_config package. Defaults to <robot_name>_moveit_config",
121 )
122 return parser
123
124
126 if args.srdf_filename is None:
127 args.srdf_filename = args.robot_name + ".srdf"
128 if args.robot_name_in_srdf is None:
129 args.robot_name_in_srdf = args.robot_name
130 if args.moveit_config_pkg is None:
131 args.moveit_config_pkg = args.robot_name + "_moveit_config"
132
133
134def print_args(args):
135 print("Creating IKFastKinematicsPlugin with parameters: ")
136 print(" robot_name: %s" % args.robot_name)
137 print(" base_link_name: %s" % args.base_link_name)
138 print(" eef_link_name: %s" % args.eef_link_name)
139 print(" planning_group_name: %s" % args.planning_group_name)
140 print(" ikfast_plugin_pkg: %s" % args.ikfast_plugin_pkg)
141 print(" ikfast_output_path: %s" % args.ikfast_output_path)
142 print(" search_mode: %s" % args.search_mode)
143 print(" srdf_filename: %s" % args.srdf_filename)
144 print(" robot_name_in_srdf: %s" % args.robot_name_in_srdf)
145 print(" moveit_config_pkg: %s" % args.moveit_config_pkg)
146 print("")
147
148
149def update_deps(reqd_deps, req_type, e_parent):
150 curr_deps = [e.text for e in e_parent.findall(req_type)]
151 missing_deps = set(reqd_deps) - set(curr_deps)
152 for dep in missing_deps:
153 etree.SubElement(e_parent, req_type).text = dep
154
155
157 if not os.path.exists(args.ikfast_output_path):
158 raise Exception("Can't find IKFast source code at " + args.ikfast_output_path)
159
160 # Detect version of IKFast used to generate solver code
161 solver_version = 0
162 with open(args.ikfast_output_path, "r") as src:
163 for line in src:
164 if line.startswith("/// ikfast version"):
165 line_search = re.search("ikfast version (.*) generated", line)
166 if line_search:
167 solver_version = int(line_search.group(1), 0) & ~0x10000000
168 break
169 print("Found source code generated by IKFast version %s" % str(solver_version))
170
171 # Chose template depending on IKFast version
172 if solver_version >= 56:
173 setattr(args, "template_version", 61)
174 else:
175 raise Exception("This converter requires IKFast 0.5.6 or newer.")
176
177
178def xmlElement(name, text=None, **attributes):
179 e = etree.Element(name, **attributes)
180 e.text = text
181 return e
182
183
185 parameter_dict = {
186 "ikfast_kinematics": {
187 "link_prefix": {
188 "default_value": "",
189 "type": "string",
190 "description": "prefix added to tip- and baseframe to allow different namespaces or multi-robot setups",
191 }
192 }
193 }
194 return parameter_dict
195
196
198 try:
199 setattr(
200 args,
201 "ikfast_plugin_pkg_path",
202 get_package_share_directory(args.ikfast_plugin_pkg),
203 )
204 except PackageNotFoundError:
205 args.ikfast_plugin_pkg_path = os.path.abspath(args.ikfast_plugin_pkg)
206 print(
207 "Failed to find package: %s. Will create it in %s."
208 % (args.ikfast_plugin_pkg, args.ikfast_plugin_pkg_path)
209 )
210 # update pkg name to basename of path
211 args.ikfast_plugin_pkg = os.path.basename(args.ikfast_plugin_pkg_path)
212
213 src_path = args.ikfast_plugin_pkg_path + "/src/"
214 if not os.path.exists(src_path):
215 os.makedirs(src_path)
216
217 include_path = args.ikfast_plugin_pkg_path + "/include/"
218 if not os.path.exists(include_path):
219 os.makedirs(include_path)
220
221 # Create package.xml
222 pkg_xml_path = args.ikfast_plugin_pkg_path + "/package.xml"
223 if not os.path.exists(pkg_xml_path):
224 root = xmlElement("package", format="2")
225 root.append(xmlElement("name", text=args.ikfast_plugin_pkg))
226 root.append(xmlElement("version", text="0.0.0"))
227 root.append(
228 xmlElement("description", text="IKFast plugin for " + args.robot_name)
229 )
230 root.append(xmlElement("license", text="BSD"))
231 user_name = getuser()
232 root.append(
233 xmlElement("maintainer", email="%[email protected]" % user_name, text=user_name)
234 )
235 root.append(xmlElement("buildtool_depend", text="ament_cmake"))
236 export = xmlElement("export")
237 export.append(xmlElement("build_type", text="ament_cmake"))
238 root.append(export)
239 etree.ElementTree(root).write(
240 pkg_xml_path, xml_declaration=True, pretty_print=True, encoding="UTF-8"
241 )
242 print("Created package.xml at: '%s'" % pkg_xml_path)
243
244 # Create parameter YAML in src folder
245 parameters_yaml_path = src_path + "ikfast_kinematics_parameters.yaml"
246 if not os.path.exists(parameters_yaml_path):
247 print("Create parameters.yaml at: '%s'" % parameters_yaml_path)
248 with open(parameters_yaml_path, "w") as file:
249 documents = yaml.dump(create_parameter_dict(), file)
250
251
253 for candidate in [os.path.dirname(__file__) + "/../templates"]:
254 if os.path.exists(candidate) and os.path.exists(candidate + "/ikfast.h"):
255 return os.path.realpath(candidate)
256 try:
257 return os.path.join(
258 get_package_share_directory(plugin_gen_pkg),
259 "ikfast_kinematics_plugin/templates",
260 )
261 except PackageNotFoundError:
262 raise Exception("Can't find package %s" % plugin_gen_pkg)
263
264
266 # Copy the source code generated by IKFast into our src folder
267 src_path = args.ikfast_plugin_pkg_path + "/src/"
268 solver_file_path = (
269 src_path
270 + args.robot_name
271 + "_"
272 + args.planning_group_name
273 + "_ikfast_solver.cpp"
274 )
275 if not os.path.exists(solver_file_path) or not os.path.samefile(
276 args.ikfast_output_path, solver_file_path
277 ):
278 shutil.copy2(args.ikfast_output_path, solver_file_path)
279
280 if not os.path.exists(solver_file_path):
281 raise Exception(
282 "Failed to copy IKFast source code from '%s' to '%s'\n"
283 "Manually copy the source file generated by IKFast to this location and re-run"
284 % (args.ikfast_output_path, solver_file_path)
285 )
286 # Remember ikfast solver file for update of MoveIt package
287 args.ikfast_output_path = solver_file_path
288
289 # Get template folder location
290 template_dir = find_template_dir()
291
292 # namespace for the plugin
293 setattr(args, "namespace", args.robot_name + "_" + args.planning_group_name)
294 replacements = dict(
295 _ROBOT_NAME_=args.robot_name,
296 _GROUP_NAME_=args.planning_group_name,
297 _SEARCH_MODE_=args.search_mode,
298 _EEF_LINK_=args.eef_link_name,
299 _BASE_LINK_=args.base_link_name,
300 _PACKAGE_NAME_=args.ikfast_plugin_pkg,
301 _NAMESPACE_=args.namespace,
302 )
303
304 # Copy ikfast header file
305 copy_file(
306 template_dir + "/ikfast.h",
307 args.ikfast_plugin_pkg_path + "/include/ikfast.h",
308 "ikfast header file",
309 )
310 # Create ikfast plugin template
311 copy_file(
312 template_dir
313 + "/ikfast"
314 + str(args.template_version)
315 + "_moveit_plugin_template.cpp",
316 args.ikfast_plugin_pkg_path
317 + "/src/"
318 + args.robot_name
319 + "_"
320 + args.planning_group_name
321 + "_ikfast_moveit_plugin.cpp",
322 "ikfast plugin file",
323 replacements,
324 )
325
326 # Create plugin definition .xml file
327 ik_library_name = args.namespace + "_moveit_ikfast_plugin"
328 plugin_def = etree.Element("library", path=ik_library_name)
329 setattr(args, "plugin_name", args.namespace + "/IKFastKinematicsPlugin")
330 cl = etree.SubElement(
331 plugin_def,
332 "class",
333 name=args.plugin_name,
334 type=args.namespace + "::IKFastKinematicsPlugin",
335 base_class_type="kinematics::KinematicsBase",
336 )
337 desc = etree.SubElement(cl, "description")
338 desc.text = (
339 "IKFast{template} plugin for closed-form kinematics of {robot} {group}".format(
340 template=args.template_version,
341 robot=args.robot_name,
342 group=args.planning_group_name,
343 )
344 )
345
346 # Write plugin definition to file
347 plugin_file_name = ik_library_name + "_description.xml"
348 plugin_file_path = args.ikfast_plugin_pkg_path + "/" + plugin_file_name
349 etree.ElementTree(plugin_def).write(
350 plugin_file_path, xml_declaration=True, pretty_print=True, encoding="UTF-8"
351 )
352 print("Created plugin definition at '%s'" % plugin_file_path)
353
354 # Create CMakeLists file
355 replacements.update(dict(_LIBRARY_NAME_=ik_library_name))
356 copy_file(
357 template_dir + "/CMakeLists.txt",
358 args.ikfast_plugin_pkg_path + "/CMakeLists.txt",
359 "cmake file",
360 replacements,
361 )
362
363 # Add plugin export to package manifest
364 parser = etree.XMLParser(remove_blank_text=True)
365 package_file_name = args.ikfast_plugin_pkg_path + "/package.xml"
366 package_xml = etree.parse(package_file_name, parser).getroot()
367
368 # Make sure at least all required dependencies are in the depends lists
369 build_deps = [
370 "liblapack-dev",
371 "moveit_core",
372 "pluginlib",
373 "rclcpp",
374 "tf2_kdl",
375 "tf2_eigen",
376 ]
377 run_deps = ["liblapack-dev", "moveit_core", "pluginlib", "rclcpp"]
378
379 update_deps(build_deps, "build_depend", package_xml)
380 update_deps(run_deps, "exec_depend", package_xml)
381
382 # Check that plugin definition file is in the export list
383 new_export = etree.Element("moveit_core", plugin="${prefix}/" + plugin_file_name)
384
385 export_element = package_xml.find("export")
386 if export_element is None:
387 export_element = etree.SubElement(package_xml, "export")
388
389 found = False
390 for el in export_element.findall("moveit_core"):
391 found = etree.tostring(new_export) == etree.tostring(el)
392 if found:
393 break
394
395 if not found:
396 export_element.append(new_export)
397
398 # Always write the package xml file, even if there are no changes, to ensure
399 # proper encodings are used in the future (UTF-8)
400 etree.ElementTree(package_xml).write(
401 package_file_name, xml_declaration=True, pretty_print=True, encoding="UTF-8"
402 )
403 print("Wrote package.xml at '%s'" % package_file_name)
404
405 # Create a script for easily updating the plugin in the future in case the plugin needs to be updated
406 easy_script_file_path = args.ikfast_plugin_pkg_path + "/update_ikfast_plugin.sh"
407 with open(easy_script_file_path, "w") as f:
408 f.write(
409 "search_mode="
410 + args.search_mode
411 + "\n"
412 + "srdf_filename="
413 + args.srdf_filename
414 + "\n"
415 + "robot_name_in_srdf="
416 + args.robot_name_in_srdf
417 + "\n"
418 + "moveit_config_pkg="
419 + args.moveit_config_pkg
420 + "\n"
421 + "robot_name="
422 + args.robot_name
423 + "\n"
424 + "planning_group_name="
425 + args.planning_group_name
426 + "\n"
427 + "ikfast_plugin_pkg="
428 + args.ikfast_plugin_pkg
429 + "\n"
430 + "base_link_name="
431 + args.base_link_name
432 + "\n"
433 + "eef_link_name="
434 + args.eef_link_name
435 + "\n"
436 + "ikfast_output_path="
437 + args.ikfast_output_path
438 + "\n\n"
439 + "rosrun moveit_kinematics create_ikfast_moveit_plugin.py\\\n"
440 + " --search_mode=$search_mode\\\n"
441 + " --srdf_filename=$srdf_filename\\\n"
442 + " --robot_name_in_srdf=$robot_name_in_srdf\\\n"
443 + " --moveit_config_pkg=$moveit_config_pkg\\\n"
444 + " $robot_name\\\n"
445 + " $planning_group_name\\\n"
446 + " $ikfast_plugin_pkg\\\n"
447 + " $base_link_name\\\n"
448 + " $eef_link_name\\\n"
449 + " $ikfast_output_path\n"
450 )
451
452 print("Created update plugin script at '%s'" % easy_script_file_path)
453
454
456 try:
457 moveit_config_pkg_path = get_package_share_directory(args.moveit_config_pkg)
458 except PackageNotFoundError:
459 raise Exception("Failed to find package: " + args.moveit_config_pkg)
460
461 try:
462 srdf_file_name = moveit_config_pkg_path + "/config/" + args.srdf_filename
463 srdf = etree.parse(srdf_file_name).getroot()
464 except IOError:
465 raise Exception("Failed to find SRDF file: " + srdf_file_name)
466 except etree.XMLSyntaxError as err:
467 raise Exception(
468 "Failed to parse xml in file: %s\n%s" % (srdf_file_name, err.msg)
469 )
470
471 if args.robot_name_in_srdf != srdf.get("name"):
472 raise Exception(
473 "Robot name in srdf ('%s') doesn't match expected name ('%s')"
474 % (srdf.get("name"), args.robot_name_in_srdf)
475 )
476
477 groups = srdf.findall("group")
478 if len(groups) < 1:
479 raise Exception("No planning groups are defined in the SRDF")
480
481 planning_group = None
482 for group in groups:
483 if group.get("name").lower() == args.planning_group_name.lower():
484 planning_group = group
485
486 if planning_group is None:
487 raise Exception(
488 "Planning group '%s' not defined in the SRDF. Available groups: \n%s"
489 % (
490 args.planning_group_name,
491 ", ".join([group_name.get("name") for group_name in groups]),
492 )
493 )
494
495 # Modify kinematics.yaml file
496 kin_yaml_file_name = moveit_config_pkg_path + "/config/kinematics.yaml"
497 with open(kin_yaml_file_name, "r") as f:
498 kin_yaml_data = yaml.safe_load(f)
499
500 kin_yaml_data[args.planning_group_name]["kinematics_solver"] = args.plugin_name
501 with open(kin_yaml_file_name, "w") as f:
502 yaml.dump(kin_yaml_data, f, default_flow_style=False)
503
504 print("Modified kinematics.yaml at '%s'" % kin_yaml_file_name)
505
506
507def copy_file(src_path, dest_path, description, replacements=None):
508 if not os.path.exists(src_path):
509 raise Exception("Can't find %s at '%s'" % (description, src_path))
510
511 if replacements is None:
512 replacements = dict()
513
514 with open(src_path, "r") as f:
515 content = f.read()
516
517 # replace templates
518 for key, value in replacements.items():
519 content = re.sub(key, value, content)
520
521 with open(dest_path, "w") as f:
522 f.write(content)
523 print("Created %s at '%s'" % (description, dest_path))
524
525
526def main():
527 parser = create_parser()
528 args = parser.parse_args()
529
531 print_args(args)
535 try:
537 except Exception as e:
538 print("Failed to update MoveIt package:\n" + str(e))
539
540
541if __name__ == "__main__":
542 main()
xmlElement(name, text=None, **attributes)
copy_file(src_path, dest_path, description, replacements=None)
update_deps(reqd_deps, req_type, e_parent)
void print(PropagationDistanceField &pdf, int numX, int numY, int numZ)