Skip to main content

Plan robot motion

The Intrinsic platform's Motion Planner Service provides motion planning and related operations, such as kinematics and collision checking. It is used by existing first-party Skills such as MoveRobot and is also available for use by third party Skills and Services.

The Motion Planner Service is a fundamental service in the Intrinsic platform and is always available to the Skill developer. Like most Intrinsic services, the Motion Planner Service provides a gRPC API. The service itself cannot be configured by Skills. Instead, all relevant parameters such as planning timeouts are passed as part of a Skill's request to the service.

Setup

Before you can follow this guide you must create a Solution with a robot, and you must select it as the target Solution in your development environment. Follow the guides for setting up your development environment and making your first Skill to learn how to do that.

There are two execution modes: "Preview" and "Full". In "Preview" mode only Skill's preview method is called. The preview method returns what a Skill would do if executed, without actually executing it. Our example Skill only implements the execute method, so you must set your Solution's execution mode to "Full".

Execution mode "Full"

In this guide, we will be using the machine_tending:machine_tending_solution example Solution for demonstrating the Motion Planner Service functionalities mentioned above. However, this guide can be used with any Solution which contains a robot kinematic object.

Create a new Skill

Create a new Python Skill in your development environment, and give it the ID com.example.plan_robot_motion. When prompted for the folder name, give skills/plan_robot_motion.

Parameters and outputs

The first step when creating a Skill is to decide on its input and output parameters. These are defined by protobuf messages.

Let's make this Skill accept parameters to reference frames in the Solution. For this example, we will use one TransformNodeReference type parameter for defining the robot gripper tip and another TransformNodeReference type parameter to define a target frame in the world. You can find more information about various types of world references here.

Put the following into the file plan_robot_motion/plan_robot_motion.proto to define the PlanRobotMotionParams message.

syntax = "proto3";

package com.intrinsic;

import "intrinsic/world/proto/object_world_refs.proto";

message PlanRobotMotionParams {
intrinsic_proto.world.TransformNodeReference gripper_tip_ref = 1;
intrinsic_proto.world.TransformNodeReference target_frame_ref = 2;
}

Ensure the skill manifest includes the following lines to specify that this message defines the skill parameters, enabling the Flowstate Editor to process it correctly.

parameter {
message_full_name: "com.example.PlanRobotMotionParams"
}

Also, ensure that the build rule for the proto in the BUILD file contains the correct dependency as follows:

proto_library(
name = "plan_robot_motion_proto",
srcs = ["plan_robot_motion.proto"],
deps = ["@ai_intrinsic_sdks//intrinsic/world/proto:object_world_refs_proto"],
)

Next, build and install the Skill to your target application to check if the parameter fields are correctly reflected in the Skill inputs. It should look as the following image.

Plan Robot Motion Skill Inputs

Equipment

The equipment objects in the world can be accessed directly using the equipment information passed to the Skill. It is not necessary to add references to Skill's parameter message for the equipment. If the Skill requires equipment, such as a robot or camera, it declares that dependency by adding it to the manifest.

Ensure that the dependencies block in plan_robot_motion.manifest.textproto contains the robot equipment as defined below.

dependencies {
required_equipment {
key: "robot"
value {
capability_names: "Icon2Connection"
capability_names: "Icon2PositionPart"
}
}
}

Add the following constants to the top of plan_robot_motion.py.

# Top level constants
ROBOT_EQUIPMENT_SLOT: str = "robot"

Then add the following to the execute() method in plan_robot_motion.py.


# ... in execute() method:

# Unpack resource handles.
robot_handle = context.resource_handles[ROBOT_EQUIPMENT_SLOT]

# Get connection to world service.
object_world = context.object_world

# Get the robot kinematic object from the world.
robot_kinematic_object = object_world.get_kinematic_object(robot_handle)

logging.info(f"Current robot joint positions: {robot_kinematic_object.joint_positions}")

Install the Skill again and run it. You will see an additional message like this in the Skill logs.

[-1.4500000000000004, -1.1800000000000719, -2.149999999999944, -1.349999999999984, 1.57, -0.5000000000000004]

