pick_place_action_capability.cpp
Go to the documentation of this file.
00001 /*********************************************************************
00002 * Software License Agreement (BSD License)
00003 *
00004 *  Copyright (c) 2012, Willow Garage, Inc.
00005 *  All rights reserved.
00006 *
00007 *  Redistribution and use in source and binary forms, with or without
00008 *  modification, are permitted provided that the following conditions
00009 *  are met:
00010 *
00011 *   * Redistributions of source code must retain the above copyright
00012 *     notice, this list of conditions and the following disclaimer.
00013 *   * Redistributions in binary form must reproduce the above
00014 *     copyright notice, this list of conditions and the following
00015 *     disclaimer in the documentation and/or other materials provided
00016 *     with the distribution.
00017 *   * Neither the name of the Willow Garage nor the names of its
00018 *     contributors may be used to endorse or promote products derived
00019 *     from this software without specific prior written permission.
00020 *
00021 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00022 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00023 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00024 *  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00025 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00026 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00027 *  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00028 *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00029 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00030 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00031 *  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00032 *  POSSIBILITY OF SUCH DAMAGE.
00033 *********************************************************************/
00034 
00035 /* Author: Ioan Sucan */
00036 
00037 #include "pick_place_action_capability.h"
00038 #include <moveit/plan_execution/plan_execution.h>
00039 #include <moveit/plan_execution/plan_with_sensing.h>
00040 #include <moveit/move_group_pick_place_capability/capability_names.h>
00041 
00042 #include <manipulation_msgs/GraspPlanning.h>
00043 #include <eigen_conversions/eigen_msg.h>
00044 
00045 move_group::MoveGroupPickPlaceAction::MoveGroupPickPlaceAction() :
00046   MoveGroupCapability("PickPlaceAction"),
00047   pickup_state_(IDLE)
00048 {
00049 }
00050 
00051 void move_group::MoveGroupPickPlaceAction::initialize()
00052 {
00053   pick_place_.reset(new pick_place::PickPlace(context_->planning_pipeline_));
00054   pick_place_->displayComputedMotionPlans(true);
00055 
00056   if (context_->debug_)
00057     pick_place_->displayProcessedGrasps(true);
00058 
00059   // start the pickup action server
00060   pickup_action_server_.reset(new actionlib::SimpleActionServer<moveit_msgs::PickupAction>(root_node_handle_, PICKUP_ACTION,
00061                                                                                            boost::bind(&MoveGroupPickPlaceAction::executePickupCallback, this, _1), false));
00062   pickup_action_server_->registerPreemptCallback(boost::bind(&MoveGroupPickPlaceAction::preemptPickupCallback, this));
00063   pickup_action_server_->start();
00064 
00065   // start the place action server
00066   place_action_server_.reset(new actionlib::SimpleActionServer<moveit_msgs::PlaceAction>(root_node_handle_, PLACE_ACTION,
00067                                                                                          boost::bind(&MoveGroupPickPlaceAction::executePlaceCallback, this, _1), false));
00068   place_action_server_->registerPreemptCallback(boost::bind(&MoveGroupPickPlaceAction::preemptPlaceCallback, this));
00069   place_action_server_->start();
00070 
00071   grasp_planning_service_ = root_node_handle_.serviceClient<manipulation_msgs::GraspPlanning>("database_grasp_planning");
00072 }
00073 
00074 void move_group::MoveGroupPickPlaceAction::startPickupExecutionCallback()
00075 {
00076   setPickupState(MONITOR);
00077 }
00078 
00079 void move_group::MoveGroupPickPlaceAction::startPickupLookCallback()
00080 {
00081   setPickupState(LOOK);
00082 }
00083 
00084 void move_group::MoveGroupPickPlaceAction::startPlaceExecutionCallback()
00085 {
00086   setPlaceState(MONITOR);
00087 }
00088 
00089 void move_group::MoveGroupPickPlaceAction::startPlaceLookCallback()
00090 {
00091   setPlaceState(LOOK);
00092 }
00093 
00094 void move_group::MoveGroupPickPlaceAction::executePickupCallback_PlanOnly(const moveit_msgs::PickupGoalConstPtr& goal, moveit_msgs::PickupResult &action_res)
00095 {
00096   pick_place::PickPlanPtr plan;
00097   try
00098   {
00099     planning_scene_monitor::LockedPlanningSceneRO ps(context_->planning_scene_monitor_);
00100     plan = pick_place_->planPick(ps, *goal);
00101   }
00102   catch(std::runtime_error &ex)
00103   {
00104     ROS_ERROR("Pick&place threw an exception: %s", ex.what());
00105   }
00106   catch(...)
00107   {
00108     ROS_ERROR("Pick&place threw an exception");
00109   }
00110 
00111   if (plan)
00112   {
00113     const std::vector<pick_place::ManipulationPlanPtr> &success = plan->getSuccessfulManipulationPlans();
00114     if (success.empty())
00115     {
00116       action_res.error_code = plan->getErrorCode();
00117     }
00118     else
00119     {
00120       const pick_place::ManipulationPlanPtr &result = success.back();
00121       convertToMsg(result->trajectories_, action_res.trajectory_start, action_res.trajectory_stages);
00122       action_res.trajectory_descriptions.resize(result->trajectories_.size());
00123       for (std::size_t i = 0 ; i < result->trajectories_.size() ; ++i)
00124         action_res.trajectory_descriptions[i] = result->trajectories_[i].description_;
00125       action_res.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
00126     }
00127   }
00128   else
00129   {
00130     action_res.error_code.val = moveit_msgs::MoveItErrorCodes::FAILURE;
00131   }
00132 }
00133 
00134 void move_group::MoveGroupPickPlaceAction::executePlaceCallback_PlanOnly(const moveit_msgs::PlaceGoalConstPtr& goal, moveit_msgs::PlaceResult &action_res)
00135 {
00136   pick_place::PlacePlanPtr plan;
00137   try
00138   {
00139     planning_scene_monitor::LockedPlanningSceneRO ps(context_->planning_scene_monitor_);
00140     plan = pick_place_->planPlace(ps, *goal);
00141   }
00142   catch(std::runtime_error &ex)
00143   {
00144     ROS_ERROR("Pick&place threw an exception: %s", ex.what());
00145   }
00146   catch(...)
00147   {
00148     ROS_ERROR("Pick&place threw an exception");
00149   }
00150 
00151   if (plan)
00152   {
00153     const std::vector<pick_place::ManipulationPlanPtr> &success = plan->getSuccessfulManipulationPlans();
00154     if (success.empty())
00155     {
00156       action_res.error_code = plan->getErrorCode();
00157     }
00158     else
00159     {
00160       const pick_place::ManipulationPlanPtr &result = success.back();
00161       convertToMsg(result->trajectories_, action_res.trajectory_start, action_res.trajectory_stages);
00162       action_res.trajectory_descriptions.resize(result->trajectories_.size());
00163       for (std::size_t i = 0 ; i < result->trajectories_.size() ; ++i)
00164         action_res.trajectory_descriptions[i] = result->trajectories_[i].description_;
00165       action_res.error_code.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
00166     }
00167   }
00168   else
00169   {
00170     action_res.error_code.val = moveit_msgs::MoveItErrorCodes::FAILURE;
00171   }
00172 }
00173 
00174 bool move_group::MoveGroupPickPlaceAction::planUsingPickPlace_Pickup(const moveit_msgs::PickupGoal& goal, plan_execution::ExecutableMotionPlan &plan)
00175 {
00176   setPickupState(PLANNING);
00177 
00178   planning_scene_monitor::LockedPlanningSceneRO ps(plan.planning_scene_monitor_);
00179 
00180   pick_place::PickPlanPtr pick_plan;
00181   try
00182   {
00183     pick_plan = pick_place_->planPick(plan.planning_scene_, goal);
00184   }
00185   catch(std::runtime_error &ex)
00186   {
00187     ROS_ERROR("Pick&place threw an exception: %s", ex.what());
00188   }
00189   catch(...)
00190   {
00191     ROS_ERROR("Pick&place threw an exception");
00192   }
00193 
00194   if (pick_plan)
00195   {
00196     const std::vector<pick_place::ManipulationPlanPtr> &success = pick_plan->getSuccessfulManipulationPlans();
00197     if (success.empty())
00198     {
00199       plan.error_code_ = pick_plan->getErrorCode();
00200     }
00201     else
00202     {
00203       const pick_place::ManipulationPlanPtr &result = success.back();
00204       plan.plan_components_ = result->trajectories_;
00205       plan.error_code_.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
00206     }
00207   }
00208   else
00209   {
00210     plan.error_code_.val = moveit_msgs::MoveItErrorCodes::FAILURE;
00211   }
00212 
00213   return plan.error_code_.val == moveit_msgs::MoveItErrorCodes::SUCCESS;
00214 }
00215 
00216 bool move_group::MoveGroupPickPlaceAction::planUsingPickPlace_Place(const moveit_msgs::PlaceGoal& goal, plan_execution::ExecutableMotionPlan &plan)
00217 {
00218   setPlaceState(PLANNING);
00219 
00220   planning_scene_monitor::LockedPlanningSceneRO ps(plan.planning_scene_monitor_);
00221 
00222   pick_place::PlacePlanPtr place_plan;
00223   try
00224   {
00225     place_plan = pick_place_->planPlace(plan.planning_scene_, goal);
00226   }
00227   catch(std::runtime_error &ex)
00228   {
00229     ROS_ERROR("Pick&place threw an exception: %s", ex.what());
00230   }
00231   catch(...)
00232   {
00233     ROS_ERROR("Pick&place threw an exception");
00234   }
00235 
00236   if (place_plan)
00237   {
00238     const std::vector<pick_place::ManipulationPlanPtr> &success = place_plan->getSuccessfulManipulationPlans();
00239     if (success.empty())
00240     {
00241       plan.error_code_ = place_plan->getErrorCode();
00242     }
00243     else
00244     {
00245       const pick_place::ManipulationPlanPtr &result = success.back();
00246       plan.plan_components_ = result->trajectories_;
00247       plan.error_code_.val = moveit_msgs::MoveItErrorCodes::SUCCESS;
00248     }
00249   }
00250   else
00251   {
00252     plan.error_code_.val = moveit_msgs::MoveItErrorCodes::FAILURE;
00253   }
00254 
00255   return plan.error_code_.val == moveit_msgs::MoveItErrorCodes::SUCCESS;
00256 }
00257 
00258 void move_group::MoveGroupPickPlaceAction::executePickupCallback_PlanAndExecute(const moveit_msgs::PickupGoalConstPtr& goal, moveit_msgs::PickupResult &action_res)
00259 {
00260   plan_execution::PlanExecution::Options opt;
00261 
00262   opt.replan_ = goal->planning_options.replan;
00263   opt.replan_attempts_ = goal->planning_options.replan_attempts;
00264   opt.replan_delay_ = goal->planning_options.replan_delay;
00265   opt.before_execution_callback_ = boost::bind(&MoveGroupPickPlaceAction::startPickupExecutionCallback, this);
00266 
00267   opt.plan_callback_ = boost::bind(&MoveGroupPickPlaceAction::planUsingPickPlace_Pickup, this, boost::cref(*goal), _1);
00268   if (goal->planning_options.look_around && context_->plan_with_sensing_)
00269   {
00270     opt.plan_callback_ = boost::bind(&plan_execution::PlanWithSensing::computePlan, context_->plan_with_sensing_.get(), _1, opt.plan_callback_,
00271                                      goal->planning_options.look_around_attempts, goal->planning_options.max_safe_execution_cost);
00272     context_->plan_with_sensing_->setBeforeLookCallback(boost::bind(&MoveGroupPickPlaceAction::startPickupLookCallback, this));
00273   }
00274 
00275   plan_execution::ExecutableMotionPlan plan;
00276   context_->plan_execution_->planAndExecute(plan, goal->planning_options.planning_scene_diff, opt);
00277 
00278   convertToMsg(plan.plan_components_, action_res.trajectory_start, action_res.trajectory_stages);
00279   action_res.trajectory_descriptions.resize(plan.plan_components_.size());
00280   for (std::size_t i = 0 ; i < plan.plan_components_.size() ; ++i)
00281   {
00282     action_res.trajectory_descriptions[i] = plan.plan_components_[i].description_;
00283   }
00284   addGraspToPickupResult(plan, action_res);
00285   action_res.error_code = plan.error_code_;
00286 }
00287 
00288 void move_group::MoveGroupPickPlaceAction::addGraspToPickupResult(const plan_execution::ExecutableMotionPlan &plan,
00289                                   moveit_msgs::PickupResult &action_res) const
00290 {
00291   for (std::size_t i = 0 ; i < plan.plan_components_.size() ; ++i)
00292   {
00293     if(plan.plan_components_[i].description_ == "pre_grasp")
00294     {
00295       action_res.grasp.pre_grasp_posture.name = plan.plan_components_[i].trajectory_->getGroup()->getJointModelNames();
00296       plan.plan_components_[i].trajectory_->getLastWayPoint().getJointStateGroup(plan.plan_components_[i].trajectory_->getGroupName())->getVariableValues(action_res.grasp.pre_grasp_posture.position);
00297     }
00298     if(plan.plan_components_[i].description_ == "grasp")
00299     {
00300       action_res.grasp.grasp_posture.name = plan.plan_components_[i].trajectory_->getGroup()->getJointModelNames();
00301       plan.plan_components_[i].trajectory_->getLastWayPoint().getJointStateGroup(plan.plan_components_[i].trajectory_->getGroupName())->getVariableValues(action_res.grasp.grasp_posture.position);
00302     }
00303   }
00304 }
00305 
00306 void move_group::MoveGroupPickPlaceAction::executePlaceCallback_PlanAndExecute(const moveit_msgs::PlaceGoalConstPtr& goal, moveit_msgs::PlaceResult &action_res)
00307 {
00308   plan_execution::PlanExecution::Options opt;
00309 
00310   opt.replan_ = goal->planning_options.replan;
00311   opt.replan_attempts_ = goal->planning_options.replan_attempts;
00312   opt.replan_delay_ = goal->planning_options.replan_delay;
00313   opt.before_execution_callback_ = boost::bind(&MoveGroupPickPlaceAction::startPlaceExecutionCallback, this);
00314   opt.plan_callback_ = boost::bind(&MoveGroupPickPlaceAction::planUsingPickPlace_Place, this, boost::cref(*goal), _1);
00315   if (goal->planning_options.look_around && context_->plan_with_sensing_)
00316   {
00317     opt.plan_callback_ = boost::bind(&plan_execution::PlanWithSensing::computePlan, context_->plan_with_sensing_.get(), _1, opt.plan_callback_,
00318                                      goal->planning_options.look_around_attempts, goal->planning_options.max_safe_execution_cost);
00319     context_->plan_with_sensing_->setBeforeLookCallback(boost::bind(&MoveGroupPickPlaceAction::startPlaceLookCallback, this));
00320   }
00321 
00322   plan_execution::ExecutableMotionPlan plan;
00323   context_->plan_execution_->planAndExecute(plan, goal->planning_options.planning_scene_diff, opt);
00324 
00325   convertToMsg(plan.plan_components_, action_res.trajectory_start, action_res.trajectory_stages);
00326   action_res.trajectory_descriptions.resize(plan.plan_components_.size());
00327   for (std::size_t i = 0 ; i < plan.plan_components_.size() ; ++i)
00328     action_res.trajectory_descriptions[i] = plan.plan_components_[i].description_;
00329   action_res.error_code = plan.error_code_;
00330 }
00331 
00332 void move_group::MoveGroupPickPlaceAction::executePickupCallback(const moveit_msgs::PickupGoalConstPtr& input_goal)
00333 {
00334   setPickupState(PLANNING);
00335 
00336   context_->planning_scene_monitor_->updateFrameTransforms();
00337 
00338   moveit_msgs::PickupGoalConstPtr goal;
00339   if (input_goal->possible_grasps.empty())
00340   {
00341     moveit_msgs::PickupGoal* copy(new moveit_msgs::PickupGoal(*input_goal));
00342     goal.reset(copy);
00343     fillGrasps(*copy);
00344   }
00345   else
00346     goal = input_goal;
00347 
00348   moveit_msgs::PickupResult action_res;
00349 
00350   if (goal->planning_options.plan_only || !context_->allow_trajectory_execution_)
00351   {
00352     if (!goal->planning_options.plan_only)
00353       ROS_WARN("This instance of MoveGroup is not allowed to execute trajectories but the pick goal request has plan_only set to false. Only a motion plan will be computed anyway.");
00354     executePickupCallback_PlanOnly(goal, action_res);
00355   }
00356   else
00357     executePickupCallback_PlanAndExecute(goal, action_res);
00358 
00359   bool planned_trajectory_empty = action_res.trajectory_stages.empty();
00360   std::string response = getActionResultString(action_res.error_code, planned_trajectory_empty, goal->planning_options.plan_only);
00361   if (action_res.error_code.val == moveit_msgs::MoveItErrorCodes::SUCCESS)
00362     pickup_action_server_->setSucceeded(action_res, response);
00363   else
00364   {
00365     if (action_res.error_code.val == moveit_msgs::MoveItErrorCodes::PREEMPTED)
00366       pickup_action_server_->setPreempted(action_res, response);
00367     else
00368       pickup_action_server_->setAborted(action_res, response);
00369   }
00370 
00371   setPickupState(IDLE);
00372 }
00373 
00374 void move_group::MoveGroupPickPlaceAction::executePlaceCallback(const moveit_msgs::PlaceGoalConstPtr& goal)
00375 {
00376   setPlaceState(PLANNING);
00377 
00378   context_->planning_scene_monitor_->updateFrameTransforms();
00379 
00380   moveit_msgs::PlaceResult action_res;
00381 
00382   if (goal->planning_options.plan_only || !context_->allow_trajectory_execution_)
00383   {
00384     if (!goal->planning_options.plan_only)
00385       ROS_WARN("This instance of MoveGroup is not allowed to execute trajectories but the place goal request has plan_only set to false. Only a motion plan will be computed anyway.");
00386     executePlaceCallback_PlanOnly(goal, action_res);
00387   }
00388   else
00389     executePlaceCallback_PlanAndExecute(goal, action_res);
00390 
00391   bool planned_trajectory_empty = action_res.trajectory_stages.empty();
00392   std::string response = getActionResultString(action_res.error_code, planned_trajectory_empty, goal->planning_options.plan_only);
00393   if (action_res.error_code.val == moveit_msgs::MoveItErrorCodes::SUCCESS)
00394     place_action_server_->setSucceeded(action_res, response);
00395   else
00396   {
00397     if (action_res.error_code.val == moveit_msgs::MoveItErrorCodes::PREEMPTED)
00398       place_action_server_->setPreempted(action_res, response);
00399     else
00400       place_action_server_->setAborted(action_res, response);
00401   }
00402 
00403   setPlaceState(IDLE);
00404 }
00405 
00406 void move_group::MoveGroupPickPlaceAction::preemptPickupCallback()
00407 {
00408 }
00409 
00410 void move_group::MoveGroupPickPlaceAction::preemptPlaceCallback()
00411 {
00412 }
00413 
00414 void move_group::MoveGroupPickPlaceAction::setPickupState(MoveGroupState state)
00415 {
00416   pickup_state_ = state;
00417   pickup_feedback_.state = stateToStr(state);
00418   pickup_action_server_->publishFeedback(pickup_feedback_);
00419 }
00420 
00421 void move_group::MoveGroupPickPlaceAction::setPlaceState(MoveGroupState state)
00422 {
00423   place_state_ = state;
00424   place_feedback_.state = stateToStr(state);
00425   place_action_server_->publishFeedback(place_feedback_);
00426 }
00427 
00428 void move_group::MoveGroupPickPlaceAction::fillGrasps(moveit_msgs::PickupGoal& goal)
00429 {
00430   planning_scene_monitor::LockedPlanningSceneRO lscene(context_->planning_scene_monitor_);
00431   manipulation_msgs::GraspPlanning::Request request;
00432   manipulation_msgs::GraspPlanning::Response response;
00433 
00434   if (grasp_planning_service_ && lscene->hasObjectType(goal.target_name) && !lscene->getObjectType(goal.target_name).key.empty())
00435   {
00436     collision_detection::World::ObjectConstPtr object = lscene->getWorld()->getObject(goal.target_name);
00437     if (object && !object->shape_poses_.empty())
00438     {
00439       request.arm_name = goal.group_name;
00440       request.target.reference_frame_id = lscene->getPlanningFrame();
00441 
00442       household_objects_database_msgs::DatabaseModelPose dbp;
00443       dbp.pose.header.frame_id = lscene->getPlanningFrame();
00444       dbp.pose.header.stamp = ros::Time::now();
00445       tf::poseEigenToMsg(object->shape_poses_[0], dbp.pose.pose);
00446 
00447       dbp.type = lscene->getObjectType(goal.target_name);
00448       std::stringstream dbp_type(dbp.type.key);
00449       dbp_type >> dbp.model_id;
00450       ROS_DEBUG("Asking database for grasps for %s with model id: %d", dbp.type.key.c_str(), dbp.model_id);
00451       request.target.potential_models.push_back(dbp);
00452     }
00453     else
00454     {
00455       ROS_ERROR("Object has no geometry");
00456     }
00457 
00458     ROS_DEBUG("Calling grasp planner...");
00459     if (grasp_planning_service_.call(request, response))
00460       goal.possible_grasps = response.grasps;
00461   }
00462 
00463   if (goal.possible_grasps.empty())
00464   {
00465     ROS_DEBUG("Using default grasp poses");
00466     goal.minimize_object_distance = true;
00467 
00468     // add a number of default grasp points
00469     // \todo add more!
00470     manipulation_msgs::Grasp g;
00471     g.grasp_pose.header.frame_id = goal.target_name;
00472     g.grasp_pose.pose.position.x = -0.2;
00473     g.grasp_pose.pose.position.y = 0.0;
00474     g.grasp_pose.pose.position.z = 0.0;
00475     g.grasp_pose.pose.orientation.x = 0.0;
00476     g.grasp_pose.pose.orientation.y = 0.0;
00477     g.grasp_pose.pose.orientation.z = 0.0;
00478     g.grasp_pose.pose.orientation.w = 1.0;
00479 
00480     g.approach.direction.header.frame_id = lscene->getPlanningFrame();
00481     g.approach.direction.vector.x = 1.0;
00482     g.approach.min_distance = 0.1;
00483     g.approach.desired_distance = 0.2;
00484 
00485     g.retreat.direction.header.frame_id = lscene->getPlanningFrame();
00486     g.retreat.direction.vector.z = 1.0;
00487     g.retreat.min_distance = 0.1;
00488     g.retreat.desired_distance = 0.2;
00489 
00490     if (lscene->getRobotModel()->hasEndEffector(goal.end_effector))
00491     {
00492       g.pre_grasp_posture.name = lscene->getRobotModel()->getEndEffector(goal.end_effector)->getJointModelNames();
00493       g.pre_grasp_posture.position.resize(g.pre_grasp_posture.name.size(), std::numeric_limits<double>::max());
00494 
00495       g.grasp_posture.name = g.pre_grasp_posture.name;
00496       g.grasp_posture.position.resize(g.grasp_posture.name.size(), -std::numeric_limits<double>::max());
00497     }
00498     goal.possible_grasps.push_back(g);
00499   }
00500 }
00501 
00502 #include <class_loader/class_loader.h>
00503 CLASS_LOADER_REGISTER_CLASS(move_group::MoveGroupPickPlaceAction, move_group::MoveGroupCapability)


manipulation
Author(s): Ioan Sucan , Sachin Chitta
autogenerated on Mon Oct 6 2014 02:33:06