Skip to content

Commit a963a38

Browse files
author
andrewluiqut
committed
moveit_tools.pose_to_xyzrpy fixed so that the parameter is Pose type instead of PosStamped. the workspace visualization can be switched off at set_workspace
. The walls and the collision objects are managed together and color can be specified for each object. A list of objects can be removed and the workspace walls can be cancelled. Fix a bug in commander_moveit publish transform object call to quaternion from euler.
1 parent 4c69230 commit a963a38

5 files changed

Lines changed: 94 additions & 77 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# The Arm Commander
2+
23
![QUT REF Collection](https://badgen.net/badge/collections/QUT%20REF-RAS?icon=github) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
34

45
**Robotics and Autonomous Systems Group, Research Engineering Facility, Research Infrastructure**

arm_commander/commander_moveit.py

Lines changed: 83 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,10 @@ def __init__(self, moveit_group_name:str, world_link:str=None) -> None:
9090
# internal states
9191
self.commander_state:GeneralCommanderStates = GeneralCommanderStates.INIT
9292
self.cached_result:MoveGroupActionResult = None
93-
# define the walls as the workspace limits
93+
# record the list of walls of the workspace
9494
self.wall_name_list = []
95-
95+
# record the collision objects, including the walls, added to the commander and their color
96+
self.object_name_color_list = {}
9697
# publisher for the planning scene
9798
self.scene_pub = rospy.Publisher('planning_scene', PlanningScene, queue_size=10)
9899
self.tf_pub = tf.TransformBroadcaster()
@@ -101,47 +102,73 @@ def __init__(self, moveit_group_name:str, world_link:str=None) -> None:
101102
self.the_constraints = Constraints()
102103
self.the_constraints.name = 'commander'
103104
# set timeout
104-
self.timer = rospy.Timer(rospy.Duration(1/self.TF_PUB_RATE), self._cb_timer)
105-
105+
self.timer = rospy.Timer(rospy.Duration(1/self.TF_PUB_RATE), self._cb_timer_tf)
106+
self.timer_scene = rospy.Timer(rospy.Duration(1.0), self._cb_timer_scene)
106107
# -----------------------------------------------------
107108
# -- Internal functions for publishing states and poses
108109

109-
# callback for waking up an IDLE robot agent
110-
def _cb_timer(self, event):
110+
# Internal functions: callback for publishing the object transformation and color of workspace
111+
def _cb_timer_tf(self, event):
111112
self._pub_transform_all_objects()
112113

113-
# publish colors of the wall objects
114-
def _pub_workspace_color(self):
114+
def _cb_timer_scene(self, event):
115+
self._pub_objects_color()
116+
117+
# publish colors of the other collision objects
118+
def _pub_objects_color(self):
115119
p = PlanningScene()
116120
p.is_diff = True
117-
for wall in self.wall_name_list:
118-
color = self._set_object_color(wall, 0.6, 0, 1, 0.2)
121+
for object_name in self.object_name_color_list.keys():
122+
rgba = self.object_name_color_list[object_name]
123+
if rgba is None:
124+
continue
125+
color = self._set_object_color(object_name, *rgba)
119126
p.object_colors.append(color)
120127
self.scene_pub.publish(p)
128+
121129

122130
# publish the transform of all added objects
131+
# def _pub_transform_all_objects(self):
132+
# object_list = self.scene.get_known_object_names()
133+
# object_poses = self.scene.get_object_poses(object_list)
134+
# for object_name in object_list:
135+
# pose_of_object = object_poses[object_name]
136+
# rospy.logwarn(f'_pub_transform_all_objects: {object_name} {type(pose_of_object)}')
137+
# self._pub_transform_object(object_name, pose_of_object)
138+
123139
def _pub_transform_all_objects(self):
124-
object_list = self.scene.get_known_object_names()
125-
object_poses = self.scene.get_object_poses(object_list)
126-
for object_name in object_list:
127-
pose_of_object = object_poses[object_name]
128-
self._pub_transform_object(object_name, pose_of_object)
140+
objects = self.scene.get_objects()
141+
for object_name in objects.keys():
142+
pose_of_object = objects[object_name].pose
143+
frame = objects[object_name].header.frame_id
144+
self._pub_transform_object(object_name, pose_of_object, frame)
129145

130146
# publish the transform of a specific named object
131-
def _pub_transform_object(self, name, pose):
147+
def _pub_transform_object(self, name, pose, frame=None):
148+
""" publish the transform of an object
149+
150+
:param name: name of the object
151+
:type name: str
152+
:param pose: the pose of the object
153+
:type pose: Pose, PoseStamped, list of 6 or 7
154+
:param frame: the frame against which the pose is defined, ignored if PoseStamped is provided, defaults to None
155+
:type frame: str, optional
156+
"""
132157
if type(pose) == Pose:
133158
pose = conversions.pose_to_list(pose)
134159
elif type(pose) == PoseStamped:
160+
frame = pose.header.frame_id
135161
pose = conversions.pose_to_list(pose.pose)
136162
xyz = pose[:3]
137163
if type(pose) != list:
138164
rospy.logerr(f'{__class__.__name__}: parameter (pose) is not list of length 6 or 7 or a Pose object -> fix the parameter at behaviour construction')
139165
raise TypeError(f'A parameter is invalid')
140166
if len(pose) == 6:
141-
q = tf.transformations.quaternion_from_euler([pose[3:]])
167+
q = tf.transformations.quaternion_from_euler(*pose[3:])
142168
elif len(pose) == 7:
143169
q = pose[3:]
144-
self.tf_pub.sendTransform(xyz, q, rospy.Time.now(), name, self.WORLD_REFERENCE_LINK)
170+
frame = self.WORLD_REFERENCE_LINK if frame is None else frame
171+
self.tf_pub.sendTransform(xyz, q, rospy.Time.now(), name, frame)
145172

146173
def _cb_move_group_result(self, msg:MoveGroupActionResult):
147174
rospy.loginfo(f'The commander (move_group result callback): {GOAL_STATUS_MAP[msg.status.status]} message: {MOVEIT_ERROR_CODE_MAP[msg.result.error_code.val]}')
@@ -1030,7 +1057,6 @@ def servo_robot(self, twist: Twist, frame: str = "", time: float = 0) -> bool:
10301057
def reset_world(self):
10311058
"""Remove all scene objects that have been added through the commander
10321059
"""
1033-
10341060
self.move_group.detach_object(self.END_EFFECTOR_LINK)
10351061
self.scene.remove_attached_object(self.END_EFFECTOR_LINK)
10361062
self.scene.remove_world_object()
@@ -1043,33 +1069,9 @@ def _wait_for_scene_update(self, func, timeout=5.0):
10431069
return
10441070
rospy.sleep(0.2)
10451071
rospy.logwarn(f'{__class__.__name__} (_wait_for_scene_update): timeout ({timeout} seconds)')
1046-
1047-
# # add an object (defined with a meshed model, pose, scale) to the scene
1048-
# def add_object_to_scene(self, object_name:str, model_file:str, object_scale:list, xyz:list, rpy:list):
1049-
# """Add an object (defined with a meshed model, pose, scale) to the scene for collision avoidance in path planning
1050-
1051-
# :param object_name: The name given to the new scene object
1052-
# :type object_name: str
1053-
# :param model_file: The path to the file that defines the mesh of the object
1054-
# :type model_file: str
1055-
# :param object_scale: The list of scale in 3 dimension
1056-
# :type object_scale: list
1057-
# :param xyz: The position of the object in the world/default reference frame
1058-
# :type xyz: list
1059-
# :param rpy: The orientation of the object in euler angles in the world/default reference frame
1060-
# :type rpy: list
1061-
# """
1062-
# self.scene.remove_world_object(object_name)
1063-
# object_pose = conversions.list_to_pose_stamped(xyz + rpy, self.WORLD_REFERENCE_LINK)
1064-
# self.scene.add_mesh(
1065-
# object_name, object_pose,
1066-
# model_file, object_scale
1067-
# )
1068-
# self._wait_for_scene_update(lambda: object_name in self.scene.get_known_object_names())
1069-
# self._pub_transform_object(object_name, object_pose)
10701072

10711073
# add an object (defined with a meshed model, pose, scale) to the scene
1072-
def add_object_to_scene(self, object_name:str, model_file:str, object_scale:list, xyz:list, rpy:list, reference_frame:str=None):
1074+
def add_object_to_scene(self, object_name:str, model_file:str, object_scale:list, xyz:list, rpy:list, reference_frame:str=None, rgba=None):
10731075
"""Add an object (defined with a meshed model, pose, scale) to the scene for collision avoidance in path planning
10741076
10751077
:param object_name: The name given to the new scene object
@@ -1094,10 +1096,11 @@ def add_object_to_scene(self, object_name:str, model_file:str, object_scale:list
10941096
model_file, object_scale
10951097
)
10961098
self._wait_for_scene_update(lambda: object_name in self.scene.get_known_object_names())
1099+
self.object_name_color_list[object_name] = rgba
10971100
self._pub_transform_object(object_name, object_pose)
10981101

10991102
# add an object (a shpere of given radius and position)
1100-
def add_sphere_to_scene(self, object_name:str, radius:float, xyz:list, reference_frame:str=None):
1103+
def add_sphere_to_scene(self, object_name:str, radius:float, xyz:list, reference_frame:str=None, rgba=None):
11011104
"""Add an object to the scene for collision avoidance in path planning
11021105
11031106
:param object_name: The name given to the new scene object
@@ -1114,10 +1117,11 @@ def add_sphere_to_scene(self, object_name:str, radius:float, xyz:list, reference
11141117
object_pose = conversions.list_to_pose_stamped(xyz + [0, 0, 0], reference_frame)
11151118
self.scene.add_sphere(object_name, object_pose, radius)
11161119
self._wait_for_scene_update(lambda: object_name in self.scene.get_known_object_names())
1120+
self.object_name_color_list[object_name] = rgba
11171121
self._pub_transform_object(object_name, object_pose)
11181122

11191123
# add a box (a box of given dimension, position and orientation)
1120-
def add_box_to_scene(self, object_name:str, dimensions:list, xyz:list, rpy:list=[0, 0, 0], reference_frame:str=None):
1124+
def add_box_to_scene(self, object_name:str, dimensions:list, xyz:list, rpy:list=[0, 0, 0], reference_frame:str=None, rgba=None):
11211125
""" Add a box to the scene for collision avoidance in path planning
11221126
11231127
:param object_name: The name given to the new scene object
@@ -1131,12 +1135,12 @@ def add_box_to_scene(self, object_name:str, dimensions:list, xyz:list, rpy:list=
11311135
:param reference_frame: The frame of reference of the xyz and rpy
11321136
:type reference_frame: str, default to WORLD_REFERENCE_LINK
11331137
"""
1134-
11351138
reference_frame = self.WORLD_REFERENCE_LINK if reference_frame is None else reference_frame
1136-
object_pose = conversions.list_to_pose_stamped(xyz + rpy, reference_frame)
1139+
object_pose = conversions.list_to_pose_stamped(xyz + rpy, reference_frame)
11371140
self.scene.add_box(object_name, object_pose, size=dimensions)
11381141
self._wait_for_scene_update(lambda: object_name in self.scene.get_known_object_names())
1139-
self._pub_transform_object(object_name, object_pose)
1142+
self.object_name_color_list[object_name] = rgba
1143+
self._pub_transform_object(object_name, object_pose, reference_frame)
11401144

11411145
# returns a list of current objects that have been added to the commander
11421146
""" Returns a list of current objects that have been added to the commander
@@ -1146,15 +1150,22 @@ def list_object_names(self) -> list:
11461150
return self.scene.get_known_object_names()
11471151

11481152
# remove object given the name
1149-
def remove_object(self, object_name:str) -> None:
1153+
def remove_object(self, object_name) -> None:
11501154
""" Remove object given the name or all objects in the scene if None
11511155
1152-
:param object_name: The name of the object, None for removing all objects
1156+
:param object_name: The name of the object, a list of names of multiple objects, or None for removing all objects
11531157
:type object_name: str
11541158
"""
1155-
1156-
self.scene.remove_world_object(object_name)
1157-
self._wait_for_scene_update(lambda: object_name not in self.scene.get_known_object_names())
1159+
if object_name is None:
1160+
self.scene.remove_world_object(object_name)
1161+
self.object_name_color_list.clear()
1162+
return
1163+
if type(object_name) not in [list, tuple]:
1164+
object_name = [object_name]
1165+
for name in object_name:
1166+
self.scene.remove_world_object(name)
1167+
self._wait_for_scene_update(lambda: name not in self.scene.get_known_object_names())
1168+
del self.object_name_color_list[name]
11581169

11591170
# attach an object to the end_effector
11601171
def attach_object_to_end_effector(self, object_name:str) -> bool:
@@ -1221,7 +1232,6 @@ def get_object_pose_as_xyzrpy(self, object_name:str, print=False) -> list:
12211232
:return: The pose of the queried object as a list of 6 floats (xyzrpy)
12221233
:rtype: list
12231234
"""
1224-
12251235
object_pose = self.get_object_pose(object_name)
12261236
object_pose_as_list = conversions.pose_to_list(object_pose)
12271237
xyzrpy = object_pose_as_list[:3]
@@ -1231,10 +1241,10 @@ def get_object_pose_as_xyzrpy(self, object_name:str, print=False) -> list:
12311241
return xyzrpy
12321242

12331243
# sets up the limits of the workspace using 6 walls
1234-
def set_workspace_walls(self, min_x:float, min_y:float, min_z:float, max_x:float, max_y:float, max_z:float):
1244+
def set_workspace_walls(self, min_x:float, min_y:float, min_z:float, max_x:float, max_y:float, max_z:float, rgba=[0.6, 0.0, 0.6, 0.05]):
12351245
"""A convenient function for setting up a workspace as a space surrounded by 6 walls
12361246
1237-
:param min_x: The minimum x of the workspace
1247+
:param min_x: The minimum x of the workspace, or None to remove the workspace walls
12381248
:type min_x: float
12391249
:param min_y: The minimum y of the workspace
12401250
:type min_y: float
@@ -1247,24 +1257,26 @@ def set_workspace_walls(self, min_x:float, min_y:float, min_z:float, max_x:float
12471257
:param max_z: The maximum z of the workspace
12481258
:type max_z: flost
12491259
"""
1260+
if min_x is None: # remove the walls
1261+
self._remove_all_walls()
1262+
return
12501263
thickness = 0.02
12511264
size_x = max_x - min_x
12521265
size_y = max_y - min_y
12531266
size_z = max_z - min_z
12541267
cx = (max_x + min_x) / 2
12551268
cy = (max_y + min_y) / 2
12561269
cz = (max_z + min_z) / 2
1257-
self._create_wall('ws_top', self.WORLD_REFERENCE_LINK, cx, cy, max_z, size_x, size_y, thickness)
1258-
self._create_wall('ws_bottom', self.WORLD_REFERENCE_LINK, cx, cy, min_z, size_x, size_y, thickness)
1259-
self._create_wall('ws_side_1', self.WORLD_REFERENCE_LINK, cx, min_y, cz, size_x, thickness, size_z)
1260-
self._create_wall('ws_side_2', self.WORLD_REFERENCE_LINK, cx, max_y, cz, size_x, thickness, size_z)
1261-
self._create_wall('ws_side_3', self.WORLD_REFERENCE_LINK, min_x, cy, cz, thickness, size_y, size_z)
1262-
self._create_wall('ws_side_4', self.WORLD_REFERENCE_LINK, max_x, cy, cz, thickness, size_y, size_z)
1270+
self._create_wall('ws_top', self.WORLD_REFERENCE_LINK, cx, cy, max_z, size_x, size_y, thickness, rgba)
1271+
self._create_wall('ws_bottom', self.WORLD_REFERENCE_LINK, cx, cy, min_z, size_x, size_y, thickness, rgba)
1272+
self._create_wall('ws_side_1', self.WORLD_REFERENCE_LINK, cx, min_y, cz, size_x, thickness, size_z, rgba)
1273+
self._create_wall('ws_side_2', self.WORLD_REFERENCE_LINK, cx, max_y, cz, size_x, thickness, size_z, rgba)
1274+
self._create_wall('ws_side_3', self.WORLD_REFERENCE_LINK, min_x, cy, cz, thickness, size_y, size_z, rgba)
1275+
self._create_wall('ws_side_4', self.WORLD_REFERENCE_LINK, max_x, cy, cz, thickness, size_y, size_z, rgba)
12631276
rospy.sleep(1.0) # wait for the walls to be registered
1264-
self._pub_workspace_color()
12651277

12661278
# internal function for creating a wall
1267-
def _create_wall(self, name, frame_id, x, y, z, size_x, size_y, size_z):
1279+
def _create_wall(self, name, frame_id, x, y, z, size_x, size_y, size_z, rgba=None):
12681280
self.scene.remove_world_object(name)
12691281
box_pose = PoseStamped()
12701282
box_pose.header.frame_id = frame_id
@@ -1275,6 +1287,12 @@ def _create_wall(self, name, frame_id, x, y, z, size_x, size_y, size_z):
12751287
self.scene.add_box(name, box_pose, size=(size_x, size_y, size_z))
12761288
if name not in self.wall_name_list:
12771289
self.wall_name_list.append(name)
1290+
self.object_name_color_list[name] = rgba
1291+
1292+
# internal function for removing all walls
1293+
def _remove_all_walls(self):
1294+
self.remove_object(self.wall_name_list)
1295+
self.wall_name_list.clear()
12781296

12791297
# internal function for setting the colour of a wall
12801298
def _set_object_color(self, name, r, g, b, a = 0.9):

arm_commander/moveit_tools.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,15 @@ def xyzrpy_to_pose_stamped(xyzrpy:list, reference_frame:str) -> PoseStamped:
122122
"""
123123
return conversions.list_to_pose_stamped(xyzrpy, target_frame=reference_frame)
124124

125-
def pose_stamped_to_xyzrpy(the_pose:PoseStamped) -> list:
126-
"""Convert a pose in a PoseStamped object to a (xyzrpy) list
125+
def pose_to_xyzrpy(the_pose:Pose) -> list:
126+
"""Convert a pose in a Pose object to a (xyzrpy) list
127127
128128
:param the_pose: The pose
129129
:type the_pose: PoseStamped
130130
:return: The pose in xyzrpy
131131
:rtype: list
132132
"""
133-
return conversions.pose_to_list(the_pose.pose)
133+
return conversions.pose_to_list(the_pose)
134134

135135
def create_pose(xyzrpy:list) -> Pose:
136136
""" Convert a pose in the form of xyzrpy (list) to a Pose object
@@ -144,7 +144,7 @@ def create_pose(xyzrpy:list) -> Pose:
144144

145145
# -----------------------------------------------
146146
# functions for creating Pose and PoseStamp objects
147-
def create_pose_stamped(xyzrpy:list, target_frame:str) -> PoseStamped:
147+
def create_pose_stamped(xyzrpy:list, reference_frame:str) -> PoseStamped:
148148
""" Convert a pose in the form of xyzrpy (list) to a PoseStamped object
149149
150150
:param xyzrpy: a list of 6 numbers
@@ -154,7 +154,7 @@ def create_pose_stamped(xyzrpy:list, target_frame:str) -> PoseStamped:
154154
:return: the created Pose object according to the parameter
155155
:rtype: Pose
156156
"""
157-
return xyzrpy_to_pose_stamped(xyzrpy, target_frame)
157+
return conversions.list_to_pose_stamped(xyzrpy, target_frame=reference_frame)
158158

159159
# -----------------------------------------------
160160
# functions for creating Constraints objects

docs/source/NOTES_ON_SPHINX.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,8 @@ sudo pip install m2r2
5858
```
5959

6060
## References
61+
[Deploy Sphinx documentation to Github Pages](https://coderefinery.github.io/documentation/gh_workflow/)
6162

63+
[Include Readme](https://stackoverflow.com/questions/46278683/include-my-markdown-readme-into-sphinx/68005314#68005314)
6264

63-
Deploy Sphinx documentation to Github Pages
64-
https://coderefinery.github.io/documentation/gh_workflow/
65-
66-
Include Readme
67-
https://stackoverflow.com/questions/46278683/include-my-markdown-readme-into-sphinx/68005314#68005314
65+
[Starter Workflow Examples](https://github.com/actions/starter-workflows/)

examples/commander_demo.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def cb_shutdown(self):
5151
sys.exit(0)
5252

5353
def stop(self, *args, **kwargs):
54-
self.arm_commander.abort_move(wait=True)
54+
self.arm_commander.abort_move(wait=False)
5555
self.arm_commander.clear_path_constraints()
5656
self.arm_commander.reset_world()
5757
sys.exit(0)
@@ -107,7 +107,7 @@ def test_1(self):
107107

108108
arm_commander.add_box_to_scene('the_box', self.demo_config['scene']['the_box']['dimensions'],
109109
self.demo_config['scene']['the_box']['position'],
110-
self.demo_config['scene']['the_box']['orientation'])
110+
self.demo_config['scene']['the_box']['orientation'], rgba=[1.0, 0.0, 0.0, 1.0])
111111
time.sleep(1.0)
112112

113113
# Move a displacement

0 commit comments

Comments
 (0)