|
| 1 | +import sys |
| 2 | + |
| 3 | +import moveit_commander |
| 4 | +import numpy as np |
| 5 | +import rospy |
| 6 | +from geometry_msgs.msg import PoseStamped |
| 7 | +from moveit_msgs.msg import DisplayRobotState |
| 8 | +from pymoveit import kinematic_constraints, collision_detection, robot_model, random_numbers |
| 9 | +from pymoveit.planning_scene import PlanningScene |
| 10 | + |
| 11 | + |
| 12 | +# BEGIN_SUB_TUTORIAL stateFeasibilityTestExample |
| 13 | +# |
| 14 | +# User defined constraints can also be specified to the PlanningScene |
| 15 | +# class. This is done by specifying a callback using the |
| 16 | +# setStateFeasibilityPredicate function. Here's a simple example of a |
| 17 | +# user-defined callback that checks whether the "panda_joint1" of |
| 18 | +# the Panda robot is at a positive or negative angle: |
| 19 | +def stateFeasibilityTestExample(kinematic_state, verbose): |
| 20 | + joint_values = kinematic_state.getJointPositions("panda_joint1") |
| 21 | + return joint_values[0] > 0.0 |
| 22 | + |
| 23 | + |
| 24 | +# END_SUB_TUTORIAL |
| 25 | + |
| 26 | +def main(): |
| 27 | + rospy.init_node("planning_scene_tutorial") |
| 28 | + moveit_commander.roscpp_initialize(sys.argv) |
| 29 | + |
| 30 | + robot_state_pub = rospy.Publisher("display_robot_state", DisplayRobotState, queue_size=10) |
| 31 | + |
| 32 | + # BEGIN_TUTORIAL |
| 33 | + # |
| 34 | + # Setup |
| 35 | + # ^^^^^ |
| 36 | + # |
| 37 | + # The :planning_scene:`PlanningScene` class can be easily setup and |
| 38 | + # configured using a :moveit_core:`RobotModel` or a URDF and |
| 39 | + # SRDF. This is, however, not the recommended way to instantiate a |
| 40 | + # PlanningScene. The :planning_scene_monitor:`PlanningSceneMonitor` |
| 41 | + # is the recommended method to create and maintain the current |
| 42 | + # planning scene (and is discussed in detail in the next tutorial) |
| 43 | + # using data from the robot's joints and the sensors on the robot. In |
| 44 | + # this tutorial, we will instantiate a PlanningScene class directly, |
| 45 | + # but this method of instantiation is only intended for illustration. |
| 46 | + |
| 47 | + urdf_path = '/opt/ros/noetic/share/moveit_resources_panda_description/urdf/panda.urdf' |
| 48 | + srdf_path = '/opt/ros/noetic/share/moveit_resources_panda_moveit_config/config/panda.srdf' |
| 49 | + kinematic_model = robot_model.load_robot_model(urdf_path, srdf_path) |
| 50 | + planning_scene = PlanningScene(kinematic_model, collision_detection.World()) |
| 51 | + current_state = planning_scene.getCurrentStateNonConst() |
| 52 | + joint_model_group = current_state.getJointModelGroup("panda_arm") |
| 53 | + |
| 54 | + # Collision Checking |
| 55 | + # ^^^^^^^^^^^^^^^^^^ |
| 56 | + # |
| 57 | + # Self-collision checking |
| 58 | + # ~~~~~~~~~~~~~~~~~~~~~~~ |
| 59 | + # |
| 60 | + # The first thing we will do is check whether the robot in its |
| 61 | + # current state is in *self-collision*, i.e. whether the current |
| 62 | + # configuration of the robot would result in the robot's parts |
| 63 | + # hitting each other. To do this, we will construct a |
| 64 | + # :collision_detection_struct:`CollisionRequest` object and a |
| 65 | + # :collision_detection_struct:`CollisionResult` object and pass them |
| 66 | + # into the collision checking function. Note that the result of |
| 67 | + # whether the robot is in self-collision or not is contained within |
| 68 | + # the result. Self collision checking uses an *unpadded* version of |
| 69 | + # the robot, i.e. it directly uses the collision meshes provided in |
| 70 | + # the URDF with no extra padding added on. |
| 71 | + |
| 72 | + collision_request = collision_detection.CollisionRequest() |
| 73 | + collision_result = collision_detection.CollisionResult() |
| 74 | + planning_scene.checkSelfCollision(collision_request, collision_result) |
| 75 | + print(f"Test 1: Current state is {'in' if collision_result.collision else 'not in'} self collision") |
| 76 | + |
| 77 | + # Change the state |
| 78 | + # ~~~~~~~~~~~~~~~~ |
| 79 | + # |
| 80 | + # Now, let's change the current state of the robot. The planning |
| 81 | + # scene maintains the current state internally. We can get a |
| 82 | + # reference to it and change it and then check for collisions for the |
| 83 | + # new robot configuration. Note in particular that we need to clear |
| 84 | + # the collision_result before making a new collision checking |
| 85 | + # request. |
| 86 | + |
| 87 | + rng = random_numbers.RandomNumbers(0) |
| 88 | + current_state.setToRandomPositions(joint_model_group, rng) |
| 89 | + |
| 90 | + collision_result.clear() |
| 91 | + planning_scene.checkSelfCollision(collision_request, collision_result) |
| 92 | + print(f"Test 2: Current state is {'in' if collision_result.collision else 'not in'} self collision") |
| 93 | + |
| 94 | + # Checking for a group |
| 95 | + # ~~~~~~~~~~~~~~~~~~~~ |
| 96 | + # |
| 97 | + # Now, we will do collision checking only for the hand of the |
| 98 | + # Panda, i.e. we will check whether there are any collisions between |
| 99 | + # the hand and other parts of the body of the robot. We can ask |
| 100 | + # for this specifically by adding the group name "hand" to the |
| 101 | + # collision request. |
| 102 | + |
| 103 | + collision_request.group_name = "hand" |
| 104 | + current_state.setToRandomPositions(joint_model_group, rng) |
| 105 | + collision_result.clear() |
| 106 | + planning_scene.checkSelfCollision(collision_request, collision_result) |
| 107 | + print(f"Test 3: Current state is {'in' if collision_result.collision else 'not in'} self collision") |
| 108 | + |
| 109 | + # Getting Contact Information |
| 110 | + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 111 | + # |
| 112 | + # First, manually set the Panda arm to a position where we know |
| 113 | + # internal (self) collisions do happen. Note that this state is now |
| 114 | + # actually outside the joint limits of the Panda, which we can also |
| 115 | + # check for directly. |
| 116 | + |
| 117 | + joint_values = np.array([0.0, 0.0, 0.0, -2.9, 0.0, 1.4, 0.0]) |
| 118 | + joint_model_group = current_state.getJointModelGroup("panda_arm") |
| 119 | + current_state.setJointGroupPositions(joint_model_group, joint_values) |
| 120 | + print( |
| 121 | + f"Test 4: Current state is {'valid' if current_state.satisfiesBounds(joint_model_group) else 'not valid'}") |
| 122 | + |
| 123 | + # Now, we can get contact information for any collisions that might |
| 124 | + # have happened at a given configuration of the Panda arm. We can ask |
| 125 | + # for contact information by filling in the appropriate field in the |
| 126 | + # collision request and specifying the maximum number of contacts to |
| 127 | + # be returned as a large number. |
| 128 | + |
| 129 | + collision_request.contacts = True |
| 130 | + collision_request.max_contacts = 1000 |
| 131 | + |
| 132 | + collision_result.clear() |
| 133 | + planning_scene.checkSelfCollision(collision_request, collision_result) |
| 134 | + print(f"Test 5: Current state is {'in' if collision_result.collision else 'not in'} self collision") |
| 135 | + for (first_name, second_name), contacts in collision_result.contacts: |
| 136 | + print(f"Contact between {first_name} and {second_name}") |
| 137 | + |
| 138 | + # Modifying the Allowed Collision Matrix |
| 139 | + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 140 | + # |
| 141 | + # The :collision_detection_class:`AllowedCollisionMatrix` (ACM) |
| 142 | + # provides a mechanism to tell the collision world to ignore |
| 143 | + # collisions between certain object: both parts of the robot and |
| 144 | + # objects in the world. We can tell the collision checker to ignore |
| 145 | + # all collisions between the links reported above, i.e. even though |
| 146 | + # the links are actually in collision, the collision checker will |
| 147 | + # ignore those collisions and return not in collision for this |
| 148 | + # particular state of the robot. |
| 149 | + # |
| 150 | + # Note also in this example how we are making copies of both the |
| 151 | + # allowed collision matrix and the current state and passing them in |
| 152 | + # to the collision checking function. |
| 153 | + |
| 154 | + acm = planning_scene.getAllowedCollisionMatrix() |
| 155 | + copied_state = planning_scene.getCurrentState() |
| 156 | + |
| 157 | + # for (it2 = collision_result.contacts.begin() it2 != collision_result.contacts.end() + +it2) |
| 158 | + for (first_name, second_name), contacts in collision_result.contacts: |
| 159 | + acm.setEntry(first_name, second_name, True) |
| 160 | + collision_result.clear() |
| 161 | + planning_scene.checkSelfCollision(collision_request, collision_result, copied_state, acm) |
| 162 | + print(f"Test 6: Current state is {'in' if collision_result.collision else 'not in'} self collision") |
| 163 | + |
| 164 | + # Full Collision Checking |
| 165 | + # ~~~~~~~~~~~~~~~~~~~~~~~ |
| 166 | + # |
| 167 | + # While we have been checking for self-collisions, we can use the |
| 168 | + # checkCollision functions instead which will check for both |
| 169 | + # self-collisions and for collisions with the environment (which is |
| 170 | + # currently empty). This is the set of collision checking |
| 171 | + # functions that you will use most often in a planner. Note that |
| 172 | + # collision checks with the environment will use the padded version |
| 173 | + # of the robot. Padding helps in keeping the robot further away |
| 174 | + # from obstacles in the environment. |
| 175 | + collision_result.clear() |
| 176 | + planning_scene.checkCollision(collision_request, collision_result, copied_state, acm) |
| 177 | + print(f"Test 7: Current state is {'in' if collision_result.collision else 'not in'} self collision") |
| 178 | + |
| 179 | + # Constraint Checking |
| 180 | + # ^^^^^^^^^^^^^^^^^^^ |
| 181 | + # |
| 182 | + # The PlanningScene class also includes easy to use function calls |
| 183 | + # for checking constraints. The constraints can be of two types: |
| 184 | + # (a) constraints chosen from the |
| 185 | + # :kinematic_constraints:`KinematicConstraint` set: |
| 186 | + # i.e. :kinematic_constraints:`JointConstraint`, |
| 187 | + # :kinematic_constraints:`PositionConstraint`, |
| 188 | + # :kinematic_constraints:`OrientationConstraint` and |
| 189 | + # :kinematic_constraints:`VisibilityConstraint` and (b) user |
| 190 | + # defined constraints specified through a callback. We will first |
| 191 | + # look at an example with a simple KinematicConstraint. |
| 192 | + # |
| 193 | + # Checking Kinematic Constraints |
| 194 | + # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 195 | + # |
| 196 | + # We will first define a simple position and orientation constraint |
| 197 | + # on the end-effector of the panda_arm group of the Panda robot. Note the |
| 198 | + # use of convenience functions for filling up the constraints |
| 199 | + # (these functions are found in the :moveit_core_files:`utils.h<utils_8h>` file from the |
| 200 | + # kinematic_constraints directory in moveit_core). |
| 201 | + |
| 202 | + end_effector_name = joint_model_group.getLinkModelNames()[-1] |
| 203 | + |
| 204 | + desired_pose = PoseStamped() |
| 205 | + desired_pose.pose.orientation.w = 1.0 |
| 206 | + desired_pose.pose.position.x = 0.3 |
| 207 | + desired_pose.pose.position.y = -0.185 |
| 208 | + desired_pose.pose.position.z = 0.5 |
| 209 | + desired_pose.header.frame_id = "panda_link0" |
| 210 | + goal_constraint = kinematic_constraints.constructGoalConstraints(end_effector_name, desired_pose) |
| 211 | + |
| 212 | + # Now, we can check a state against this constraint using the |
| 213 | + # isStateConstrained functions in the PlanningScene class. |
| 214 | + copied_state.setToRandomPositions(joint_model_group, rng) |
| 215 | + copied_state.update() |
| 216 | + constrained = planning_scene.isStateConstrained(copied_state, goal_constraint) |
| 217 | + print(f"Test 8: Random state is {'constrained' if constrained else 'not constrained'}") |
| 218 | + |
| 219 | + # There's a more efficient way of checking constraints (when you want |
| 220 | + # to check the same constraint over and over again, e.g. inside a |
| 221 | + # planner). We first construct a KinematicConstraintSet which |
| 222 | + # pre-processes the ROS Constraints messages and sets it up for quick |
| 223 | + # processing. |
| 224 | + |
| 225 | + kinematic_constraint_set = kinematic_constraints.KinematicConstraintSet(kinematic_model) |
| 226 | + kinematic_constraint_set.add(goal_constraint, planning_scene.getTransforms()) |
| 227 | + constrained_2 = planning_scene.isStateConstrained(copied_state, kinematic_constraint_set) |
| 228 | + print(f"Test 9: Random state is {'constrained' if constrained_2 else 'not constrained'}") |
| 229 | + |
| 230 | + # There's a direct way to do this using the KinematicConstraintSet |
| 231 | + # class. |
| 232 | + |
| 233 | + constraint_eval_result = kinematic_constraint_set.decide(copied_state) |
| 234 | + print( |
| 235 | + f"Test 10: Random state is {'constrained' if constraint_eval_result.satisfied else 'not constrained'}") |
| 236 | + |
| 237 | + # User-defined constraints |
| 238 | + # ~~~~~~~~~~~~~~~~~~~~~~~~ |
| 239 | + # |
| 240 | + # CALL_SUB_TUTORIAL stateFeasibilityTestExample |
| 241 | + |
| 242 | + # Now, whenever isStateFeasible is called, this user-defined callback |
| 243 | + # will be called. |
| 244 | + |
| 245 | + # planning_scene.setStateFeasibilityPredicate(stateFeasibilityTestExample) |
| 246 | + # state_feasible = planning_scene.isStateFeasible(copied_state) |
| 247 | + # print(f"Test 11: Random state is {'feasible' if state_feasible else 'not feasible'}") |
| 248 | + |
| 249 | + # Whenever isStateValid is called, three checks are conducted: (a) |
| 250 | + # collision checking (b) constraint checking and (c) feasibility |
| 251 | + # checking using the user-defined callback. |
| 252 | + |
| 253 | + state_valid = planning_scene.isStateValid(copied_state, kinematic_constraint_set, "panda_arm") |
| 254 | + print(f"Test 12: Random state is {'valid' if state_valid else 'not valid'}") |
| 255 | + |
| 256 | + # Note that all the planners available through MoveIt and OMPL will |
| 257 | + # currently perform collision checking, constraint checking and |
| 258 | + # feasibility checking using user-defined callbacks. |
| 259 | + # END_TUTORIAL |
| 260 | + |
| 261 | + moveit_commander.roscpp_shutdown() |
| 262 | + |
| 263 | + |
| 264 | +if __name__ == '__main__': |
| 265 | + main() |
0 commit comments