pick_and_place.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 """
4  pick_and_place.py - Version 0.1 2014-08-01
5 
6  Command the gripper to grasp a target object and move it to a new
7  location, all while avoiding simulated obstacles.
8 
9  Before running, set environment variable TURTLEBOT_ARM1 to either:
10  turtlebot - for original turtlebot arm
11  pincher - for PhantomX Pincher arm
12 
13  Created for the Pi Robot Project: http://www.pirobot.org
14  Copyright (c) 2014 Patrick Goebel. All rights reserved.
15 
16  Adapted to the Turtlebot arm by Jorge Santos
17 
18  This program is free software; you can redistribute it and/or modify
19  it under the terms of the GNU General Public License as published by
20  the Free Software Foundation; either version 2 of the License, or
21  (at your option) any later version.5
22 
23  This program is distributed in the hope that it will be useful,
24  but WITHOUT ANY WARRANTY; without even the implied warranty of
25  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26  GNU General Public License for more details at:
27 
28  http://www.gnu.org/licenses/gpl.html
29 """
30 
31 import sys
32 import rospy
33 import numpy as np
34 import tf2_ros
35 import tf2_geometry_msgs
36 import moveit_commander
37 
38 from math import atan2
39 from copy import deepcopy
40 from geometry_msgs.msg import Pose, PoseStamped
41 from moveit_msgs.msg import PlanningScene, ObjectColor
42 from moveit_msgs.msg import CollisionObject, AttachedCollisionObject
43 from moveit_msgs.msg import Grasp, GripperTranslation
44 from moveit_msgs.msg import MoveItErrorCodes
45 from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint
46 from moveit_commander import MoveGroupCommander, PlanningSceneInterface
47 from tf.transformations import quaternion_from_euler, quaternion_matrix, quaternion_from_matrix
48 
49 GROUP_NAME_ARM = 'arm'
50 GROUP_NAME_GRIPPER = 'gripper'
51 
52 GRIPPER_FRAME = 'gripper_link'
53 GRIPPER_JOINT_NAMES = ['gripper_joint']
54 GRIPPER_EFFORT = [1.0]
55 GRIPPER_PARAM = '/gripper_controller'
56 
57 REFERENCE_FRAME = 'base_link'
58 ARM_BASE_FRAME = 'arm_base_link'
59 
60 class MoveItDemo:
61  def __init__(self):
62  # Initialize the move_group API
63  moveit_commander.roscpp_initialize(sys.argv)
64 
65  rospy.init_node('moveit_demo')
66 
67  # We need a tf2 listener to convert poses into arm reference base
68  try:
71  except rospy.ROSException as err:
72  rospy.logerr("MoveItDemo: could not start tf buffer client: " + str(err))
73  raise err
74 
75  self.gripper_opened = [rospy.get_param(GRIPPER_PARAM + "/max_opening") - 0.01]
76  self.gripper_closed = [rospy.get_param(GRIPPER_PARAM + "/min_opening") + 0.01]
77  self.gripper_neutral = [rospy.get_param(GRIPPER_PARAM + "/neutral",
78  (self.gripper_opened[0] + self.gripper_closed[0])/2.0) ]
79 
80  self.gripper_tighten = rospy.get_param(GRIPPER_PARAM + "/tighten", 0.0)
81 
82  # Use the planning scene object to add or remove objects
83  self.scene = PlanningSceneInterface()
84 
85  # Create a scene publisher to push changes to the scene
86  self.scene_pub = rospy.Publisher('planning_scene', PlanningScene, queue_size=10)
87 
88  # Create a publisher for displaying gripper poses
89  self.gripper_pose_pub = rospy.Publisher('target_pose', PoseStamped, queue_size=10)
90 
91  # Create a dictionary to hold object colors
92  self.colors = dict()
93 
94  # Initialize the move group for the right arm
95  arm = MoveGroupCommander(GROUP_NAME_ARM)
96 
97  # Initialize the move group for the right gripper
98  gripper = MoveGroupCommander(GROUP_NAME_GRIPPER)
99 
100  # Get the name of the end-effector link
101  end_effector_link = arm.get_end_effector_link()
102 
103  # Allow some leeway in position (meters) and orientation (radians)
104  arm.set_goal_position_tolerance(0.04)
105  arm.set_goal_orientation_tolerance(0.01)
106 
107  # Allow replanning to increase the odds of a solution
108  arm.allow_replanning(True)
109 
110  # Set the right arm reference frame
111  arm.set_pose_reference_frame(REFERENCE_FRAME)
112 
113  # Allow 5 seconds per planning attempt
114  arm.set_planning_time(5)
115 
116  # Set a limit on the number of pick attempts before bailing
117  max_pick_attempts = 3
118 
119  # Set a limit on the number of place attempts
120  max_place_attempts = 3
121  rospy.loginfo("Scaling for MoveIt timeout=" + str(rospy.get_param('/move_group/trajectory_execution/allowed_execution_duration_scaling')))
122 
123  # Give each of the scene objects a unique name
124  table_id = 'table'
125  box1_id = 'box1'
126  box2_id = 'box2'
127  target_id = 'target'
128  tool_id = 'tool'
129 
130  # Remove leftover objects from a previous run
131  self.scene.remove_world_object(table_id)
132  self.scene.remove_world_object(box1_id)
133  self.scene.remove_world_object(box2_id)
134  self.scene.remove_world_object(target_id)
135  self.scene.remove_world_object(tool_id)
136 
137  # Remove any attached objects from a previous session
138  self.scene.remove_attached_object(GRIPPER_FRAME, target_id)
139 
140  # Give the scene a chance to catch up
141  rospy.sleep(1)
142 
143  # Start the arm in the "arm_up" pose stored in the SRDF file
144  rospy.loginfo("Set Arm: right_up")
145  arm.set_named_target('right_up')
146  if arm.go() != True:
147  rospy.logwarn(" Go failed")
148 
149  # Move the gripper to the open position
150  rospy.loginfo("Set Gripper: Open " + str(self.gripper_opened))
151  gripper.set_joint_value_target(self.gripper_opened)
152  if gripper.go() != True:
153  rospy.logwarn(" Go failed")
154 
155  # Set the height of the table off the ground
156  table_ground = 0.4
157 
158  # Set the dimensions of the scene objects [l, w, h]
159  table_size = [0.2, 0.7, 0.01]
160  box1_size = [0.1, 0.05, 0.05]
161  box2_size = [0.05, 0.05, 0.15]
162 
163  # Set the target size [l, w, h]
164  target_size = [0.02, 0.005, 0.12]
165 
166  # Add a table top and two boxes to the scene
167  table_pose = PoseStamped()
168  table_pose.header.frame_id = REFERENCE_FRAME
169  table_pose.pose.position.x = 0.36
170  table_pose.pose.position.y = 0.0
171  table_pose.pose.position.z = table_ground + table_size[2] / 2.0
172  table_pose.pose.orientation.w = 1.0
173  self.scene.add_box(table_id, table_pose, table_size)
174 
175  box1_pose = PoseStamped()
176  box1_pose.header.frame_id = REFERENCE_FRAME
177  box1_pose.pose.position.x = table_pose.pose.position.x - 0.04
178  box1_pose.pose.position.y = 0.0
179  box1_pose.pose.position.z = table_ground + table_size[2] + box1_size[2] / 2.0
180  box1_pose.pose.orientation.w = 1.0
181  self.scene.add_box(box1_id, box1_pose, box1_size)
182 
183  box2_pose = PoseStamped()
184  box2_pose.header.frame_id = REFERENCE_FRAME
185  box2_pose.pose.position.x = table_pose.pose.position.x - 0.06
186  box2_pose.pose.position.y = 0.2
187  box2_pose.pose.position.z = table_ground + table_size[2] + box2_size[2] / 2.0
188  box2_pose.pose.orientation.w = 1.0
189  self.scene.add_box(box2_id, box2_pose, box2_size)
190 
191  # Set the target pose in between the boxes and on the table
192  target_pose = PoseStamped()
193  target_pose.header.frame_id = REFERENCE_FRAME
194  target_pose.pose.position.x = table_pose.pose.position.x - 0.03
195  target_pose.pose.position.y = 0.1
196  target_pose.pose.position.z = table_ground + table_size[2] + target_size[2] / 2.0
197  target_pose.pose.orientation.w = 1.0
198 
199  # Add the target object to the scene
200  self.scene.add_box(target_id, target_pose, target_size)
201 
202  # Make the table red and the boxes orange
203  self.setColor(table_id, 0.8, 0, 0, 1.0)
204  self.setColor(box1_id, 0.8, 0.4, 0, 1.0)
205  self.setColor(box2_id, 0.8, 0.4, 0, 1.0)
206 
207  # Make the target yellow
208  self.setColor(target_id, 0.9, 0.9, 0, 1.0)
209 
210  # Send the colors to the planning scene
211  self.sendColors()
212 
213  # Set the support surface name to the table object
214  arm.set_support_surface_name(table_id)
215 
216  # Specify a pose to place the target after being picked up
217  place_pose = PoseStamped()
218  place_pose.header.frame_id = REFERENCE_FRAME
219  place_pose.pose.position.x = table_pose.pose.position.x - 0.03
220  place_pose.pose.position.y = -0.15
221  place_pose.pose.position.z = table_ground + table_size[2] + target_size[2] / 2.0
222  place_pose.pose.orientation.w = 1.0
223 
224  # Initialize the grasp pose to the target pose
225  grasp_pose = target_pose
226 
227  # Shift the grasp pose by half the width of the target to center it
228  grasp_pose.pose.position.y -= target_size[1] / 2.0
229 
230  # Generate a list of grasps
231  grasps = self.make_grasps(grasp_pose, [target_id], [target_size[1] - self.gripper_tighten])
232 
233  # Track success/failure and number of attempts for pick operation
234  result = MoveItErrorCodes.FAILURE
235  n_attempts = 0
236 
237  # Repeat until we succeed or run out of attempts
238  while result != MoveItErrorCodes.SUCCESS and n_attempts < max_pick_attempts:
239  rospy.loginfo("Pick attempt #" + str(n_attempts))
240  for grasp in grasps:
241  # Publish the grasp poses so they can be viewed in RViz
242  self.gripper_pose_pub.publish(grasp.grasp_pose)
243  rospy.sleep(0.2)
244 
245  result = arm.pick(target_id, grasps)
246  if result == MoveItErrorCodes.SUCCESS:
247  break
248 
249  n_attempts += 1
250  rospy.sleep(0.2)
251 
252  # If the pick was successful, attempt the place operation
253  if result == MoveItErrorCodes.SUCCESS:
254  rospy.loginfo(" Pick: Done!")
255  # Generate valid place poses
256  places = self.make_places(target_id, place_pose)
257 
258  success = False
259  n_attempts = 0
260 
261  # Repeat until we succeed or run out of attempts
262  while not success and n_attempts < max_place_attempts:
263  rospy.loginfo("Place attempt #" + str(n_attempts))
264  for place in places:
265  # Publish the place poses so they can be viewed in RViz
266  self.gripper_pose_pub.publish(place)
267  rospy.sleep(0.2)
268 
269  success = arm.place(target_id, place)
270  if success:
271  break
272 
273  n_attempts += 1
274  rospy.sleep(0.2)
275 
276  if not success:
277  rospy.logerr("Place operation failed after " + str(n_attempts) + " attempts.")
278  else:
279  rospy.loginfo(" Place: Done!")
280  else:
281  rospy.logerr("Pick operation failed after " + str(n_attempts) + " attempts.")
282 
283  # Return the arm to the "resting" pose stored in the SRDF file (passing through right_up)
284  arm.set_named_target('right_up')
285  arm.go()
286 
287  arm.set_named_target('resting')
288  arm.go()
289 
290  # Open the gripper to the neutral position
291  gripper.set_joint_value_target(self.gripper_neutral)
292  gripper.go()
293 
294  rospy.sleep(1)
295 
296  # Shut down MoveIt cleanly
297  moveit_commander.roscpp_shutdown()
298 
299  # Exit the script
300  moveit_commander.os._exit(0)
301 
302  # Get the gripper posture as a JointTrajectory
303  def make_gripper_posture(self, joint_positions):
304  # Initialize the joint trajectory for the gripper joints
305  t = JointTrajectory()
306 
307  # Set the joint names to the gripper joint names
308  t.header.stamp = rospy.get_rostime()
309  t.joint_names = GRIPPER_JOINT_NAMES
310 
311  # Initialize a joint trajectory point to represent the goal
312  tp = JointTrajectoryPoint()
313 
314  # Assign the trajectory joint positions to the input positions
315  tp.positions = joint_positions
316 
317  # Set the gripper effort
318  tp.effort = GRIPPER_EFFORT
319 
320  tp.time_from_start = rospy.Duration(0.0)
321 
322  # Append the goal point to the trajectory points
323  t.points.append(tp)
324 
325  # Return the joint trajectory
326  return t
327 
328  # Generate a gripper translation in the direction given by vector
329  def make_gripper_translation(self, min_dist, desired, vector):
330  # Initialize the gripper translation object
331  g = GripperTranslation()
332 
333  # Set the direction vector components to the input
334  g.direction.vector.x = vector[0]
335  g.direction.vector.y = vector[1]
336  g.direction.vector.z = vector[2]
337 
338  # The vector is relative to the gripper frame
339  g.direction.header.frame_id = GRIPPER_FRAME
340 
341  # Assign the min and desired distances from the input
342  g.min_distance = min_dist
343  g.desired_distance = desired
344 
345  return g
346 
347  # Generate a list of possible grasps
348  def make_grasps(self, initial_pose_stamped, allowed_touch_objects, grasp_opening=[0]):
349  # Initialize the grasp object
350  g = Grasp()
351 
352  # Set the pre-grasp and grasp postures appropriately;
353  # grasp_opening should be a bit smaller than target width
354  g.pre_grasp_posture = self.make_gripper_posture(self.gripper_opened)
355  g.grasp_posture = self.make_gripper_posture(grasp_opening)
356 
357  # Set the approach and retreat parameters as desired
358  g.pre_grasp_approach = self.make_gripper_translation(0.01, 0.1, [1.0, 0.0, 0.0])
359  g.post_grasp_retreat = self.make_gripper_translation(0.1, 0.15, [0.0, -1.0, 1.0])
360 
361  # Set the first grasp pose to the input pose
362  g.grasp_pose = initial_pose_stamped
363 
364  # Pitch angles to try
365  pitch_vals = [0, 0.1, -0.1, 0.2, -0.2, 0.4, -0.4]
366 
367  # Yaw angles to try; given the limited dofs of turtlebot_arm, we must calculate the heading
368  # from arm base to the object to pick (first we must transform its pose to arm base frame)
369  target_pose_arm_ref = self._tf2_buff.transform(initial_pose_stamped, ARM_BASE_FRAME)
370  x = target_pose_arm_ref.pose.position.x
371  y = target_pose_arm_ref.pose.position.y
372  yaw_vals = [atan2(y, x) + inc for inc in [0, 0.1,-0.1]]
373 
374  # A list to hold the grasps
375  grasps = []
376 
377  # Generate a grasp for each pitch and yaw angle
378  for yaw in yaw_vals:
379  for pitch in pitch_vals:
380  # Create a quaternion from the Euler angles
381  q = quaternion_from_euler(0, pitch, yaw)
382 
383  # Set the grasp pose orientation accordingly
384  g.grasp_pose.pose.orientation.x = q[0]
385  g.grasp_pose.pose.orientation.y = q[1]
386  g.grasp_pose.pose.orientation.z = q[2]
387  g.grasp_pose.pose.orientation.w = q[3]
388 
389  # Set and id for this grasp (simply needs to be unique)
390  g.id = str(len(grasps))
391 
392  # Set the allowed touch objects to the input list
393  g.allowed_touch_objects = allowed_touch_objects
394 
395  # Don't restrict contact force
396  g.max_contact_force = 0
397 
398  # Degrade grasp quality for increasing pitch angles
399  g.grasp_quality = 1.0 - abs(pitch)
400 
401  # Append the grasp to the list
402  grasps.append(deepcopy(g))
403 
404  # Return the list
405  return grasps
406 
407  # Generate a list of possible place poses
408  def make_places(self, target_id, init_pose):
409  # Initialize the place location as a PoseStamped message
410  place = PoseStamped()
411 
412  # Start with the input place pose
413  place = init_pose
414 
415  # A list of x shifts (meters) to try
416  x_vals = [0, 0.005, -0.005] #, 0.01, -0.01, 0.015, -0.015]
417 
418  # A list of y shifts (meters) to try
419  y_vals = [0, 0.005, -0.005, 0.01, -0.01] #, 0.015, -0.015]
420 
421  # A list of pitch angles to try
422  pitch_vals = [0] #, 0.005, -0.005, 0.01, -0.01, 0.02, -0.02]
423 
424  # A list to hold the places
425  places = []
426 
427  # Generate a place pose for each angle and translation
428  for pitch in pitch_vals:
429  for dy in y_vals:
430  for dx in x_vals:
431  place.pose.position.x = init_pose.pose.position.x + dx
432  place.pose.position.y = init_pose.pose.position.y + dy
433 
434  # Yaw angle: given the limited dofs of turtlebot_arm, we must calculate the heading from
435  # arm base to the place location (first we must transform its pose to arm base frame)
436  target_pose_arm_ref = self._tf2_buff.transform(place, ARM_BASE_FRAME)
437  x = target_pose_arm_ref.pose.position.x
438  y = target_pose_arm_ref.pose.position.y
439  yaw = atan2(y, x)
440 
441  # Create a quaternion from the Euler angles
442  q = quaternion_from_euler(0, pitch, yaw)
443 
444  # Set the place pose orientation accordingly
445  place.pose.orientation.x = q[0]
446  place.pose.orientation.y = q[1]
447  place.pose.orientation.z = q[2]
448  place.pose.orientation.w = q[3]
449 
450  # MoveGroup::place will transform the provided place pose with the attached body pose, so the object retains
451  # the orientation it had when picked. However, with our 4-dofs arm this is infeasible (nor we care about the
452  # objects orientation!), so we cancel this transformation. It is applied here:
453  # https://github.com/ros-planning/moveit_ros/blob/jade-devel/manipulation/pick_place/src/place.cpp#L64
454  # More details on this issue: https://github.com/ros-planning/moveit_ros/issues/577
455  acobjs = self.scene.get_attached_objects([target_id])
456  aco_pose = self.get_pose(acobjs[target_id])
457  if aco_pose is None:
458  rospy.logerr("Attached collision object '%s' not found" % target_id)
459  return None
460 
461  aco_tf = self.pose_to_mat(aco_pose)
462  place_tf = self.pose_to_mat(place.pose)
463  place.pose = self.mat_to_pose(place_tf, aco_tf)
464  rospy.logdebug("Compensate place pose with the attached object pose [%s]. Results: [%s]" \
465  % (aco_pose, place.pose))
466 
467  # Append this place pose to the list
468  places.append(deepcopy(place))
469 
470  # Return the list
471  return places
472 
473  # Set the color of an object
474  def setColor(self, name, r, g, b, a=0.9):
475  # Initialize a MoveIt color object
476  color = ObjectColor()
477 
478  # Set the id to the name given as an argument
479  color.id = name
480 
481  # Set the rgb and alpha values given as input
482  color.color.r = r
483  color.color.g = g
484  color.color.b = b
485  color.color.a = a
486 
487  # Update the global color dictionary
488  self.colors[name] = color
489 
490  # Actually send the colors to MoveIt!
491  def sendColors(self):
492  # Initialize a planning scene object
493  p = PlanningScene()
494 
495  # Need to publish a planning scene diff
496  p.is_diff = True
497 
498  # Append the colors from the global color dictionary
499  for color in self.colors.values():
500  p.object_colors.append(color)
501 
502  # Publish the scene diff
503  self.scene_pub.publish(p)
504 
505  def get_pose(self, co):
506  # We get object's pose from the mesh/primitive poses; try first with the meshes
507  if isinstance(co, CollisionObject):
508  if co.mesh_poses:
509  return co.mesh_poses[0]
510  elif co.primitive_poses:
511  return co.primitive_poses[0]
512  else:
513  rospy.logerr("Collision object '%s' has no mesh/primitive poses" % co.id)
514  return None
515  elif isinstance(co, AttachedCollisionObject):
516  if co.object.mesh_poses:
517  return co.object.mesh_poses[0]
518  elif co.object.primitive_poses:
519  return co.object.primitive_poses[0]
520  else:
521  rospy.logerr("Attached collision object '%s' has no mesh/primitive poses" % co.id)
522  return None
523  else:
524  rospy.logerr("Input parameter is not a collision object")
525  return None
526 
527  def pose_to_mat(self, pose):
528  '''Convert a pose message to a 4x4 numpy matrix.
529 
530  Args:
531  pose (geometry_msgs.msg.Pose): Pose rospy message class.
532 
533  Returns:
534  mat (numpy.matrix): 4x4 numpy matrix
535  '''
536  quat = [pose.orientation.x, pose.orientation.y, pose.orientation.z, pose.orientation.w]
537  pos = np.matrix([pose.position.x, pose.position.y, pose.position.z]).T
538  mat = np.matrix(quaternion_matrix(quat))
539  mat[0:3, 3] = pos
540  return mat
541 
542  def mat_to_pose(self, mat, transform=None):
543  '''Convert a homogeneous matrix to a Pose message, optionally premultiply by a transform.
544 
545  Args:
546  mat (numpy.ndarray): 4x4 array (or matrix) representing a homogenous transform.
547  transform (numpy.ndarray): Optional 4x4 array representing additional transform
548 
549  Returns:
550  pose (geometry_msgs.msg.Pose): Pose message representing transform.
551  '''
552  if transform != None:
553  mat = np.dot(mat, transform)
554  pose = Pose()
555  pose.position.x = mat[0,3]
556  pose.position.y = mat[1,3]
557  pose.position.z = mat[2,3]
558  quat = quaternion_from_matrix(mat)
559  pose.orientation.x = quat[0]
560  pose.orientation.y = quat[1]
561  pose.orientation.z = quat[2]
562  pose.orientation.w = quat[3]
563  return pose
564 
565 if __name__ == "__main__":
566  MoveItDemo()
def make_gripper_posture(self, joint_positions)
def setColor(self, name, r, g, b, a=0.9)
def pose_to_mat(self, pose)
def make_gripper_translation(self, min_dist, desired, vector)
def mat_to_pose(self, mat, transform=None)
def make_grasps(self, initial_pose_stamped, allowed_touch_objects, grasp_opening=[0])
def make_places(self, target_id, init_pose)


turtlebot_arm_moveit_demos
Author(s): Jorge Santos
autogenerated on Fri Feb 7 2020 03:56:37