├── .gitignore ├── src └── gazebo2rviz │ ├── __init__.py │ └── conversions.py ├── .gitremotes ├── setup.py ├── launch ├── gazebo2tf.launch ├── sdf2rviz.launch ├── gazebo2rviz.launch └── gazebo2marker.launch ├── package.xml ├── CMakeLists.txt └── scripts ├── sdf2extract_tfstatic_node.py ├── sdf2marker_node.py ├── sdf2tfstatic_node.py ├── gazebo2marker_node.py ├── gazebo2tf_node.py └── sdf2moveit_collision.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | -------------------------------------------------------------------------------- /src/gazebo2rviz/__init__.py: -------------------------------------------------------------------------------- 1 | from gazebo2rviz.conversions import link2marker_msg 2 | -------------------------------------------------------------------------------- /.gitremotes: -------------------------------------------------------------------------------- 1 | i60p414 git@i60p414.ira.uka.de:ahb/gazebo2rviz.git 2 | github git@github.com:andreasBihlmaier/gazebo2rviz.git 3 | ipr git@gitlab.ira.uka.de:ipr/gazebo2rviz.git 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from catkin_pkg.python_setup import generate_distutils_setup 3 | 4 | d = generate_distutils_setup( 5 | packages=['gazebo2rviz'], 6 | package_dir={'': 'src'} 7 | ) 8 | 9 | setup(**d) 10 | -------------------------------------------------------------------------------- /launch/gazebo2tf.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /launch/sdf2rviz.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /launch/gazebo2rviz.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /launch/gazebo2marker.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | gazebo2rviz 4 | 0.1.0 5 | This package contains nodes to automatically import all entities simulated in a ROS-enabled Gazebo instance into rviz through the TF and Marker plugin. 6 | Andreas Bihlmaier 7 | MIT 8 | http://wiki.ros.org/gazebo2rviz 9 | Andreas Bihlmaier 10 | 11 | catkin 12 | rospy 13 | pysdf 14 | pysdf 15 | rospy 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(gazebo2rviz) 3 | 4 | find_package(catkin REQUIRED COMPONENTS 5 | rospy 6 | pysdf 7 | ) 8 | 9 | catkin_python_setup() 10 | 11 | catkin_package( 12 | # INCLUDE_DIRS include 13 | # LIBRARIES gazebo2rviz 14 | CATKIN_DEPENDS rospy pysdf 15 | # DEPENDS python-rospkg 16 | ) 17 | 18 | 19 | ########### 20 | ## Build ## 21 | ########### 22 | include_directories( 23 | ${catkin_INCLUDE_DIRS} 24 | ) 25 | 26 | 27 | ############# 28 | ## Install ## 29 | ############# 30 | install(PROGRAMS 31 | scripts/gazebo2tf_node.py 32 | scripts/gazebo2marker_node.py 33 | scripts/sdf2marker_node.py 34 | scripts/sdf2tfstatic_node.py 35 | scripts/sdf2extract_tfstatic_node.py 36 | DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 37 | ) 38 | 39 | install(DIRECTORY launch/ 40 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}/launch 41 | ) 42 | -------------------------------------------------------------------------------- /scripts/sdf2extract_tfstatic_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Publish specific transform between two links within a SDF file as 'static' tf transformation (with different name) 4 | """ 5 | 6 | import argparse 7 | 8 | import rospy 9 | import tf 10 | import tf_conversions.posemath as pm 11 | from tf.transformations import * 12 | 13 | import pysdf 14 | 15 | 16 | 17 | def main(): 18 | parser = argparse.ArgumentParser() 19 | parser.add_argument('-f', '--freq', type=float, default=10, help='Frequency TFs are published (default: 10 Hz)') 20 | parser.add_argument('sdf', help='SDF model to publish (e.g. coke_can)') 21 | parser.add_argument('source_from_tf', help='Base Frame from SDF') 22 | parser.add_argument('source_to_tf', help='Target Frame from SDF') 23 | parser.add_argument('target_from_tf', nargs='?', help='Published Base Frame') 24 | parser.add_argument('target_to_tf', nargs='?', help='Published Target Frame') 25 | args = parser.parse_args(rospy.myargv()[1:]) 26 | source_from_tf, source_to_tf = args.source_from_tf, args.source_to_tf 27 | target_from_tf = args.target_from_tf if args.target_from_tf else source_from_tf 28 | target_to_tf = args.target_to_tf if args.target_to_tf else source_to_tf 29 | 30 | rospy.init_node('sdf2extract_tfstatic') 31 | 32 | sdf = pysdf.SDF(model=args.sdf) 33 | world = sdf.world 34 | from_link = world.get_link(source_from_tf) 35 | if not from_link: 36 | rospy.logerr('SDF %s does not contain source_from_tf %s' % (args.sdf, source_from_tf)) 37 | return 1 38 | to_link = world.get_link(source_to_tf) 39 | if not to_link: 40 | rospy.logerr('SDF %s does not contain source_to_tf %s' % (args.sdf, source_to_tf)) 41 | return 2 42 | 43 | from_to_tf = concatenate_matrices(inverse_matrix(from_link.pose_world), to_link.pose_world) 44 | translation, quaternion = pysdf.homogeneous2translation_quaternion(from_to_tf) 45 | 46 | tfBroadcaster = tf.TransformBroadcaster() 47 | 48 | rospy.loginfo('Publishing TF %s -> %s from SDF %s as TF %s -> %s: t=%s q=%s' % (source_from_tf, source_to_tf, args.sdf, target_from_tf, target_to_tf, translation, quaternion)) 49 | rospy.loginfo('Spinning') 50 | r = rospy.Rate(args.freq) 51 | while not rospy.is_shutdown(): 52 | tfBroadcaster.sendTransform(translation, quaternion, rospy.get_rostime(), target_to_tf, target_from_tf) 53 | r.sleep() 54 | 55 | 56 | if __name__ == '__main__': 57 | main() 58 | -------------------------------------------------------------------------------- /scripts/sdf2marker_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Publish all visuals within a SDF file as rviz Markers 4 | """ 5 | 6 | import argparse 7 | 8 | import rospy 9 | from visualization_msgs.msg import Marker 10 | from tf.transformations import * 11 | 12 | import pysdf 13 | from gazebo2rviz import * 14 | 15 | 16 | updatePeriod = 0.5 17 | use_collision = False 18 | submodelsToBeIgnored = [] 19 | markerPub = None 20 | world = None 21 | markers = [] 22 | 23 | 24 | 25 | def prepare_link_marker(link, full_linkname): 26 | marker_msg = link2marker_msg(link, full_linkname, use_collision, rospy.Duration(2 * updatePeriod)) 27 | if marker_msg: 28 | markers.append(marker_msg) 29 | 30 | 31 | def prepare_markers(prefix): 32 | world.for_all_links(prepare_link_marker) 33 | for marker in markers: 34 | marker.header.frame_id = prefix + pysdf.sdf2tfname(marker.header.frame_id) 35 | marker.ns = prefix + pysdf.sdf2tfname(marker.ns) 36 | 37 | 38 | def publishMarkers(): 39 | for marker in markers: 40 | #print(marker) 41 | markerPub.publish(marker) 42 | 43 | 44 | 45 | def main(): 46 | parser = argparse.ArgumentParser() 47 | parser.add_argument('-f', '--freq', type=float, default=2, help='Frequency Markers are published (default: 2 Hz)') 48 | parser.add_argument('-p', '--prefix', default='', help='Publish with prefix') 49 | parser.add_argument('-c', '--collision', action='store_true', help='Publish collision instead of visual elements') 50 | parser.add_argument('sdf', help='SDF model to publish (e.g. coke_can)') 51 | args = parser.parse_args(rospy.myargv()[1:]) 52 | 53 | rospy.init_node('sdf2marker') 54 | 55 | global submodelsToBeIgnored 56 | submodelsToBeIgnored = rospy.get_param('~ignore_submodels_of', '').split(';') 57 | rospy.loginfo('Ignoring submodels of: ' + str(submodelsToBeIgnored)) 58 | 59 | global updatePeriod 60 | updatePeriod = 1. / args.freq 61 | 62 | global use_collision 63 | use_collision = args.collision 64 | 65 | global markerPub 66 | markerPub = rospy.Publisher('/visualization_marker', Marker, queue_size=10) 67 | 68 | global world 69 | sdf = pysdf.SDF(model=args.sdf) 70 | world = sdf.world 71 | 72 | prepare_markers(args.prefix) 73 | 74 | rospy.loginfo('Spinning') 75 | r = rospy.Rate(args.freq) 76 | while not rospy.is_shutdown(): 77 | publishMarkers(); 78 | r.sleep() 79 | 80 | if __name__ == '__main__': 81 | main() 82 | -------------------------------------------------------------------------------- /scripts/sdf2tfstatic_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Publish all links within a SDF file as 'static' tf transformation 4 | """ 5 | 6 | import argparse 7 | 8 | import rospy 9 | import tf 10 | import tf_conversions.posemath as pm 11 | from tf.transformations import * 12 | 13 | import pysdf 14 | 15 | ignored_submodels = [] 16 | tfBroadcaster = None 17 | world = None 18 | tfs = [] 19 | 20 | 21 | 22 | def is_ignored(model): 23 | model_full_name = model.get_full_name() 24 | for ignored_submodel in ignored_submodels: 25 | if model_full_name == ignored_submodel or model_full_name.endswith('::' + ignored_submodel): 26 | return True 27 | return False 28 | 29 | 30 | def calculate_tfs(prefix): 31 | world.for_all_joints(calculate_joint_tf) 32 | for tf in tfs: 33 | tf[0] = prefix + pysdf.sdf2tfname(tf[0]) 34 | tf[1] = prefix + pysdf.sdf2tfname(tf[1]) 35 | 36 | 37 | def calculate_joint_tf(joint, full_jointname): 38 | full_prefix = full_jointname.replace(joint.name, '') 39 | if is_ignored(joint.parent_model): 40 | print("Ignoring TF %s -> %s" % (full_prefix + joint.parent, full_prefix + joint.child)) 41 | return 42 | rel_tf = concatenate_matrices(inverse_matrix(joint.tree_parent_link.pose_world), joint.tree_child_link.pose_world) 43 | translation, quaternion = pysdf.homogeneous2translation_quaternion(rel_tf) 44 | tfs.append([full_prefix + joint.parent, full_prefix + joint.child, translation, quaternion]) 45 | 46 | 47 | def publish_tf(): 48 | for tf in tfs: 49 | #print(tf) 50 | tfBroadcaster.sendTransform(tf[2], tf[3], rospy.get_rostime(), tf[1], tf[0]) 51 | 52 | 53 | def main(): 54 | parser = argparse.ArgumentParser() 55 | parser.add_argument('-f', '--freq', type=float, default=10, help='Frequency TFs are published (default: 10 Hz)') 56 | parser.add_argument('-p', '--prefix', default='', help='Publish with prefix') 57 | parser.add_argument('sdf', help='SDF model to publish (e.g. coke_can)') 58 | args = parser.parse_args(rospy.myargv()[1:]) 59 | 60 | rospy.init_node('sdf2tfstatic') 61 | 62 | global ignored_submodels 63 | ignored_submodels = rospy.get_param('~ignore_submodels', '').split(';') 64 | rospy.loginfo('Ignoring submodels of: %s' % ignored_submodels) 65 | 66 | global tfBroadcaster 67 | tfBroadcaster = tf.TransformBroadcaster() 68 | 69 | global world 70 | sdf = pysdf.SDF(model=args.sdf) 71 | world = sdf.world 72 | 73 | calculate_tfs(args.prefix) 74 | 75 | rospy.loginfo('Spinning') 76 | r = rospy.Rate(args.freq) 77 | while not rospy.is_shutdown(): 78 | publish_tf(); 79 | r.sleep() 80 | 81 | 82 | if __name__ == '__main__': 83 | main() 84 | -------------------------------------------------------------------------------- /scripts/gazebo2marker_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | import rospy 6 | import tf 7 | from gazebo_msgs.msg import ModelStates 8 | from visualization_msgs.msg import Marker 9 | 10 | import pysdf 11 | from gazebo2rviz import * 12 | 13 | 14 | updatePeriod = 0.5 15 | use_collision = False 16 | submodelsToBeIgnored = [] 17 | markerPub = None 18 | world = None 19 | model_cache = {} 20 | lastUpdateTime = None 21 | worldsdf = None 22 | 23 | 24 | 25 | def publish_link_marker(link, full_linkname, **kwargs): 26 | full_linkinstancename = full_linkname 27 | if 'model_name' in kwargs and 'instance_name' in kwargs: 28 | full_linkinstancename = full_linkinstancename.replace(kwargs['model_name'], kwargs['instance_name'], 1) 29 | marker_msgs = link2marker_msg(link, full_linkinstancename, use_collision, rospy.Duration(2 * updatePeriod)) 30 | if len(marker_msgs) > 0: 31 | for marker_msg in marker_msgs: 32 | markerPub.publish(marker_msg) 33 | 34 | 35 | def on_model_states_msg(model_states_msg): 36 | global lastUpdateTime 37 | sinceLastUpdateDuration = rospy.get_rostime() - lastUpdateTime 38 | if sinceLastUpdateDuration.to_sec() < updatePeriod: 39 | return 40 | lastUpdateTime = rospy.get_rostime() 41 | 42 | for (model_idx, modelinstance_name) in enumerate(model_states_msg.name): 43 | #print(model_idx, modelinstance_name) 44 | model_name = pysdf.name2modelname(modelinstance_name) 45 | #print('model_name:', model_name) 46 | if not model_name in model_cache: 47 | model_cache[model_name] = None 48 | if worldsdf: 49 | for model in worldsdf.world.models: 50 | if model.name == model_name: 51 | model_cache[model_name] = model 52 | break 53 | else: 54 | sdf = pysdf.SDF(model=model_name) 55 | if len(sdf.world.models) >= 1: 56 | model_cache[model_name] = sdf.world.models[0] 57 | if model_cache[model_name]: 58 | rospy.loginfo('Loaded model: %s' % model_cache[model_name].name) 59 | else: 60 | rospy.loginfo('Unable to load model: %s' % model_name) 61 | model = model_cache[model_name] 62 | if not model: # Not an SDF model 63 | continue 64 | #print('model:', model) 65 | model.for_all_links(publish_link_marker, model_name=model_name, instance_name=modelinstance_name) 66 | 67 | 68 | def main(): 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument('-f', '--freq', type=float, default=2, help='Frequency Markers are published (default: 2 Hz)') 71 | parser.add_argument('-c', '--collision', action='store_true', help='Publish collision instead of visual elements') 72 | parser.add_argument('-w', '--worldfile', type=str, help='Read models from this world file') 73 | args = parser.parse_args(rospy.myargv()[1:]) 74 | 75 | rospy.init_node('gazebo2marker') 76 | 77 | global submodelsToBeIgnored 78 | submodelsToBeIgnored = rospy.get_param('~ignore_submodels_of', '').split(';') 79 | rospy.loginfo('Ignoring submodels of: ' + str(submodelsToBeIgnored)) 80 | if submodelsToBeIgnored: 81 | rospy.logerr('ignore_submodels_of is currently not supported and will thus have no effect') 82 | 83 | 84 | global updatePeriod 85 | updatePeriod = 1. / args.freq 86 | 87 | global use_collision 88 | use_collision = args.collision 89 | 90 | if args.worldfile: 91 | global worldsdf 92 | worldsdf = pysdf.SDF(file=args.worldfile) 93 | 94 | global markerPub 95 | markerPub = rospy.Publisher('/visualization_marker', Marker, queue_size=10) 96 | rospy.sleep(rospy.Duration(0, 100 * 1000)) 97 | 98 | global lastUpdateTime 99 | lastUpdateTime = rospy.get_rostime() 100 | modelStatesSub = rospy.Subscriber('gazebo/model_states', ModelStates, on_model_states_msg) 101 | 102 | rospy.loginfo('Spinning') 103 | rospy.spin() 104 | 105 | if __name__ == '__main__': 106 | main() 107 | -------------------------------------------------------------------------------- /scripts/gazebo2tf_node.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import rospy 4 | import tf 5 | from geometry_msgs.msg import Pose 6 | from gazebo_msgs.msg import LinkStates 7 | import tf_conversions.posemath as pm 8 | from tf.transformations import * 9 | 10 | import pysdf 11 | 12 | tfBroadcaster = None 13 | submodelsToBeIgnored = [] 14 | lastUpdateTime = None 15 | updatePeriod = 0.05 16 | model_cache = {} 17 | 18 | 19 | def is_ignored(link_name): 20 | for ignored_submodel in submodelsToBeIgnored: 21 | if link_name.startswith(ignored_submodel + '::'): 22 | return True 23 | return False 24 | 25 | 26 | def on_link_states_msg(link_states_msg): 27 | """ 28 | Publish tf for each model in current Gazebo world 29 | """ 30 | global lastUpdateTime 31 | sinceLastUpdateDuration = rospy.get_rostime() - lastUpdateTime 32 | if sinceLastUpdateDuration.to_sec() < updatePeriod: 33 | return 34 | lastUpdateTime = rospy.get_rostime() 35 | 36 | poses = {'gazebo_world': identity_matrix()} 37 | for (link_idx, link_name) in enumerate(link_states_msg.name): 38 | poses[link_name] = pysdf.pose_msg2homogeneous(link_states_msg.pose[link_idx]) 39 | #print('%s:\n%s' % (link_name, poses[link_name])) 40 | 41 | for (link_idx, link_name) in enumerate(link_states_msg.name): 42 | #print(link_idx, link_name) 43 | modelinstance_name = link_name.split('::')[0] 44 | #print('modelinstance_name:', modelinstance_name) 45 | model_name = pysdf.name2modelname(modelinstance_name) 46 | #print('model_name:', model_name) 47 | if not model_name in model_cache: 48 | sdf = pysdf.SDF(model=model_name) 49 | model_cache[model_name] = sdf.world.models[0] if len(sdf.world.models) >= 1 else None 50 | if model_cache[model_name]: 51 | rospy.loginfo('Loaded model: %s' % model_cache[model_name].name) 52 | else: 53 | rospy.loginfo('Unable to load model: %s' % model_name) 54 | model = model_cache[model_name] 55 | link_name_in_model = link_name.replace(modelinstance_name + '::', '') 56 | if model: 57 | link = model.get_link(link_name_in_model) 58 | if link.tree_parent_joint: 59 | parent_link = link.tree_parent_joint.tree_parent_link 60 | parent_link_name = parent_link.get_full_name() 61 | #print('parent:', parent_link_name) 62 | parentinstance_link_name = parent_link_name.replace(model_name, modelinstance_name, 1) 63 | else: # direct child of world 64 | parentinstance_link_name = 'gazebo_world' 65 | else: # Not an SDF model 66 | parentinstance_link_name = 'gazebo_world' 67 | #print('parentinstance:', parentinstance_link_name) 68 | if is_ignored(parentinstance_link_name): 69 | rospy.loginfo("Ignoring TF %s -> %s" % (parentinstance_link_name, link_name)) 70 | continue 71 | pose = poses[link_name] 72 | parent_pose = poses[parentinstance_link_name] 73 | rel_tf = concatenate_matrices(inverse_matrix(parent_pose), pose) 74 | translation, quaternion = pysdf.homogeneous2translation_quaternion(rel_tf) 75 | #print('Publishing TF %s -> %s: t=%s q=%s' % (pysdf.sdf2tfname(parentinstance_link_name), pysdf.sdf2tfname(link_name), translation, quaternion)) 76 | tfBroadcaster.sendTransform(translation, quaternion, rospy.get_rostime(), pysdf.sdf2tfname(link_name), pysdf.sdf2tfname(parentinstance_link_name)) 77 | 78 | 79 | 80 | def main(): 81 | rospy.init_node('gazebo2tf') 82 | 83 | global submodelsToBeIgnored 84 | submodelsToBeIgnored = rospy.get_param('~ignore_submodels_of', '').split(';') 85 | rospy.loginfo('Ignoring submodels of: ' + str(submodelsToBeIgnored)) 86 | 87 | global tfBroadcaster 88 | tfBroadcaster = tf.TransformBroadcaster() 89 | 90 | global lastUpdateTime 91 | lastUpdateTime = rospy.get_rostime() 92 | linkStatesSub = rospy.Subscriber('gazebo/link_states', LinkStates, on_link_states_msg) 93 | 94 | rospy.loginfo('Spinning') 95 | rospy.spin() 96 | 97 | if __name__ == '__main__': 98 | main() 99 | -------------------------------------------------------------------------------- /src/gazebo2rviz/conversions.py: -------------------------------------------------------------------------------- 1 | import copy 2 | 3 | import rospy 4 | from rospkg import RosPack, ResourceNotFound 5 | from visualization_msgs.msg import Marker 6 | 7 | import pysdf 8 | import os.path 9 | 10 | protoMarkerMsg = Marker() 11 | protoMarkerMsg.frame_locked = True 12 | protoMarkerMsg.id = 0 13 | protoMarkerMsg.action = Marker.ADD 14 | protoMarkerMsg.mesh_use_embedded_materials = True 15 | protoMarkerMsg.color.a = 0.0 16 | protoMarkerMsg.color.r = 0.0 17 | protoMarkerMsg.color.g = 0.0 18 | protoMarkerMsg.color.b = 0.0 19 | supported_geometry_types = ['mesh', 'cylinder', 'sphere', 'box'] 20 | 21 | gazebo_rospack = RosPack() 22 | 23 | 24 | def link2marker_msg(link, full_linkname, use_collision = False, lifetime = rospy.Duration(0)): 25 | marker_msg = None 26 | linkpart = None 27 | if use_collision: 28 | linkparts = getattr(link, 'collisions') 29 | else: # visual 30 | linkparts = getattr(link, 'visuals') 31 | 32 | msgs = [] 33 | 34 | for linkpart in linkparts: 35 | if not linkpart.geometry_type in supported_geometry_types: 36 | if linkpart.geometry_type: 37 | print("Element %s with geometry type %s not supported. Ignored." % (full_linkname, linkpart.geometry_type)) 38 | return None 39 | 40 | marker_msg = copy.deepcopy(protoMarkerMsg) 41 | marker_msg.header.frame_id = pysdf.sdf2tfname(full_linkname) 42 | marker_msg.header.stamp = rospy.get_rostime() 43 | marker_msg.lifetime = lifetime 44 | marker_msg.ns = pysdf.sdf2tfname(full_linkname + "::" + linkpart.name) 45 | marker_msg.pose = pysdf.homogeneous2pose_msg(linkpart.pose) 46 | 47 | if linkpart.geometry_type == 'mesh': 48 | marker_msg.type = Marker.MESH_RESOURCE 49 | #print('linkpart.geometry_data: %s' % linkpart.geometry_data['uri']) 50 | for models_path in pysdf.models_paths: 51 | resource = linkpart.geometry_data['uri'].replace('model://', models_path + '/') 52 | #print('resource: %s' % resource) 53 | if os.path.isfile(resource): 54 | marker_msg.mesh_resource = 'file://' + resource 55 | #print('found resource %s at %s' % (linkpart.geometry_data['uri'], resource)) 56 | break 57 | # support URDF-like resource paths starting with model:// 58 | if not marker_msg.mesh_resource and linkpart.geometry_data['uri'].startswith('model://'): 59 | stripped_uri = linkpart.geometry_data['uri'].replace('model://', '') 60 | uri_parts = stripped_uri.split('/', 1) 61 | 62 | if len(uri_parts) == 2: 63 | package_name = uri_parts[0] 64 | try: 65 | package_path = gazebo_rospack.get_path(package_name) 66 | mesh_path = os.path.join(package_path, uri_parts[1]) 67 | if os.path.isfile(mesh_path): 68 | marker_msg.mesh_resource = 'file://' + mesh_path 69 | except ResourceNotFound as e: 70 | pass 71 | 72 | if not marker_msg.mesh_resource: 73 | print('ERROR! could not find resource: %s' % linkpart.geometry_data['uri']) 74 | return None 75 | 76 | scale = (float(val) for val in linkpart.geometry_data['scale'].split()) 77 | marker_msg.scale.x, marker_msg.scale.y, marker_msg.scale.z = scale 78 | else: 79 | marker_msg.color.a = 1 80 | marker_msg.color.r = marker_msg.color.g = marker_msg.color.b = 0.5 81 | 82 | if linkpart.geometry_type == 'box': 83 | marker_msg.type = Marker.CUBE 84 | scale = (float(val) for val in linkpart.geometry_data['size'].split()) 85 | marker_msg.scale.x, marker_msg.scale.y, marker_msg.scale.z = scale 86 | elif linkpart.geometry_type == 'sphere': 87 | marker_msg.type = Marker.SPHERE 88 | marker_msg.scale.x = marker_msg.scale.y = marker_msg.scale.z = 2.0 * float(linkpart.geometry_data['radius']) 89 | elif linkpart.geometry_type == 'cylinder': 90 | marker_msg.type = Marker.CYLINDER 91 | marker_msg.scale.x = marker_msg.scale.y = 2.0 * float(linkpart.geometry_data['radius']) 92 | marker_msg.scale.z = float(linkpart.geometry_data['length']) 93 | 94 | #print(marker_msg) 95 | msgs.append(marker_msg) 96 | return msgs 97 | -------------------------------------------------------------------------------- /scripts/sdf2moveit_collision.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Publish all collision elements within a SDF file as MoveIt CollisionObjects 4 | """ 5 | 6 | import argparse 7 | import os 8 | try: 9 | from pyassimp import pyassimp 10 | except: 11 | # support pyassimp > 3.0 12 | import pyassimp 13 | 14 | import rospy 15 | #from visualization_msgs.msg import Marker 16 | from geometry_msgs.msg import Pose, PoseStamped, Point 17 | from moveit_msgs.msg import CollisionObject, PlanningScene 18 | from shape_msgs.msg import SolidPrimitive, Plane, Mesh, MeshTriangle 19 | from tf.transformations import * 20 | 21 | import pysdf 22 | from gazebo2rviz import * 23 | 24 | 25 | ignored_submodels = [] 26 | collision_objects = {} 27 | 28 | 29 | 30 | # Slightly modified PlanningSceneInterface.__make_mesh from moveit_commander/src/moveit_commander/planning_scene_interface.py 31 | def make_mesh(co, name, pose, filename, scale = (1, 1, 1)): 32 | #print("make_mesh(name=%s filename=%s)" % (name, filename)) 33 | scene = pyassimp.load(filename) 34 | if not scene.meshes or len(scene.meshes) == 0: 35 | raise MoveItCommanderException("There are no meshes in the file") 36 | if len(scene.meshes[0].faces) == 0: 37 | raise MoveItCommanderException("There are no faces in the mesh") 38 | co.operation = CollisionObject.ADD 39 | co.id = name 40 | co.header = pose.header 41 | 42 | mesh = Mesh() 43 | first_face = scene.meshes[0].faces[0] 44 | if hasattr(first_face, '__len__'): 45 | for face in scene.meshes[0].faces: 46 | if len(face) == 3: 47 | triangle = MeshTriangle() 48 | triangle.vertex_indices = [face[0], face[1], face[2]] 49 | mesh.triangles.append(triangle) 50 | elif hasattr(first_face, 'indices'): 51 | for face in scene.meshes[0].faces: 52 | if len(face.indices) == 3: 53 | triangle = MeshTriangle() 54 | triangle.vertex_indices = [face.indices[0], 55 | face.indices[1], 56 | face.indices[2]] 57 | mesh.triangles.append(triangle) 58 | else: 59 | raise MoveItCommanderException("Unable to build triangles from mesh due to mesh object structure") 60 | for vertex in scene.meshes[0].vertices: 61 | point = Point() 62 | point.x = vertex[0]*scale[0] 63 | point.y = vertex[1]*scale[1] 64 | point.z = vertex[2]*scale[2] 65 | mesh.vertices.append(point) 66 | if not isinstance(co.meshes, list): 67 | co.meshes = [] 68 | co.meshes += [mesh] 69 | 70 | if not isinstance(co.mesh_poses, list): 71 | co.mesh_poses = [] 72 | co.mesh_poses += [pose.pose] 73 | 74 | pyassimp.release(scene) 75 | return co 76 | 77 | 78 | def append_to_collision_object(sink_collision_object, source_collision_object): 79 | sink_collision_object.primitives.extend(source_collision_object.primitives) 80 | sink_collision_object.primitive_poses.extend(source_collision_object.primitive_poses) 81 | sink_collision_object.meshes.extend(source_collision_object.meshes) 82 | sink_collision_object.mesh_poses.extend(source_collision_object.mesh_poses) 83 | sink_collision_object.planes.extend(source_collision_object.planes) 84 | sink_collision_object.plane_poses.extend(source_collision_object.plane_poses) 85 | 86 | 87 | def is_ignored(model): 88 | model_full_name = model.get_full_name() 89 | for ignored_submodel in ignored_submodels: 90 | if model_full_name == ignored_submodel or model_full_name.endswith('::' + ignored_submodel): 91 | return True 92 | return False 93 | 94 | 95 | def get_root_collision_model(link): 96 | # Travere model tree upward until either at the root or an ignored submodel is found which is origin of collision object. 97 | model = link.parent_model 98 | while True: 99 | if not model.parent_model: 100 | break 101 | elif is_ignored(model): 102 | break 103 | model = model.parent_model 104 | return model 105 | 106 | 107 | def link_to_collision_object(link, full_linkname): 108 | supported_geometry_types = ['mesh', 'cylinder', 'sphere', 'box'] 109 | linkparts = getattr(link, 'collisions') 110 | 111 | if is_ignored(link.parent_model): 112 | print("Ignoring link %s." % full_linkname) 113 | return 114 | 115 | collision_object = CollisionObject() 116 | collision_object.header.frame_id = pysdf.sdf2tfname(full_linkname) 117 | root_collision_model = get_root_collision_model(link) 118 | link_pose_in_root_frame = pysdf.homogeneous2pose_msg(concatenate_matrices(link.pose_world, inverse_matrix(root_collision_model.pose_world))) 119 | 120 | for linkpart in linkparts: 121 | if linkpart.geometry_type not in supported_geometry_types: 122 | print("Element %s with geometry type %s not supported. Ignored." % (full_linkname, linkpart.geometry_type)) 123 | continue 124 | 125 | if linkpart.geometry_type == 'mesh': 126 | scale = tuple(float(val) for val in linkpart.geometry_data['scale'].split()) 127 | for models_path in pysdf.models_paths: 128 | test_mesh_path = linkpart.geometry_data['uri'].replace('model://', models_path) 129 | if os.path.isfile(test_mesh_path): 130 | mesh_path = test_mesh_path 131 | break 132 | if mesh_path: 133 | link_pose_stamped = PoseStamped() 134 | link_pose_stamped.pose = link_pose_in_root_frame 135 | make_mesh(collision_object, full_linkname, link_pose_stamped, mesh_path, scale) 136 | else: 137 | print("ERROR: No mesh found for '%s' in element '%s'." % (linkpart.geometry_data['uri'], full_linkname)) 138 | elif linkpart.geometry_type == 'box': 139 | scale = tuple(float(val) for val in linkpart.geometry_data['size'].split()) 140 | box = SolidPrimitive() 141 | box.type = SolidPrimitive.BOX 142 | box.dimensions = scale 143 | collision_object.primitives.append(box) 144 | collision_object.primitive_poses.append(link_pose_in_root_frame) 145 | elif linkpart.geometry_type == 'sphere': 146 | sphere = SolidPrimitive() 147 | sphere.type = SolidPrimitive.SPHERE 148 | sphere.dimensions = 2.0 * float(linkpart.geometry_data['radius']) 149 | collision_object.primitives.append(sphere) 150 | collision_object.primitive_poses.append(link_pose_in_root_frame) 151 | elif linkpart.geometry_type == 'cylinder': 152 | cylinder = SolidPrimitive() 153 | cylinder.type = SolidPrimitive.CYLINDER 154 | cylinder.dimensions = tuple((2.0 * float(linkpart.geometry_data['radius']), float(linkpart.geometry_data['length']))) 155 | collision_object.primitives.append(cylinder) 156 | collision_object.primitive_poses.append(link_pose_in_root_frame) 157 | 158 | #print('CollisionObject for %s:\n%s' % (full_linkname, collision_object)) 159 | return collision_object 160 | 161 | 162 | def convert_to_collision_object(link, full_linkname): 163 | collision_object = link_to_collision_object(link, full_linkname) 164 | if not collision_object: 165 | return 166 | 167 | link_root = get_root_collision_model(link).root_link.name 168 | if link_root not in collision_objects: 169 | collision_objects[link_root] = CollisionObject() 170 | collision_objects[link_root].id = link_root 171 | collision_objects[link_root].operation = CollisionObject.ADD 172 | append_to_collision_object(collision_objects[link_root], collision_object) 173 | 174 | 175 | 176 | def main(): 177 | parser = argparse.ArgumentParser() 178 | #parser.add_argument('-f', '--freq', type=float, default=2, help='Frequency Markers are published (default: 2 Hz)') 179 | #parser.add_argument('-p', '--prefix', default='', help='Publish with prefix') 180 | parser.add_argument('sdf', help='SDF model to publish (e.g. coke_can)') 181 | args = parser.parse_args(rospy.myargv()[1:]) 182 | 183 | rospy.init_node('sdf2moveit_collision') 184 | 185 | global ignored_submodels 186 | ignored_submodels = rospy.get_param('~ignore_submodels', '').split(';') 187 | rospy.loginfo('Ignoring submodels of: %s' % ignored_submodels) 188 | 189 | planning_scene_pub = rospy.Publisher('/planning_scene', PlanningScene, queue_size=10) 190 | 191 | sdf = pysdf.SDF(model=args.sdf) 192 | world = sdf.world 193 | 194 | world.for_all_links(convert_to_collision_object) 195 | planning_scene_msg = PlanningScene() 196 | planning_scene_msg.is_diff = True 197 | for (collision_object_root, collision_object) in collision_objects.iteritems(): 198 | if collision_object_root in ignored_submodels: 199 | print('TODO2') # attached object instead of collision object 200 | else: 201 | planning_scene_msg.world.collision_objects.append(collision_object) 202 | planning_scene_msg.world.collision_objects[-1].header.frame_id = 'world' 203 | 204 | while planning_scene_pub.get_num_connections() < 1: 205 | rospy.sleep(0.1) 206 | planning_scene_pub.publish(planning_scene_msg) 207 | 208 | 209 | #rospy.loginfo('Spinning') 210 | #r = rospy.Rate(args.freq) 211 | #while not rospy.is_shutdown(): 212 | # publishMarkers(); 213 | # r.sleep() 214 | 215 | 216 | if __name__ == '__main__': 217 | main() 218 | --------------------------------------------------------------------------------