From 88020006d6361116d5809559232f608a37413c49 Mon Sep 17 00:00:00 2001 From: Andrej Orsula Date: Mon, 2 May 2022 17:27:24 +0200 Subject: [PATCH] 2.1.0 (#11) * Add support for Cartesian paths (#8) * Add support for collision objects (#9) * Remove trimesh from ROS 2 dependencies Signed-off-by: Andrej Orsula * Expose cartesian planning as a parameter in the pose goal example Signed-off-by: Andrej Orsula * Add default test mesh for collision object example Signed-off-by: Andrej Orsula * Move public functions before private Signed-off-by: Andrej Orsula Co-authored-by: Nils Schulte <47043622+Schnilz@users.noreply.github.com> --- CMakeLists.txt | 1 + README.md | 4 +- examples/assets/suzanne.stl | Bin 0 -> 48484 bytes examples/ex_collision_object.py | 101 ++++++++++++++++++ examples/ex_pose_goal.py | 6 +- pymoveit2/moveit2.py | 182 ++++++++++++++++++++++++++++++-- 6 files changed, 283 insertions(+), 11 deletions(-) create mode 100644 examples/assets/suzanne.stl create mode 100755 examples/ex_collision_object.py diff --git a/CMakeLists.txt b/CMakeLists.txt index b2087af..aba31f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ ament_python_install_package(pymoveit2) # Install examples set(EXAMPLES_DIR examples) install(PROGRAMS + ${EXAMPLES_DIR}/ex_collision_object.py ${EXAMPLES_DIR}/ex_gripper_command.py ${EXAMPLES_DIR}/ex_gripper.py ${EXAMPLES_DIR}/ex_joint_goal.py diff --git a/README.md b/README.md index 504c3b2..a14644e 100644 --- a/README.md +++ b/README.md @@ -64,8 +64,8 @@ After that, the individual scripts can be run. ```bash # Move to joint configuration ros2 run pymoveit2 ex_joint_goal.py --ros-args -p joint_positions:="[1.57, -1.57, 0.0, -1.57, 0.0, 1.57, 0.7854]" -# Move to Cartesian pose -ros2 run pymoveit2 ex_pose_goal.py --ros-args -p position:="[0.25, 0.0, 1.0]" -p quat_xyzw:="[0.0, 0.0, 0.0, 1.0]" +# Move to Cartesian pose (motion in either joint or Cartesian space) +ros2 run pymoveit2 ex_pose_goal.py --ros-args -p position:="[0.25, 0.0, 1.0]" -p quat_xyzw:="[0.0, 0.0, 0.0, 1.0]" -p cartesian:=False # Repeatadly toggle the gripper (or use "open"/"close" actions) ros2 run pymoveit2 ex_gripper.py --ros-args -p action:="toggle" # Example of using MoveIt 2 Servo to move the end-effector in a circular motion diff --git a/examples/assets/suzanne.stl b/examples/assets/suzanne.stl new file mode 100644 index 0000000000000000000000000000000000000000..7c6e9cf1539e42dda42842427062c3eec0eea475 GIT binary patch literal 48484 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 literal 0 HcmV?d00001 diff --git a/examples/ex_collision_object.py b/examples/ex_collision_object.py new file mode 100755 index 0000000..6802213 --- /dev/null +++ b/examples/ex_collision_object.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 +""" +Example of adding and removing a collision object with a mesh geometry. +Note: Python module `trimesh` is required for this example (`pip install trimesh`). +`ros2 run pymoveit2 ex_collision_object.py --ros-args -p action:="add" -p position:="[0.5, 0.0, 0.5]" -p quat_xyzw:="[0.0, 0.0, -0.707, 0.707]"` +`ros2 run pymoveit2 ex_collision_object.py --ros-args -p action:="add" -p filepath:="./my_favourity_mesh.stl"` +`ros2 run pymoveit2 ex_collision_object.py --ros-args -p action:="remove"` +""" + +from os import path +from threading import Thread + +import rclpy +from rclpy.callback_groups import ReentrantCallbackGroup +from rclpy.node import Node + +from pymoveit2 import MoveIt2 +from pymoveit2.robots import panda + +DEFAULT_EXAMPLE_MESH = path.join( + path.dirname(path.realpath(__file__)), "assets", "suzanne.stl" +) + + +def main(args=None): + + rclpy.init(args=args) + + # Create node for this example + node = Node("ex_collision_object") + + # Declare parameter for joint positions + node.declare_parameter( + "filepath", + "", + ) + node.declare_parameter( + "action", + "add", + ) + node.declare_parameter("position", [0.5, 0.0, 0.5]) + node.declare_parameter("quat_xyzw", [0.0, 0.0, -0.707, 0.707]) + + # Create callback group that allows execution of callbacks in parallel without restrictions + callback_group = ReentrantCallbackGroup() + + # Create MoveIt 2 interface + moveit2 = MoveIt2( + node=node, + joint_names=panda.joint_names(), + base_link_name=panda.base_link_name(), + end_effector_name=panda.end_effector_name(), + group_name=panda.MOVE_GROUP_ARM, + callback_group=callback_group, + ) + + # Spin the node in background thread(s) + executor = rclpy.executors.MultiThreadedExecutor(2) + executor.add_node(node) + executor_thread = Thread(target=executor.spin, daemon=True, args=()) + executor_thread.start() + + # Get parameters + filepath = node.get_parameter("filepath").get_parameter_value().string_value + action = node.get_parameter("action").get_parameter_value().string_value + position = node.get_parameter("position").get_parameter_value().double_array_value + quat_xyzw = node.get_parameter("quat_xyzw").get_parameter_value().double_array_value + + # Use the default example mesh if invalid + if not filepath: + node.get_logger().info(f"Using the default example mesh file") + filepath = DEFAULT_EXAMPLE_MESH + + # Make sure the mesh file exists + if not path.exists(filepath): + node.get_logger().error(f"File '{filepath}' does not exist") + rclpy.shutdown() + exit(1) + + # Determine ID of the collision mesh + mesh_id = path.basename(filepath).split(".")[0] + + if "add" == action: + # Add collision mesh + node.get_logger().info( + f"Adding collision mesh '{filepath}' {{position: {list(position)}, quat_xyzw: {list(quat_xyzw)}}}" + ) + moveit2.add_collision_mesh( + filepath=filepath, id=mesh_id, position=position, quat_xyzw=quat_xyzw + ) + else: + # Remove collision mesh + node.get_logger().info(f"Removing collision mesh with ID '{mesh_id}'") + moveit2.remove_collision_mesh(id=mesh_id) + + rclpy.shutdown() + exit(0) + + +if __name__ == "__main__": + main() diff --git a/examples/ex_pose_goal.py b/examples/ex_pose_goal.py index 13075be..d35c6ea 100755 --- a/examples/ex_pose_goal.py +++ b/examples/ex_pose_goal.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """ Example of moving to a pose goal. -`ros2 run pymoveit2 ex_pose_goal.py --ros-args -p position:="[0.25, 0.0, 1.0]" -p quat_xyzw:="[0.0, 0.0, 0.0, 1.0]"` +`ros2 run pymoveit2 ex_pose_goal.py --ros-args -p position:="[0.25, 0.0, 1.0]" -p quat_xyzw:="[0.0, 0.0, 0.0, 1.0]" -p cartesian:=False` """ from threading import Thread @@ -24,6 +24,7 @@ def main(args=None): # Declare parameters for position and orientation node.declare_parameter("position", [0.5, 0.0, 0.25]) node.declare_parameter("quat_xyzw", [1.0, 0.0, 0.0, 0.0]) + node.declare_parameter("cartesian", False) # Create callback group that allows execution of callbacks in parallel without restrictions callback_group = ReentrantCallbackGroup() @@ -47,12 +48,13 @@ def main(args=None): # Get parameters position = node.get_parameter("position").get_parameter_value().double_array_value quat_xyzw = node.get_parameter("quat_xyzw").get_parameter_value().double_array_value + cartesian = node.get_parameter("cartesian").get_parameter_value().bool_value # Move to pose node.get_logger().info( f"Moving to {{position: {list(position)}, quat_xyzw: {list(quat_xyzw)}}}" ) - moveit2.move_to_pose(position=position, quat_xyzw=quat_xyzw) + moveit2.move_to_pose(position=position, quat_xyzw=quat_xyzw, cartesian=cartesian) moveit2.wait_until_executed() rclpy.shutdown() diff --git a/pymoveit2/moveit2.py b/pymoveit2/moveit2.py index 88f64c1..c5019b0 100644 --- a/pymoveit2/moveit2.py +++ b/pymoveit2/moveit2.py @@ -6,13 +6,19 @@ from geometry_msgs.msg import Point, Pose, PoseStamped, Quaternion from moveit_msgs.action import MoveGroup from moveit_msgs.msg import ( + CollisionObject, Constraints, JointConstraint, MoveItErrorCodes, OrientationConstraint, PositionConstraint, ) -from moveit_msgs.srv import GetMotionPlan, GetPositionFK, GetPositionIK +from moveit_msgs.srv import ( + GetCartesianPath, + GetMotionPlan, + GetPositionFK, + GetPositionIK, +) from rclpy.action import ActionClient from rclpy.callback_groups import CallbackGroup from rclpy.node import Node @@ -23,7 +29,7 @@ QoSReliabilityPolicy, ) from sensor_msgs.msg import JointState -from shape_msgs.msg import SolidPrimitive +from shape_msgs.msg import Mesh, MeshTriangle, SolidPrimitive from trajectory_msgs.msg import JointTrajectory, JointTrajectoryPoint @@ -131,6 +137,20 @@ def __init__( ) self.__kinematic_path_request = GetMotionPlan.Request() + # Create a separate service client for Cartesian planning + self._plan_cartesian_path_service = self._node.create_client( + srv_type=GetCartesianPath, + srv_name="compute_cartesian_path", + qos_profile=QoSProfile( + durability=QoSDurabilityPolicy.VOLATILE, + reliability=QoSReliabilityPolicy.RELIABLE, + history=QoSHistoryPolicy.KEEP_LAST, + depth=1, + ), + callback_group=callback_group, + ) + self.__cartesian_path_request = GetCartesianPath.Request() + # Create action client for trajectory execution self.__follow_joint_trajectory_action_client = ActionClient( node=self._node, @@ -169,6 +189,10 @@ def __init__( callback_group=self._callback_group, ) + self.__collision_object_publisher = self._node.create_publisher( + CollisionObject, "/collision_object", 10 + ) + self.__joint_state_mutex = threading.Lock() self.__joint_state = None self.__new_joint_state_available = False @@ -208,6 +232,7 @@ def move_to_pose( tolerance_position: float = 0.001, tolerance_orientation: float = 0.001, weight_position: float = 1.0, + cartesian: bool = False, weight_orientation: float = 1.0, ): """ @@ -255,6 +280,7 @@ def move_to_pose( tolerance_orientation=tolerance_orientation, weight_position=weight_position, weight_orientation=weight_orientation, + cartesian=cartesian, ) ) @@ -263,6 +289,7 @@ def move_to_configuration( joint_positions: List[float], joint_names: Optional[List[str]] = None, tolerance: float = 0.001, + cartesian: bool = False, weight: float = 1.0, ): """ @@ -303,6 +330,7 @@ def move_to_configuration( joint_names=joint_names, tolerance_joint_position=tolerance, weight_joint_position=weight, + cartesian=cartesian, ) ) @@ -322,6 +350,7 @@ def plan( weight_orientation: float = 1.0, weight_joint_position: float = 1.0, start_joint_state: Optional[Union[JointState, List[float]]] = None, + cartesian: bool = False, ) -> Optional[JointTrajectory]: """ Plan motion based on previously set goals. Optional arguments can be passed in to @@ -372,12 +401,15 @@ def plan( self.__move_action_goal.request.start_state.joint_state = self.joint_state # Plan trajectory by sending a goal (blocking) - if self.__execute_via_moveit: - # Use action client - joint_trajectory = self._send_goal_move_action_plan_only() + if cartesian: + joint_trajectory = self._plan_cartesian_path() else: - # Use service - joint_trajectory = self._plan_kinematic_path() + if self.__execute_via_moveit: + # Use action client + joint_trajectory = self._send_goal_move_action_plan_only() + else: + # Use service + joint_trajectory = self._plan_kinematic_path() # Clear all previous goal constrains self.clear_goal_constraints() @@ -788,6 +820,77 @@ def force_reset_executing_state(self): self.__is_motion_requested = False self.__is_executing = False + def add_collision_mesh( + self, + filepath: str, + id: str, + position: Union[Point, Tuple[float, float, float]], + quat_xyzw: Union[Quaternion, Tuple[float, float, float, float]], + operation: int = CollisionObject.ADD, + frame_id: Optional[str] = None, + ): + """ + Add collision object with a mesh geometry specified by `filepath`. + Note: This function required 'trimesh' Python module to be installed. + """ + + try: + import trimesh + except ImportError as err: + raise ImportError( + "Python module 'trimesh' not found! Please install it manually in order " + "to add collision objects into the MoveIt 2 planning scene." + ) from err + + mesh = trimesh.load(filepath) + msg = CollisionObject() + + if not isinstance(position, Point): + position = Point( + x=float(position[0]), y=float(position[1]), z=float(position[2]) + ) + if not isinstance(quat_xyzw, Quaternion): + quat_xyzw = Quaternion( + x=float(quat_xyzw[0]), + y=float(quat_xyzw[1]), + z=float(quat_xyzw[2]), + w=float(quat_xyzw[3]), + ) + + pose = Pose() + pose.position = position + pose.orientation = quat_xyzw + msg.pose = pose + + msg.meshes.append( + Mesh( + triangles=[MeshTriangle(vertex_indices=face) for face in mesh.faces], + vertices=[ + Point(x=vert[0], y=vert[1], z=vert[2]) for vert in mesh.vertices + ], + ) + ) + + msg.id = id + msg.operation = operation + msg.header.frame_id = ( + frame_id if frame_id is not None else self.__base_link_name + ) + msg.header.stamp = self._node.get_clock().now().to_msg() + + self.__collision_object_publisher.publish(msg) + + def remove_collision_mesh(self, id: str): + """ + Remove collision object specified by its `id`. + """ + + msg = CollisionObject() + msg.id = id + msg.operation = CollisionObject.REMOVE + msg.header.stamp = self._node.get_clock().now().to_msg() + self.__collision_object_publisher.publish(msg) + def __joint_state_callback(self, msg: JointState): # Update only if all relevant joints are included in the message @@ -873,6 +976,71 @@ def _plan_kinematic_path( ) return None + def _plan_cartesian_path( + self, + max_step: float = 0.0025, + wait_for_server_timeout_sec: Optional[float] = 1.0, + ) -> Optional[JointTrajectory]: + + # Re-use request from move action goal + self.__cartesian_path_request.start_state = ( + self.__move_action_goal.request.start_state + ) + self.__cartesian_path_request.group_name = ( + self.__move_action_goal.request.group_name + ) + self.__cartesian_path_request.link_name = self.__end_effector_name + self.__cartesian_path_request.max_step = max_step + + stamp = self._node.get_clock().now().to_msg() + self.__cartesian_path_request.header.stamp = stamp + + self.__cartesian_path_request.path_constraints = ( + self.__move_action_goal.request.path_constraints + ) + for ( + position_constraint + ) in self.__cartesian_path_request.path_constraints.position_constraints: + position_constraint.header.stamp = stamp + for ( + orientation_constraint + ) in self.__cartesian_path_request.path_constraints.orientation_constraints: + orientation_constraint.header.stamp = stamp + # no header in joint_constraint message type + + target_pose = Pose() + target_pose.position = ( + self.__move_action_goal.request.goal_constraints[-1] + .position_constraints[-1] + .constraint_region.primitive_poses[0] + .position + ) + target_pose.orientation = ( + self.__move_action_goal.request.goal_constraints[-1] + .orientation_constraints[-1] + .orientation + ) + + self.__cartesian_path_request.waypoints = [target_pose] + + if not self._plan_cartesian_path_service.wait_for_service( + timeout_sec=wait_for_server_timeout_sec + ): + self._node.get_logger().warn( + f"Service '{self._plan_cartesian_path_service.srv_name}' is not yet available. Better luck next time!" + ) + return None + + res = self._plan_cartesian_path_service.call(self.__cartesian_path_request) + + if MoveItErrorCodes.SUCCESS == res.error_code.val: + return res.solution.joint_trajectory + else: + self._node.get_logger().warn( + f"Planning failed! Error code: {res.error_code.val}." + ) + return None + def _send_goal_async_move_action( self, wait_for_server_timeout_sec: Optional[float] = 1.0 ):