Access the Motion Planner Service

The Intrinsic platform provides a motion planning client class for the Motion Planner Service. This client wraps the gRPC interface, providing a convenient class for interfacing to the service with methods provided for each of the planning operations.

Add the following import to the plan_robot_motion.py file.

from intrinsic.motion_planning import motion_planner_client

Add the following dependency to the build rule for plan_robot_motion.py in the BUILD file.

  "@ai_intrinsic_sdks//intrinsic/motion_planning:motion_planner_client_py",

The following code shows how to get an instance of the client:

@overrides(skill_interface.Skill)
def execute(
self,
request: skill_interface.ExecuteRequest,
context: skill_interface.ExecuteContext,
) -> None:

. . .

# Get connection to the motion planner service
mp_client: motion_planner_client.MotionPlannerClient = context.motion_planner

The motion planning client is created for a specific world, based on a world ID. When a planning operation is called on the client, the Motion Planner Service retrieves the corresponding world from the world service as a first step. The context.motion_planner above uses the belief world (a specific world maintained by the world service that represents the current state), which is usually what you want in a Skill's execute method. If you want to plan for a different world, you must instantiate the client object with the world ID of the world you want to reference, as well as the gRPC stub for the Motion Planner Service.

note

The Motion Planner Service and client may be treated as stateless (while there may be caching and other optimizations under the hood, calling code shouldn't depend on this).

Plan a Trajectory

The main operation provided by the Motion Planner Service is trajectory planning. This includes path planning, which finds a sequence of waypoints in configuration space, as well as inter-segment blending and time parametrization of that path. Trajectory planning depends on:

  • A world - As mentioned above, the world is specified when creating the client. The world includes the relevant collision geometry, kinematic models, and the start configuration of the given arm (the latter can be overridden by the robot specification).
  • A robot specification describing the arm for which you are planning the motion - The main required field here is the ID of the robot in the world.
  • A motion specification defining the goal and path constraints - It is a flexible representation that allows for specifying various kinds of motions.

In order to specify the parameters for trajectory planning, first we need to add the following imports to plan_robot_motion.py.

from intrinsic.motion_planning.proto.v1 import robot_specification_pb2
from intrinsic.motion_planning.proto.v1 import motion_specification_pb2
from intrinsic.moiton_planning.proto.v1 import geometric_constraints_pb2
from intrinsic.world.proto import object_world_refs_pb2

Also, ensure that the build rule for plan_robot_motion.py in the BUILD file contains the following dependencies.

"@ai_intrinsic_sdks//intrinsic/motion_planning/proto/v1:robot_specification_py_pb2",
"@ai_intrinsic_sdks//intrinsic/motion_planning/proto/v1:motion_specification_py_pb2",
"@ai_intrinsic_sdks//intrinsic/motion_planning/proto/v1:geometric_constraints_py_pb2",
"@ai_intrinsic_sdks//intrinsic/world/proto:object_world_refs_py_pb2",

The following code snippet shows an example of how the robot specification and motion specification can be constructed and used for planning a trajectory using the motion planner client. You can add this snippet to the execute method of your Skill, followed by building and installing it in your application.

  @overrides(skill_interface.Skill)
def execute(
self,
request: skill_interface.ExecuteRequest,
context: skill_interface.ExecuteContext,
) -> None

. . .

# Get connection to the motion planner service
mp_client: motion_planner_client.MotionPlannerClient = context.motion_planner

robot_specification = robot_specification_pb2.RobotSpecification(
robot_reference=robot_specification_pb2.RobotReference(
object_id=object_world_refs_pb2.ObjectReference(
by_name=object_world_refs_pb2.ObjectReferenceByName(
object_name=robot_kinematic_object.name
)
)
)
)

pose_constraint = geometric_constraints_pb2.GeometricConstraint(
cartesian_pose=geometric_constraints_pb2.PoseEquality(
moving_frame=request.params.gripper_tip_ref,
target_frame=request.params.target_frame_ref
)
)

motion_segment = motion_specification_pb2.MotionSegment(target=pose_constraint)
motion_specification = motion_specification_pb2.MotionSpecification(motion_segments=[motion_segment])

result = mp_client.plan_trajectory(
robot_specification=robot_specification,
motion_specification=motion_specification
)

logging.info(f"Trajectory planning completed!")

Enter Parameters and Run

Once the Skill is successfully built and installed in the app, we can use it to plan motions for different targets. You can add an instance of the Skill to the process tree and parameterize it as follows for the machine_tending:machine_tending_solution Solution:

  • Choose a gripper tip

Choose Gripper Tip

  • Choose a target frame

Choose Target Frame

Once, the Gripper tip ref and Target frame ref parameters are set, you can run the process or the Skill node. If motion planning is successful, the Skill execution should succeed as well and you should see the Trajectory planning completed! message in the Skill logs. If not, the Process error message should specify why motion planning failed. Feel free to experiment with different target frames to see the results.

Compute Inverse Kinematic Solutions

In addition to trajectory planning, the Motion Planner Service provides forward and inverse kinematics.

Add the following imports to plan_robot_motion.py.

from intrinsic.icon.proto import joint_space_pb2
from intrinsic.world.proto import collision_settings_pb2

An example of inverse kinematics is shown below, it computes up to eight (if available) joint configurations of the robot robot for a desired Cartesian pose_constraint. Depending on the robot and IK solver, the Process might be seeded with the defined start configuration. The returned solution will be sorted according to the distance to the defined starting_joint configuration.

# Compute IK solutions without collision checking.
options = motion_planner_client.IKOptions(
max_num_solutions=8)

ik_solutions = mp_client.compute_ik(
robot_name=ROBOT_EQUIPMENT_SLOT,
target=pose_constraint,
starting_joints=[0, 0, 0, 0, 0, 0],
options=options)

logging.info(f"Computed {len(ik_solutions)} IK solutions for the target!")

None of the solutions returned are by default checked for collisions. If only collision free solutions are desired, collision checking can be specified as part of the IK options. The example below specifies the same IK request as above, but instead of returning all solutions, only collision free solutions that have at least a margin (i.e., distance) of 1 cm between all objects are returned.

# Compute IK solutions with collision checking.
collision_settings = collision_settings_pb2.CollisionSettings()
collision_settings.minimum_margin=0.01

options = motion_planner_client.IKOptions(
max_num_solutions=8,
collision_settings=collision_settings)

ik_solutions_with_collision_checking = mp_client.compute_ik(
robot_name=ROBOT_EQUIPMENT_SLOT,
target=pose_constraint,
starting_joints=[0, 0, 0, 0, 0, 0],
options=options)

logging.info(f"Computed {len(ik_solutions_with_collision_checking)} ",
"collision-free IK solutions for the target!")

Forward kinematics queries can be performed similarly. Note that depending on the use case, it may be more efficient to perform forward kinematics directly using the Object World API.

Check Collisions

In case a pre-existing or externally generated trajectory is executed, we strongly suggest to collision check this trajectory before execution. The motion planning API allows to check a set of waypoints for collision. It assumes linear interpolation between those waypoints. The example below shows how to collision check a path consisting of an initial and final configuration. The result is whether the path is in collision.

# Check collision for path between waypoints.
waypoints= [
joint_space_pb2.JointVec(joints=[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
joint_space_pb2.JointVec(joints=[0.0, 0.1, 0.0, 0.1, 0.0, 0.1])
]

collision_settings = collision_settings_pb2.CollisionSettings()
collision_settings.minimum_margin=0.01

options = motion_planner_client.CheckCollisionsOptions(
collision_settings=collision_settings)

check_collision_result = mp_client.check_collisions(
robot_name=ROBOT_EQUIPMENT_SLOT,
waypoints=waypoints,
options=options)

logging.info(f"The path between specified waypoints have collision: {check_collision_result.has_collision}. ",
f"Debug message: {check_collision_result.collision_debug_msg}")

Source code

The full source code for this example is available in the intrinsic-ai/sdk-examples repository.