Skip to content

Commit f94a7a0

Browse files
author
andrewluiqut
committed
fixed the asset file loading issue add_object_to_scene in the arm_commander, both are accepting the same formats including absolute file path, relative package file path, and file, package, http uri. Removed the pycache file under arm_commander/tools from git cache. Added custom transform function. Updated the documentation accordingly.
1 parent 21ee051 commit f94a7a0

9 files changed

Lines changed: 223 additions & 31 deletions

arm_commander/commander_moveit.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
__status__ = 'Development'
1515

1616
import sys, copy, threading, time, signal, math, traceback, timeit, numbers, logging
17+
from collections import defaultdict
1718
import rospy, tf
1819
from tf2_msgs.msg import TFMessage
1920
import moveit_commander
@@ -26,8 +27,10 @@
2627
from controller_manager_msgs.srv import SwitchController
2728

2829
from arm_commander.tools.moveit_tools import MOVEIT_ERROR_CODE_MAP, GOAL_STATUS_MAP
30+
from arm_commander.tools.rospkg_tools import PackageFile
2931
from arm_commander.states import GeneralCommanderStates, ControllerState
3032

33+
3134
class GeneralCommander():
3235
"""
3336
GeneralCommander: an interface for robot commander, with this class specifically interfacing
@@ -93,6 +96,8 @@ def __init__(self, moveit_group_name:str, world_link:str=None) -> None:
9396
self.wall_name_list = []
9497
# record the collision objects, including the walls, added to the commander and their color
9598
self.object_name_color_list = {}
99+
# setup custom transforms
100+
self.custom_transform_object_dict = defaultdict(lambda: None)
96101
# publisher for the planning scene
97102
self.scene_pub = rospy.Publisher('planning_scene', PlanningScene, queue_size=10)
98103
self.tf_pub = tf.TransformBroadcaster()
@@ -124,14 +129,20 @@ def _pub_objects_color(self):
124129
color = self._set_object_color(object_name, *rgba)
125130
p.object_colors.append(color)
126131
self.scene_pub.publish(p)
127-
132+
128133
# publish the transform of the collision objects
129134
def _pub_transform_all_objects(self):
135+
# publish the collision objects
130136
objects = self.scene.get_objects()
131137
for object_name in objects.keys():
132138
pose_of_object = objects[object_name].pose
133139
frame = objects[object_name].header.frame_id
134140
self._pub_transform_object(object_name, pose_of_object, frame)
141+
# publish the custom transform objects
142+
for name in self.custom_transform_object_dict:
143+
the_transform = self.custom_transform_object_dict[name]
144+
xyzrpy = the_transform['xyzrpy']
145+
self._pub_transform_object(name, xyzrpy, the_transform['frame'])
135146

136147
# publish the transform of a specific named object
137148
def _pub_transform_object(self, name, pose, frame=None) -> None:
@@ -1084,6 +1095,11 @@ def add_object_to_scene(self, object_name:str, model_file:str, object_scale:list
10841095
reference_frame = self.WORLD_REFERENCE_LINK if reference_frame is None else reference_frame
10851096
self.scene.remove_world_object(object_name)
10861097
object_pose = conversions.list_to_pose_stamped(xyz + rpy, reference_frame)
1098+
try:
1099+
model_file = PackageFile.resolve_to_path(model_file)
1100+
except Exception as ex:
1101+
logger.warning(f'SceneToRViz (display_objects): Invalid model_file for object ({model_file}): {ex}')
1102+
return
10871103
self.scene.add_mesh(
10881104
object_name, object_pose,
10891105
model_file, object_scale
@@ -1297,6 +1313,49 @@ def _set_object_color(self, name, r, g, b, a = 0.9) -> ObjectColor:
12971313
color.color.a = a
12981314
return color
12991315

1316+
# --------------------------------
1317+
# functions for adding custom transforms
1318+
def add_custom_transform(self, name, xyz, rpy, parent_frame=None) -> None:
1319+
""" Add a custom transform to the rviz visualizer, which is broadcast regularly
1320+
1321+
:param name: the name of the transform
1322+
:param xyz: the xyz pose
1323+
:param rpy: the rpy pose
1324+
:param parent_frame: the reference frame
1325+
"""
1326+
if name is None or xyz is None or rpy is None:
1327+
logger.error(f'SceneToRViz (add_custom_transform): No parameter can be None')
1328+
raise AssertionError('A parameter is none')
1329+
self.custom_transform_object_dict[name] = {'xyzrpy': xyz + rpy, 'frame': parent_frame}
1330+
1331+
def update_custom_transform_pose(self, name, xyz=None, rpy=None, parent_frame=None) -> None:
1332+
""" Update the pose of a custom transform
1333+
1334+
:param name: the name of the transform to be updated
1335+
:param xyz: the updated xyz, defaults to None meaning unchanged
1336+
:param rpy: the updated rpy, defaults to None meaning unchanged
1337+
:param frame: the updated frame, defaults to None meaning unchanged
1338+
"""
1339+
transform_object = self.custom_transform_object_dict[name]
1340+
if transform_object is None:
1341+
logger.warning(f'__name__ (update_custom_transform_pose): the custom transform name {name} is not found')
1342+
return False
1343+
xyz = xyz if xyz is not None else transform_object['xyzrpy'][:3]
1344+
rpy = rpy if rpy is not None else transform_object['xyzrpy'][3:]
1345+
parent_frame = parent_frame if xyz is not None else transform_object['frame']
1346+
self.add_custom_transform(name, xyz, rpy, parent_frame)
1347+
1348+
def remove_custom_transform(self, name=None):
1349+
""" Clear a custom transform or all if name is None
1350+
1351+
:param name: the name of the transform to be removed, or None to remove all
1352+
"""
1353+
if name is None:
1354+
self.custom_transform_object_dict.clear()
1355+
elif name in self.custom_transform_object_dict:
1356+
del self.custom_transform_object_dict[name]
1357+
1358+
13001359
# --------------------------------
13011360
# functions for setting path constraints
13021361

-169 Bytes
Binary file not shown.
-6.77 KB
Binary file not shown.
-7.74 KB
Binary file not shown.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Copyright 2024 - Andrew Kwok Fai LUI,
2+
# Robotics and Autonomous Systems Group, REF, RI
3+
# and the Queensland University of Technology
4+
5+
__author__ = 'Andrew Lui'
6+
__copyright__ = 'Copyright 2024'
7+
__license__ = 'GPL'
8+
__version__ = '1.0'
9+
__email__ = 'ak.lui@qut.edu.au'
10+
__status__ = 'Development'
11+
12+
import math, os
13+
import rospy, tf, rospkg
14+
import tf.transformations
15+
from geometry_msgs.msg import Pose, PoseStamped, Point, Quaternion
16+
17+
class PackageFile():
18+
""" The class provides tools for dealing with the uri for ros resource retriever
19+
"""
20+
21+
def resolve_to_file_uri(uri_or_path:str) -> str:
22+
""" Converts a uri or a file path into a uri starting with file://
23+
24+
:param uri_or_path: a file, package or http uri or a full or package relative file path
25+
:type uri_or_path: str
26+
:return: the file prototcol uri of type str
27+
"""
28+
if uri_or_path is None:
29+
raise Exception(f'Parameter (uri_or_path) is None')
30+
try:
31+
resolved_path = PackageFile._parse(uri_or_path)
32+
return 'file://' + resolved_path
33+
except:
34+
raise
35+
36+
def resolve_to_file_or_http_uri(uri_or_path:str) -> str:
37+
""" Converts a uri or a file path into a uri starting with file:// or http://
38+
39+
:param uri_or_path: a file, package or http uri or a full or package relative file path
40+
:type uri_or_path: str
41+
:return: the file prototcol uri
42+
:rtype: str
43+
"""
44+
if uri_or_path.startswith(('http://', 'https://')):
45+
return uri_or_path
46+
return PackageFile.resolve_to_file_uri(uri_or_path)
47+
48+
def resolve_to_path(uri_or_path:str) -> str:
49+
"""Converts a uri or a file path into a full local file path
50+
51+
:param uri_or_path: a file, package or http uri or a full or package relative file path
52+
:type uri_or_path: str
53+
:return: the absolute file path
54+
:rtype: str
55+
"""
56+
if uri_or_path is None:
57+
raise Exception(f'Parameter (uri_or_path) is None')
58+
try:
59+
resolved_path = PackageFile._parse(uri_or_path)
60+
return resolved_path
61+
except:
62+
raise
63+
64+
# -----------------------------------
65+
# internal function: searching if the relative path leads to an existing file
66+
def _search_rospkgs_for_file(path):
67+
ros_package = rospkg.RosPack()
68+
package_path_list = ros_package.get_ros_paths()
69+
for package_path in package_path_list:
70+
candidate_path = os.path.join(package_path, path)
71+
if os.path.isfile(candidate_path):
72+
return candidate_path
73+
return None
74+
75+
# internal function: parse the input uri or file path
76+
def _parse(uri_or_path):
77+
if uri_or_path.startswith('file://localhost'):
78+
path = uri_or_path[16:]
79+
elif uri_or_path.startswith('file://'):
80+
path = uri_or_path[7:]
81+
elif uri_or_path.startswith('package://'):
82+
path = uri_or_path[10:]
83+
elif uri_or_path.startswith(('http://', 'https://')):
84+
raise Exception(f'Unsupported protocol: http or https')
85+
elif uri_or_path.find('://') > 0:
86+
raise Exception(f'Unsupported protocol in uri: {uri_or_path}')
87+
else:
88+
path = uri_or_path
89+
if len(path) == 0:
90+
raise Exception(f'Malformed uri or path: empty path')
91+
if path[0] != '/': # path relative to package
92+
path = PackageFile._search_rospkgs_for_file(path)
93+
if path is None:
94+
raise Exception('No file is found by prepending ros package directories to the partial path')
95+
return path
96+
97+
if __name__ == '__main__':
98+
rospy.init_node('test_node')
99+
# test conversion functions
100+
tests = ['/home/qcr/arm_commander_ws/src/task_trees/demos/rviz_display/utah_teapot.stl',
101+
'task_trees/demos/rviz_display/utah_teapot.stl',
102+
'file:///home/qcr/arm_commander_ws/src/task_trees/demos/rviz_display/utah_teapot.stl',
103+
'file://localhost/home/qcr/arm_commander_ws/src/task_trees/demos/rviz_display/utah_teapot.stl',
104+
'package://task_trees/demos/rviz_display/utah_teapot.stl',
105+
]
106+
for test in tests:
107+
print(f'resolve_to_fileuri: {test}\n -> {PackageFile.resolve_to_file_uri(test)}')
108+
print(f'resolve_to_path: {test}\n -> {PackageFile.resolve_to_path(test)}')

docs/source/API_SUMMARY.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ The function `get_object` of the class `GeneralCommanderFactory` returns the __s
6868
### Example: Essential Startup Sequence in Applications
6969

7070
The following shows the essential `GeneralCommander` startup sequence in applications.
71-
```
71+
```python
7272
arm_commander: GeneralCommander = GeneralCommanderFactory.get_object('panda_arm')
7373
arm_commander.spin(spin_in_thread=True)
7474
arm_commander.wait_for_ready_to_move()
@@ -126,7 +126,7 @@ The following functions accepts multiple positions or poses in one move command.
126126
### Example: Issuing an Asynchronous Move Command
127127

128128
The following shows an example of issuing an asynchronous move command
129-
```
129+
```python
130130
arm_commander.move_to_position(x = -0.6, y = 0.2, wait=False)
131131
while True:
132132
the_state = arm_commander.get_commander_state()
@@ -138,7 +138,7 @@ arm_commander.reset_state()
138138
### Example: Issuing an Multi-Waypoints Move Command
139139

140140
The following shows an example of issuing a move command comprising multiple waypoints.
141-
```
141+
```python
142142
xyzrpy_list = [(0.6, 0.0, 0.4, 3.14, 0, 0),
143143
(0.6, 0.2, 0.5, 3.14, 0, 0),
144144
(0.6, 0.2, 0.6, 3.14, 0, 1.58)]
@@ -173,6 +173,23 @@ elif the_state in [GeneralCommanderStates.ABORTED, GeneralCommanderStates.ERROR]
173173
| attach_object_to_end_effector | The object name | |
174174
| detach_all_from_end_effector | The object name | |
175175

176+
The object mesh file accepts absolute file path or package relative file path.
177+
178+
#### Example: Adding a collision object
179+
180+
The following shows an example of issuing an asynchronous move command
181+
```python
182+
arm_commander.add_object_to_scene('the_teapot', '/path_to_file/utah_teapot.stl', [0.5, 1.2, 0.3], [0, 0, 3.14])
183+
```
184+
185+
### Custom Transform in the Scene
186+
187+
| Functions | Parameters | Remarks |
188+
| -------- | ---------- | ------- |
189+
| add_custom_transform | The name, xyz, rpy and the parent_frame of the custom transform | |
190+
| update_custom_transform_pose | The updated xyz, rpy or the parent frame of the named custom transform | the xyz, rpy, and the parent frame are optional|
191+
| remove_custom_transform| The name of the custom transform | |
192+
176193
### Path Constraints
177194

178195
| Functions | Parameters | Remarks |

0 commit comments

Comments
 (0)