Pilz Industrial Motion Planner
pilz_industrial_motion_planner
provides a trajectory generator to plan standard robot
motions like point-to-point, linear, and circular with MoveIt.
By loading the corresponding planning pipeline
(pilz_industrial_motion_planner_planning_planner.yaml
in your
*_moveit_config
package), the trajectory generation
functionalities can be accessed through the user interface (C++, Python
or RViz) provided by the move_group
node, e.g.
/plan_kinematic_path
service and /move_action
action.
For detailed usage tutorials, please refer to
MoveItCpp Tutorial and
Move Group C++ Interface.
Joint Limits
The planner uses maximum velocities and accelerations from the
parameters of the ROS node that is operating the Pilz planning pipeline.
Using the MoveIt Setup Assistant the file joint_limits.yaml
is auto-generated with proper defaults and loaded during startup.
The specified limits override the limits from the URDF robot description.
Note that while setting position limits and velocity limits is possible
in both the URDF and a parameter file, setting acceleration limits is
only possible using a parameter file. In addition to the common
has_acceleration
and max_acceleration
parameters, we added the
ability to also set has_deceleration
and max_deceleration
(<0.0).
The limits are merged under the premise that the limits from the node parameters must be stricter or at least equal to the parameters set in the URDF.
Currently, the calculated trajectory will respect the limits by using the strictest combination of all limits as a common limit for all joints.
Cartesian Limits
For Cartesian trajectory generation (LIN/CIRC), the planner needs information about the maximum speed in 3D Cartesian space. Namely, translational/rotational velocity/acceleration/deceleration need to be set in the node parameters like this:
cartesian_limits:
max_trans_vel: 1
max_trans_acc: 2.25
max_trans_dec: -5
max_rot_vel: 1.57
You can specify Cartesian velocity and acceleration limits in a file named
pilz_cartesian_limits.yaml
in your *_moveit_config
package.
The planners assume the same acceleration ratio for translational and
rotational trapezoidal shapes. The rotational acceleration is
calculated as max_trans_acc / max_trans_vel * max_rot_vel
(and for deceleration accordingly).
Planning Interface
This package uses moveit_msgs::msgs::MotionPlanRequest
and moveit_msgs::msg::MotionPlanResponse
as input and output for motion planning. The parameters needed for each planning algorithm
are explained below.
For a general introduction on how to fill a MotionPlanRequest
, see
Planning to a Pose goal.
You can specify "PTP"
, "LIN"
or "CIRC"
as the planner_id
of the MotionPlanRequest
.
The PTP motion command
This planner generates fully synchronized point-to-point trajectories with trapezoidal joint velocity profiles. All joints are assumed to have the same maximal joint velocity/acceleration/deceleration limits. If not, the strictest limits are adopted. The axis with the longest time to reach the goal is selected as the lead axis. Other axes are decelerated so that they share the same acceleration/constant velocity/deceleration phases as the lead axis.
PTP Input Parameters in moveit_msgs::MotionPlanRequest
planner_id
:"PTP"
group_name
: the name of the planning groupmax_velocity_scaling_factor
: scaling factor of maximal joint velocitymax_acceleration_scaling_factor
: scaling factor of maximal joint acceleration/decelerationstart_state/joint_state/(name, position and velocity)
: joint name/position/velocity (optional) of the start state.goal_constraints
: (goal can be given in joint space or Cartesian space)- for a goal in joint space
goal_constraints/joint_constraints/joint_name
: goal joint namegoal_constraints/joint_constraints/position
: goal joint position
- for a goal in Cartesian space
goal_constraints/position_constraints/header/frame_id
: frame this data is associated withgoal_constraints/position_constraints/link_name
: target link namegoal_constraints/position_constraints/constraint_region
: bounding volume of the target pointgoal_constraints/position_constraints/target_point_offset
: offset (in the link frame) for the target point on the target link (optional)
PTP Planning Result in moveit_msg::MotionPlanResponse
trajectory_start
: first robot state of the planned trajectorytrajectory/joint_trajectory/joint_names
: a list of the joint names of the generated joint trajectorytrajectory/joint_trajectory/points/(positions,velocities,accelerations,time_from_start)
: a list of generated way points. Each point has positions/velocities/accelerations of all joints (same order as the joint names) and time from start. The last point will have zero velocity and acceleration.group_name
: the name of the planning grouperror_code/val
: error code of the motion planning
The LIN motion command
This planner generates a linear Cartesian trajectory between goal and start poses. The planner uses the Cartesian limits to generate a trapezoidal velocity profile in Cartesian space. The translational motion is a linear interpolation between start and goal position vector. The rotational motion is quaternion slerp between start and goal orientation. The translational and rotational motion is synchronized in time. This planner only accepts start state with zero velocity. Planning result is a joint trajectory. The user needs to adapt the Cartesian velocity/acceleration scaling factor if the motion plan fails due to violation of joint space limits.
LIN Input Parameters in moveit_msgs::MotionPlanRequest
planner_id
:"LIN"
group_name
: the name of the planning groupmax_velocity_scaling_factor
: scaling factor of maximal Cartesian translational/rotational velocitymax_acceleration_scaling_factor
: scaling factor of maximal Cartesian translational/rotational acceleration/decelerationstart_state/joint_state/(name, position and velocity
: joint name/position of the start state.goal_constraints
(goal can be given in joint space or Cartesian space)for a goal in joint space
goal_constraints/joint_constraints/joint_name
: goal joint namegoal_constraints/joint_constraints/position
: goal joint position
for a goal in Cartesian space
goal_constraints/position_constraints/header/frame_id
: frame this data is associated withgoal_constraints/position_constraints/link_name
: target link namegoal_constraints/position_constraints/constraint_region
: bounding volume of the target pointgoal_constraints/position_constraints/target_point_offset
: offset (in the link frame) for the target point on the target link (optional)
LIN Planning Result in moveit_msg::MotionPlanResponse
trajectory_start
: first robot state of the planned trajectorytrajectory/joint_trajectory/joint_names
: a list of the joint names of the generated joint trajectorytrajectory/joint_trajectory/points/(positions,velocities,accelerations,time_from_start)
: a list of generated way points. Each point has positions/velocities/accelerations of all joints (same order as the joint names) and time from start. The last point will have zero velocity and acceleration.group_name
: the name of the planning grouperror_code/val
: error code of the motion planning
The CIRC motion command
This planner generates a circular arc trajectory in Cartesian space between goal and start poses. There are two options for giving a path constraint:
the center point of the circle: The planner always generates the shorter arc between start and goal and cannot generate a half circle,
an interim point on the arc: The generated trajectory always goes through the interim point. The planner cannot generate a full circle.
The Cartesian limits, namely translational/rotational velocity/acceleration/deceleration need to be set and the planner uses these limits to generate a trapezoidal velocity profile in Cartesian space. The rotational motion is quaternion slerp between start and goal orientation. The translational and rotational motion is synchronized in time. This planner only accepts start state with zero velocity. The planning result is a joint trajectory. The user needs to adapt the Cartesian velocity/acceleration scaling factor if motion plan fails due to violation of joint limits.
CIRC Input Parameters in moveit_msgs::MotionPlanRequest
planner_id
:"CIRC"
group_name
: the name of the planning groupmax_velocity_scaling_factor
: scaling factor of maximal Cartesian translational/rotational velocitymax_acceleration_scaling_factor
: scaling factor of maximal Cartesian translational/rotational acceleration/decelerationstart_state/joint_state/(name, position and velocity
: joint name/position of the start state.goal_constraints
(goal can be given in joint space or Cartesian space)for a goal in joint space
goal_constraints/joint_constraints/joint_name
: goal joint namegoal_constraints/joint_constraints/position
: goal joint position
for a goal in Cartesian space
goal_constraints/position_constraints/header/frame_id
: frame this data is associated withgoal_constraints/position_constraints/link_name
: target link namegoal_constraints/position_constraints/constraint_region
: bounding volume of the target pointgoal_constraints/position_constraints/target_point_offset
: offset (in the link frame) for the target point on the target link (optional)
path_constraints
(position of the interim/center point)path_constraints/name
: interim or centerpath_constraints/position_constraints/constraint_region/primitive_poses/point
: position of the point
CIRC Planning Result in moveit_msg::MotionPlanResponse
trajectory_start
: first robot state of the planned trajectorytrajectory/joint_trajectory/joint_names
: a list of the joint names of the generated joint trajectorytrajectory/joint_trajectory/points/(positions,velocities,accelerations,time_from_start)
: a list of generated way points. Each point has positions/velocities/accelerations of all joints (same order as the joint names) and time from start. The last point will have zero velocity and acceleration.group_name
: the name of the planning grouperror_code/val
: error code of the motion planning
Examples
By running
ros2 launch moveit_resources_panda_moveit_config demo.launch.py
you can interact with the planner through the RViz MotionPlanning panel.
To use the planner through the MoveGroup Interface, refer to the MoveGroup Interface C++ example. To run this, execute the following commands in separate Terminals:
ros2 launch moveit2_tutorials pilz_moveit.launch.py
ros2 run moveit2_tutorials pilz_move_group
To use the planner using MoveIt Task Constructor, refer to the MoveIt Task Constructor C++ example. To run this, execute the following commands in separate Terminals:
ros2 launch moveit2_tutorials mtc_demo.launch.py
ros2 launch moveit2_tutorials pilz_mtc.launch.py
Using the planner
The pilz_industrial_motion_planner::CommandPlanner is provided as a MoveIt Motion Planning
Pipeline and, therefore, can be used with all other manipulators using
MoveIt. Loading the plugin requires the param
/move_group/<pipeline_name>/planning_plugins
to be set to [pilz_industrial_motion_planner/CommandPlanner]
before the move_group
node is started.
For example, the panda_moveit_config package
has a pilz_industrial_motion_planner
pipeline set up as follows:
ros2 param get /move_group pilz_industrial_motion_planner.planning_plugins
String value is: pilz_industrial_motion_planner/CommandPlanner
To use the command planner, Cartesian limits have to be defined. The
limits are expected to be under the namespace
<robot_description>_planning
, where <robot_description>
refers
to the parameter name under which the URDF is loaded.
For example, if the URDF was loaded into /robot_description
the
Cartesian limits have to be defined at /robot_description_planning
.
You can set these using a pilz_cartesian_limits.yaml
file in your
*_moveit_config
package.
An example showing this file can be found in panda_moveit_config.
To verify the limits were set correctly, you can check the parameters for your
move_group
node. For example,
ros2 param list /move_group --filter .*cartesian_limits
/move_group:
robot_description_planning.cartesian_limits.max_rot_vel
robot_description_planning.cartesian_limits.max_trans_acc
robot_description_planning.cartesian_limits.max_trans_dec
robot_description_planning.cartesian_limits.max_trans_vel
Sequence of multiple segments
To concatenate multiple trajectories and plan the trajectory at once, you can use the sequence capability. This reduces the planning overhead and allows to follow a pre-desribed path without stopping at intermediate points.
Please note: In case the planning of a command in a sequence fails, non of the commands in the sequence are executed.
Please note: Sequences commands are allowed to contain commands for multiple groups (e.g. “Manipulator”, “Gripper”)
User interface sequence capability
A specialized MoveIt functionality known as the
command list manager
takes a moveit_msgs::msg::MotionSequenceRequest
as input.
The request contains a list of subsequent goals as described above and an additional
blend_radius
parameter. If the given blend_radius
in meter is
greater than zero, the corresponding trajectory is merged together with
the following goal such that the robot does not stop at the current
goal. When the TCP comes closer to the goal than the given
blend_radius
, it is allowed to travel towards the next goal already.
When leaving a sphere around the current goal, the robot returns onto
the trajectory it would have taken without blending.
Implementation details are available as PDF.
Restrictions for MotionSequenceRequest
Only the first goal may have a start state. Following trajectories start at the previous goal.
Two subsequent
blend_radius
spheres must not overlap.blend_radius
(i) +blend_radius
(i+1) has to be smaller than the distance between the goals.
Service interface
The service plan_sequence_path
allows the user to generate a joint
trajectory for a moveit_msgs::msg::MotionSequenceRequest
.
The trajectory is returned and not executed.
To use the MoveGroupSequenceService
and the MoveGroupSequenceAction
, refer to the Pilz Motion Planner sequence example.
To run this, execute the following commands in separate terminals:
ros2 launch moveit2_tutorials pilz_moveit.launch.py
ros2 run moveit2_tutorials pilz_sequence
For this service and action, the move_group launch file needs to be modified to include these Pilz Motion Planner capabilities. The new pilz_moveit.launch.py is used instead:
moveit_config = (
MoveItConfigsBuilder("moveit_resources_panda")
.robot_description(file_path="config/panda.urdf.xacro")
.trajectory_execution(file_path="config/gripper_moveit_controllers.yaml")
.planning_scene_monitor(
publish_robot_description=True, publish_robot_description_semantic=True
)
.planning_pipelines(
pipelines=["pilz_industrial_motion_planner"]
)
.to_moveit_configs()
)
# Starts Pilz Industrial Motion Planner MoveGroupSequenceAction and MoveGroupSequenceService servers
move_group_capabilities = {
"capabilities": "pilz_industrial_motion_planner/MoveGroupSequenceAction pilz_industrial_motion_planner/MoveGroupSequenceService"
}
The pilz_sequence.cpp file creates two target poses that will be reached sequentially.
// ----- Motion Sequence Item 1
// Create a MotionSequenceItem
moveit_msgs::msg::MotionSequenceItem item1;
// Set pose blend radius
item1.blend_radius = 0.1;
// MotionSequenceItem configuration
item1.req.group_name = PLANNING_GROUP;
item1.req.planner_id = "LIN";
item1.req.allowed_planning_time = 5.0;
item1.req.max_velocity_scaling_factor = 0.1;
item1.req.max_acceleration_scaling_factor = 0.1;
moveit_msgs::msg::Constraints constraints_item1;
moveit_msgs::msg::PositionConstraint pos_constraint_item1;
pos_constraint_item1.header.frame_id = "world";
pos_constraint_item1.link_name = "panda_hand";
// Set a constraint pose
auto target_pose_item1 = [] {
geometry_msgs::msg::PoseStamped msg;
msg.header.frame_id = "world";
msg.pose.position.x = 0.3;
msg.pose.position.y = -0.2;
msg.pose.position.z = 0.6;
msg.pose.orientation.x = 1.0;
msg.pose.orientation.y = 0.0;
msg.pose.orientation.z = 0.0;
msg.pose.orientation.w = 0.0;
return msg;
} ();
item1.req.goal_constraints.push_back(kinematic_constraints::constructGoalConstraints("panda_link8", target_pose_item1));
The service client needs to be initialized:
// MoveGroupSequence service client
using GetMotionSequence = moveit_msgs::srv::GetMotionSequence;
auto service_client = node->create_client<GetMotionSequence>("/plan_sequence_path");
// Verify that the action server is up and running
while (!service_client->wait_for_service(std::chrono::seconds(10)))
{
RCLCPP_WARN(LOGGER, "Waiting for service /plan_sequence_path to be available...");
}
Then, the request is created:
// Create request
auto service_request = std::make_shared<GetMotionSequence::Request>();
service_request->request.items.push_back(item1);
service_request->request.items.push_back(item2);
Once the service call is completed, the method future.wait_for(timeout_duration)
blocks until
a specified timeout_duration
has elapsed or the result becomes available, whichever comes
first. The return value identifies the state of the result. This is performed every second
until the future is ready.
// Call the service and process the result
auto service_future = service_client->async_send_request(service_request);
// Function to draw the trajectory
auto const draw_trajectory_tool_path =
[&moveit_visual_tools, jmg = move_group_interface.getRobotModel()->getJointModelGroup("panda_arm")](
auto const& trajectories) {
for (const auto& trajectory : trajectories) {
moveit_visual_tools.publishTrajectoryLine(trajectory, jmg);
}
};
// Wait for the result
std::future_status service_status;
do
{
switch (service_status = service_future.wait_for(std::chrono::seconds(1)); service_status)
{
case std::future_status::deferred:
RCLCPP_ERROR(LOGGER, "Deferred");
break;
case std::future_status::timeout:
RCLCPP_INFO(LOGGER, "Waiting for trajectory plan...");
break;
case std::future_status::ready:
RCLCPP_INFO(LOGGER, "Service ready!");
break;
}
} while (service_status != std::future_status::ready);
The future response is read with the future.get()
method.
auto service_response = service_future.get();
if (service_response->response.error_code.val == moveit_msgs::msg::MoveItErrorCodes::SUCCESS)
{
RCLCPP_INFO(LOGGER, "Planning successful");
// Access the planned trajectory
auto trajectory = service_response->response.planned_trajectories;
draw_trajectory_tool_path(trajectory);
moveit_visual_tools.trigger();
}
else
{
RCLCPP_ERROR(LOGGER, "Planning failed with error code: %d", service_response->response.error_code.val);
rclcpp::shutdown();
return 0;
}
In this case, the planned trajectory is drawn. Here is a comparison of a blend radius of 0 and 0.1 for the first and second trajectory, respectively.
Action interface
In analogy to the MoveGroup
action interface, the user can plan and
execute a moveit_msgs::MotionSequenceRequest
through the action server
at /sequence_move_group
.
In one point the MoveGroupSequenceAction
differs from the standard
MoveGroup capability: If the robot is already at the goal position, the
path is still executed. The underlying PlannerManager can check, if the
constraints of an individual moveit_msgs::msg::MotionPlanRequest
are
already satisfied but the MoveGroupSequenceAction
capability doesn’t
implement such a check to allow moving on a circular or comparable path.
The action client needs to be initialized:
// MoveGroupSequence action client
using MoveGroupSequence = moveit_msgs::action::MoveGroupSequence;
auto action_client = rclcpp_action::create_client<MoveGroupSequence>(node, "/sequence_move_group");
// Verify that the action server is up and running
if (!action_client->wait_for_action_server(std::chrono::seconds(10)))
{
RCLCPP_ERROR(LOGGER, "Error waiting for action server /sequence_move_group");
return -1;
}
Then, the request is created:
// Create a MotionSequenceRequest
moveit_msgs::msg::MotionSequenceRequest sequence_request;
sequence_request.items.push_back(item1);
sequence_request.items.push_back(item2);
The goal and planning options are configured. A goal response callback and result callback can be included as well.
// Create action goal
auto goal_msg = MoveGroupSequence::Goal();
goal_msg.request = sequence_request;
// Planning options
goal_msg.planning_options.planning_scene_diff.is_diff = true;
goal_msg.planning_options.planning_scene_diff.robot_state.is_diff = true;
// goal_msg.planning_options.plan_only = true; // Uncomment to only plan the trajectory
Finally, send the goal request and wait for the response:
// Send the action goal
auto goal_handle_future = action_client->async_send_goal(goal_msg, send_goal_options);
// Get result
auto action_result_future = action_client->async_get_result(goal_handle_future.get());
// Wait for the result
std::future_status action_status;
do
{
switch (action_status = action_result_future.wait_for(std::chrono::seconds(1)); action_status)
{
case std::future_status::deferred:
RCLCPP_ERROR(LOGGER, "Deferred");
break;
case std::future_status::timeout:
RCLCPP_INFO(LOGGER, "Executing trajectory...");
break;
case std::future_status::ready:
RCLCPP_INFO(LOGGER, "Action ready!");
break;
}
} while (action_status != std::future_status::ready);
if (action_result_future.valid())
{
auto result = action_result_future.get();
RCLCPP_INFO(LOGGER, "Action completed. Result: %d", static_cast<int>(result.code));
}
else
{
RCLCPP_ERROR(LOGGER, "Action couldn't be completed.");
}
If the motion needs to be stopped mid-execution, the action can be canceled with:
auto future_cancel_motion = client->async_cancel_goal(goal_handle_future_new.get());