00001 '''Represents a single 3D marker (in RViz) for controlling the Fetch arm.'''
00002
00003
00004
00005
00006
00007
00008 import rospy
00009
00010
00011 from numpy import array, linalg
00012 import threading
00013
00014
00015 from geometry_msgs.msg import Quaternion, Point, Pose, PoseStamped
00016 from std_msgs.msg import Header, ColorRGBA
00017 import tf
00018 from interactive_markers.interactive_marker_server import (
00019 InteractiveMarkerServer)
00020 from interactive_markers.menu_handler import MenuHandler
00021 from visualization_msgs.msg import (
00022 Marker, InteractiveMarker, InteractiveMarkerControl,
00023 InteractiveMarkerFeedback)
00024
00025
00026 from fetch_arm_control.msg import GripperState
00027
00028
00029
00030
00031
00032
00033 DEFAULT_OFFSET = 0.085
00034 COLOR_MESH_REACHABLE = ColorRGBA(1.0, 0.5, 0.0, 0.6)
00035 COLOR_MESH_UNREACHABLE = ColorRGBA(0.5, 0.5, 0.5, 0.6)
00036 STR_MESH_GRIPPER_FOLDER = 'package://fetch_description/meshes/'
00037 STR_GRIPPER_PALM_FILE = STR_MESH_GRIPPER_FOLDER + 'gripper_link.STL'
00038 STR_L_GRIPPER_FINGER_FILE = STR_MESH_GRIPPER_FOLDER + \
00039 'l_gripper_finger_link.STL'
00040 STR_R_GRIPPER_FINGER_FILE = STR_MESH_GRIPPER_FOLDER + \
00041 'r_gripper_finger_link.STL'
00042 INT_MARKER_SCALE = 0.2
00043 GRIPPER_MARKER_SCALE = 1.05
00044 REF_FRAME = 'base_link'
00045
00046
00047
00048
00049
00050 class ArmControlMarker:
00051 '''Marker for controlling arm of robot.'''
00052
00053 _im_server = None
00054
00055 def __init__(self, arm):
00056 '''
00057 Args:
00058 arm (Arm): Arm object for the robot's arm.
00059 '''
00060 if ArmControlMarker._im_server is None:
00061 im_server = InteractiveMarkerServer('interactive_arm_control')
00062 ArmControlMarker._im_server = im_server
00063
00064 self._arm = arm
00065 self._name = 'arm'
00066 self._is_control_visible = False
00067 self._menu_handler = None
00068 self._prev_is_reachable = None
00069 self._pose = self._arm.get_ee_state()
00070 self._lock = threading.Lock()
00071 self._current_pose = None
00072 self._menu_control = None
00073
00074 self.update()
00075
00076
00077
00078
00079
00080
00081 def update(self):
00082 '''Updates marker/arm loop'''
00083
00084 self._menu_handler = MenuHandler()
00085
00086
00087 self._menu_handler.insert(
00088 'Move gripper here', callback=self._move_to_cb)
00089 self._menu_handler.insert(
00090 'Move marker to current gripper pose',
00091 callback=self._move_pose_to_cb)
00092
00093 if self._is_hand_open():
00094 self._menu_handler.insert(
00095 'Close gripper',
00096 callback=self._close_gripper_cb)
00097 else:
00098 self._menu_handler.insert(
00099 'Open gripper',
00100 callback=self._open_gripper_cb)
00101
00102 frame_id = REF_FRAME
00103 pose = self.get_pose()
00104
00105
00106 rospy.loginfo("Marker moved")
00107
00108 menu_control = InteractiveMarkerControl()
00109 menu_control.interaction_mode = InteractiveMarkerControl.BUTTON
00110 menu_control.always_visible = True
00111
00112
00113 menu_control = self._make_gripper_marker(
00114 menu_control, self._is_hand_open())
00115 self._menu_control = menu_control
00116
00117
00118 int_marker = InteractiveMarker()
00119 int_marker.name = self._get_name()
00120 int_marker.header.frame_id = frame_id
00121 int_marker.pose = pose.pose
00122 int_marker.scale = INT_MARKER_SCALE
00123 self._add_6dof_marker(int_marker, True)
00124 int_marker.controls.append(self._menu_control)
00125 ArmControlMarker._im_server.insert(
00126 int_marker, self._marker_feedback_cb)
00127
00128 self._menu_handler.apply(ArmControlMarker._im_server,
00129 self._get_name())
00130 ArmControlMarker._im_server.applyChanges()
00131
00132 def reset(self):
00133 '''Sets marker to current arm pose'''
00134 self.set_new_pose(self._arm.get_ee_state(), is_offset=True)
00135
00136 def destroy(self):
00137 '''Removes marker from the world.'''
00138 ArmControlMarker._im_server.erase(self._get_name())
00139 ArmControlMarker._im_server.applyChanges()
00140
00141 def set_new_pose(self, new_pose, is_offset=False):
00142 '''Changes the pose of the marker to new_pose.
00143
00144 Args:
00145 new_pose (PoseStamped)
00146 '''
00147 self._lock.acquire()
00148 if is_offset:
00149 self._pose = new_pose
00150 else:
00151 self._pose = ArmControlMarker._offset_pose(new_pose, -1)
00152 self._lock.release()
00153
00154 def get_pose(self):
00155 '''Returns the pose of the marker
00156
00157 Returns:
00158 Pose
00159 '''
00160 self._lock.acquire()
00161 pose = ArmControlMarker._copy_pose(self._pose)
00162 self._lock.release()
00163 return ArmControlMarker._offset_pose(pose)
00164
00165
00166
00167
00168
00169 @staticmethod
00170 def _get_pose_from_transform(transform):
00171 '''Returns pose for transformation matrix.
00172
00173 Args:
00174 transform (Matrix3x3)
00175
00176 Returns:
00177 PoseStamped
00178 '''
00179 pos = transform[:3, 3].copy()
00180 rot = tf.transformations.quaternion_from_matrix(transform)
00181 return PoseStamped(Header(frame_id='base_link'),
00182 Pose(Point(pos[0], pos[1], pos[2]),
00183 Quaternion(rot[0], rot[1], rot[2], rot[3])
00184 ))
00185
00186 @staticmethod
00187 def _get_matrix_from_pose(pose):
00188 '''Returns the transformation matrix for given pose.
00189
00190 Args:
00191 pose (PoseStamped)
00192
00193 Returns:
00194 Matrix3x3
00195 '''
00196 pp, po = pose.pose.position, pose.pose.orientation
00197 rotation = [po.x, po.y, po.z, po.w]
00198 transformation = tf.transformations.quaternion_matrix(rotation)
00199 position = [pp.x, pp.y, pp.z]
00200 transformation[:3, 3] = position
00201 return transformation
00202
00203 @staticmethod
00204 def _offset_pose(pose, constant=1):
00205 '''Offsets the world pose for visualization.
00206
00207 Args:
00208 pose (PoseStamped): The pose to offset.
00209 constant (int, optional): How much to scale the set offset
00210 by (scales DEFAULT_OFFSET). Defaults to 1.
00211
00212 Returns:
00213 PoseStamped: The offset pose.
00214 '''
00215 transform = ArmControlMarker._get_matrix_from_pose(pose)
00216 offset_array = [constant * DEFAULT_OFFSET, 0, 0]
00217 offset_transform = tf.transformations.translation_matrix(offset_array)
00218 hand_transform = tf.transformations.concatenate_matrices(
00219 transform, offset_transform)
00220 return ArmControlMarker._get_pose_from_transform(hand_transform)
00221
00222
00223
00224 @staticmethod
00225 def _copy_pose(pose):
00226 '''Copies pose msg
00227
00228 Args:
00229 pose (PoseStamped)
00230 Returns:
00231 PoseStamped
00232 '''
00233 copy = PoseStamped(
00234 Header(frame_id=pose.header.frame_id),
00235 Pose(Point(pose.pose.position.x, pose.pose.position.y,
00236 pose.pose.position.z),
00237 Quaternion(pose.pose.orientation.x,
00238 pose.pose.orientation.y,
00239 pose.pose.orientation.z,
00240 pose.pose.orientation.w))
00241 )
00242 return copy
00243
00244 @staticmethod
00245 def _pose2array(p):
00246 '''Converts pose to array
00247
00248 Args:
00249 p (PoseStamped)
00250 Returns:
00251 numpy.array
00252 '''
00253 return array((p.pose.position.x, p.pose.position.y,
00254 p.pose.position.z,
00255 p.pose.orientation.x, p.pose.orientation.y,
00256 p.pose.orientation.z, p.pose.orientation.w))
00257
00258 @staticmethod
00259 def _is_the_same(pose1, pose2, tol=0.001):
00260 '''Checks if poses are the same within tolerance
00261
00262 Args:
00263 pose1 (Pose)
00264 pose2 (Pose)
00265 tol (float, optional)
00266 Returns:
00267 bool
00268 '''
00269 if pose1 is None or pose2 is None:
00270 return True
00271
00272 diff = pose1 - pose2
00273 dist = linalg.norm(diff)
00274 return dist < tol
00275
00276 @staticmethod
00277 def _make_mesh_marker(color):
00278 '''Creates and returns a mesh marker.
00279
00280 Args:
00281 color (ColorRGBA)
00282 Returns:
00283 Marker
00284 '''
00285 mesh = Marker()
00286 mesh.mesh_use_embedded_materials = False
00287 mesh.type = Marker.MESH_RESOURCE
00288 mesh.scale.x = 1.0
00289 mesh.scale.y = 1.0
00290 mesh.scale.z = 1.0
00291 mesh.color = color
00292 return mesh
00293
00294
00295
00296
00297
00298 def _marker_moved(self):
00299 '''Returns whether marker has moved
00300
00301 Returns:
00302 bool
00303 '''
00304 moved = True
00305 current_pose = self.get_pose()
00306 if not self._current_pose is None:
00307 if ArmControlMarker._is_the_same(
00308 ArmControlMarker._pose2array(current_pose),
00309 ArmControlMarker._pose2array(self._current_pose)):
00310 moved = False
00311 self._current_pose = current_pose
00312 return moved
00313
00314 def _marker_feedback_cb(self, feedback):
00315 '''Callback for when an event occurs on the marker.
00316
00317 Args:
00318 feedback (InteractiveMarkerFeedback)
00319 '''
00320 if feedback.event_type == InteractiveMarkerFeedback.BUTTON_CLICK:
00321
00322
00323
00324
00325 rospy.logdebug('Changing visibility of the pose controls.')
00326 self._is_control_visible = not self._is_control_visible
00327 elif feedback.event_type == InteractiveMarkerFeedback.MOUSE_UP:
00328 self.set_new_pose(PoseStamped(Header(frame_id='base_link'),
00329 feedback.pose))
00330 self.update()
00331 else:
00332
00333
00334
00335 rospy.logdebug('Unknown event: ' + str(feedback.event_type))
00336
00337 def _open_gripper_cb(self, feedback):
00338 '''Callback for opening gripper.
00339
00340 Args:
00341 feedback (InteractiveMarkerFeedback): Unused
00342 '''
00343 self._arm.open_gripper()
00344 for i in range(30):
00345 if self._arm.get_gripper_state() == GripperState.OPEN:
00346 break
00347 rospy.sleep(0.1)
00348 self.update()
00349
00350 def _close_gripper_cb(self, feedback):
00351 '''Callback for closing gripper.
00352
00353 Args:
00354 feedback (InteractiveMarkerFeedback): Unused
00355 '''
00356 self._arm.close_gripper()
00357 for i in range(30):
00358 if self._arm.get_gripper_state() != GripperState.OPEN:
00359 break
00360 rospy.sleep(0.1)
00361 self.update()
00362
00363 def _move_to_cb(self, feedback):
00364 '''Callback for when moving to a pose is requested.
00365
00366 Args:
00367 feedback (InteractiveMarkerFeedback): Unused
00368 '''
00369
00370 if not ArmControlMarker._is_the_same(
00371 ArmControlMarker._pose2array(
00372 self._pose),
00373 ArmControlMarker._pose2array(self._arm.get_ee_state()),
00374 0.01):
00375 rospy.loginfo("Move to cb from marker for real")
00376
00377 self._lock.acquire()
00378 pose = ArmControlMarker._copy_pose(self._pose)
00379 self._lock.release()
00380
00381 target_pose = pose
00382
00383 if target_pose is not None:
00384
00385
00386 thread = threading.Thread(
00387 group=None,
00388 target=self._arm.move_to_pose,
00389 args=(target_pose,),
00390 name='move_to_arm_state_thread'
00391 )
00392 thread.start()
00393
00394
00395
00396 rospy.loginfo('Started thread to move arm.')
00397 else:
00398 rospy.loginfo('Will not move arm; unreachable.')
00399 else:
00400 rospy.loginfo("move too small?")
00401
00402 def _move_pose_to_cb(self, feedback):
00403 '''Callback for moving gripper marker to current pose.
00404
00405 Args:
00406 feedback (InteractiveMarkerFeedback): Unused
00407 '''
00408 self.reset()
00409 self.update()
00410
00411 def _is_reachable(self):
00412 '''Checks and returns whether there is an IK solution for this
00413 marker pose
00414
00415 Returns:
00416 bool: Whether this action step is reachable.
00417 '''
00418
00419 self._lock.acquire()
00420 pose = ArmControlMarker._copy_pose(self._pose)
00421 self._lock.release()
00422
00423 target_joints = self._arm.get_ik_for_ee(
00424 pose, self._arm.get_joint_state())
00425
00426
00427 is_reachable = (target_joints is not None)
00428
00429
00430
00431
00432 report = False
00433
00434
00435 if self._prev_is_reachable is None:
00436 report = True
00437 reachable_str = (
00438 'is reachable' if is_reachable else 'is not reachable')
00439
00440 elif self._prev_is_reachable != is_reachable:
00441 report = True
00442 reachable_str = (
00443 'is now reachable' if is_reachable else
00444 'is no longer reachable')
00445
00446
00447 if report:
00448 rospy.loginfo(reachable_str)
00449
00450
00451 self._prev_is_reachable = is_reachable
00452
00453 print
00454 return is_reachable
00455
00456 def _get_name(self):
00457 '''Generates the unique name for the marker.
00458
00459 Returns:
00460 str: A human-readable unique name for the marker.
00461 '''
00462 return self._name
00463
00464 def _is_hand_open(self):
00465 '''Returns whether the gripper is open
00466
00467 Returns:
00468 bool
00469 '''
00470 return self._arm.get_gripper_state() == GripperState.OPEN
00471
00472 def _add_6dof_marker(self, int_marker, is_fixed):
00473 '''Adds a 6 DoF control marker to the interactive marker.
00474
00475 Args:
00476 int_marker (InteractiveMarker)
00477 is_fixed (bool): Looks like whether position is fixed (?).
00478 Currently only passed as True.
00479 '''
00480
00481 options = [
00482 ('rotate_x', Quaternion(1, 0, 0, 1), False),
00483 ('move_x', Quaternion(1, 0, 0, 1), True),
00484 ('rotate_z', Quaternion(0, 1, 0, 1), False),
00485 ('move_z', Quaternion(0, 1, 0, 1), True),
00486 ('rotate_y', Quaternion(0, 0, 1, 1), False),
00487 ('move_y', Quaternion(0, 0, 1, 1), True),
00488 ]
00489 for opt in options:
00490 name, orient, is_move = opt
00491 control = self._make_6dof_control(
00492 name, orient, is_move, is_fixed)
00493 int_marker.controls.append(control)
00494
00495 def _make_6dof_control(self, name, orientation, is_move, is_fixed):
00496 '''Creates and returns one component of the 6dof controller.
00497
00498 Args:
00499 name (str): Name for the control
00500 orientation (Quaternion): How the control should be
00501 oriented.
00502 is_move (bool): Looks like whether the marker moves the
00503 object (?). Currently passed as True for moving markers,
00504 False for rotating ones.
00505 is_fixed (bool): Looks like whether position is fixed (?).
00506 Currently always passed as True.
00507
00508 Returns:
00509 InteractiveMarkerControl
00510 '''
00511 control = InteractiveMarkerControl()
00512 control.name = name
00513 control.orientation = orientation
00514 control.always_visible = False
00515 if self._is_control_visible:
00516 if is_move:
00517 control.interaction_mode = InteractiveMarkerControl.MOVE_AXIS
00518 else:
00519 control.interaction_mode = InteractiveMarkerControl.ROTATE_AXIS
00520 else:
00521 control.interaction_mode = InteractiveMarkerControl.NONE
00522 if is_fixed:
00523 control.orientation_mode = InteractiveMarkerControl.FIXED
00524 return control
00525
00526 def _get_mesh_marker_color(self):
00527 '''Gets the color for the mesh marker (thing that looks like a
00528 gripper) for this step.
00529
00530 A simple implementation of this will return one color for
00531 reachable poses, another for unreachable ones. Future
00532 implementations may provide further visual cues.
00533
00534 Returns:
00535 ColorRGBA: The color for the gripper mesh for this step.
00536 '''
00537 if self._is_reachable():
00538 return COLOR_MESH_REACHABLE
00539 else:
00540 return COLOR_MESH_UNREACHABLE
00541
00542 def _make_gripper_marker(self, control, is_hand_open=False):
00543 '''Makes a gripper marker, adds it to control, returns control.
00544
00545 Args:
00546 control (InteractiveMarkerControl): IM Control we're using.
00547 is_hand_open (bool, optional): Whether the gripper is open.
00548 Defaults to False (closed).
00549
00550 Returns:
00551 InteractiveMarkerControl: The passed control.
00552 '''
00553 mesh_color = self._get_mesh_marker_color()
00554
00555
00556 mesh1 = ArmControlMarker._make_mesh_marker(mesh_color)
00557 mesh1.mesh_resource = STR_GRIPPER_PALM_FILE
00558 mesh1.pose.position.x = DEFAULT_OFFSET
00559 mesh1.pose.orientation.w = 1
00560
00561
00562 if is_hand_open:
00563 mesh2 = ArmControlMarker._make_mesh_marker(mesh_color)
00564 mesh2.mesh_resource = STR_L_GRIPPER_FINGER_FILE
00565 mesh2.pose.position.x = 0.08
00566 mesh2.pose.position.y = -0.165
00567 mesh2.pose.orientation.w = 1
00568
00569 mesh3 = ArmControlMarker._make_mesh_marker(mesh_color)
00570 mesh3.mesh_resource = STR_R_GRIPPER_FINGER_FILE
00571 mesh3.pose.position.x = 0.08
00572 mesh3.pose.position.y = 0.165
00573 mesh3.pose.orientation.w = 1
00574 else:
00575 mesh2 = ArmControlMarker._make_mesh_marker(mesh_color)
00576 mesh2.mesh_resource = STR_L_GRIPPER_FINGER_FILE
00577 mesh2.pose.position.x = 0.08
00578 mesh2.pose.position.y = -0.116
00579 mesh2.pose.orientation.w = 1
00580
00581 mesh3 = ArmControlMarker._make_mesh_marker(mesh_color)
00582 mesh3.mesh_resource = STR_R_GRIPPER_FINGER_FILE
00583 mesh3.pose.position.x = 0.08
00584 mesh3.pose.position.y = 0.116
00585 mesh3.pose.orientation.w = 1
00586
00587
00588 control.markers.append(mesh1)
00589 control.markers.append(mesh2)
00590 control.markers.append(mesh3)
00591
00592
00593
00594 return control