Main Content

Generate Code for Manipulator Motion Planning in Perceived Environment

This example shows how to generate code for planning manipulator motion in a perceived environment. Perceived environments can have a variable number of collision objects that can be a combination of heterogeneous types (spheres, cylinders, meshes, and boxes). This example uses the convertToCollisionMesh function to homogenize the cell array of collision objects by converting the primitive type objects to their mesh equivalents.

Set Up Robot and Environment

In this example, you generate a MEX function for a MATLAB® function that uses a manipulatorRRT object to plan for a Kinova® Gen 3 robot.

Load Robot Model

Create a rigid body tree object to model the robot. For this example, load a Kinova Gen3 manipulator.

kinova = loadrobot("kinovaGen3",DataFormat="row");

Create Environment

Real perception subsystems can output a structure that specifies the type of perceived geometry, its dimensions, and the pose of the object in the base frame of the robot. To simulate this, first create a structure to contain collision objects.

geomStructType = struct("Type",exampleHelperCollisionEnum.Box, ...
                  "X",0, ...
                  "Y",0, ...
                  "Z",0, ...
                  "Vertices",coder.typeof(zeros(3),[inf 3],[1 0]), ...
                  "Radius",0, ...
                  "Height",0, ...
                  "Pose",eye(4));
geomStruct = geomStructType;
geomStruct.Vertices=zeros(3);

Next create a cylinder, a box, and two spheres, as perceived by a perception subsystem.

Use geomStruct as the framework to create a perceived box with X, Y, and Z lengths of 1, 1, and 0.1, respectively.

boxGeom = geomStruct;
boxGeom.Type = exampleHelperCollisionEnum.Box;
boxGeom.X = 1;
boxGeom.Y = 1;
boxGeom.Z = 0.1;
boxGeom.Pose = trvec2tform([0 0 -0.051]);

Create a perceived cylinder of radius 1 and height 0.1.

cylinderGeom = geomStruct;
cylinderGeom.Type = exampleHelperCollisionEnum.Cylinder;
cylinderGeom.Radius = 0.1;
cylinderGeom.Height = 1.0;
cylinderGeom.Pose = trvec2tform([0.5 0.3 0.50]);

Create two perceived spheres, each with a radius of 0.1.

sphere1Geom = geomStruct;
sphere1Geom.Type = exampleHelperCollisionEnum.Sphere;
sphere1Geom.Radius = 0.1;
sphere1Geom.Pose = trvec2tform([0.4 0.4 0.4]);
sphere2Geom = geomStruct;
sphere2Geom.Type = exampleHelperCollisionEnum.Sphere;
sphere2Geom.Radius = 0.1;
sphere2Geom.Pose = trvec2tform([-0.4 0.4 0.4]);

Set Up Planning Variables

The, exampleHelperVariableHeterogeneousPlanner helper function accepts the start and goal joint configurations of the robot, as well as an array of structure elements representing the environment. In this example, the start and goal joint configurations are set arbitrarily, but you can set them as necessary.

startConfig = [-0.0035 1.4453 0.0334 0.0144 -0.0294 1.0600 -0.0077];
goalConfig = [-1.1519 1.5243 0.3186 0.0009 0.2466 1.2090 -1.1952];
env1 = [boxGeom cylinderGeom sphere1Geom sphere2Geom];

Visualize the start configuration of the robot in the perceived environment env1.

figure(Name="Env1",Visible="on",Units="normalized",OuterPosition=[0 0 1 1])
exampleHelperVisualizeVarSizeEnvironment(env1,kinova,startConfig);

Generate Code for Planning

In order to generate a MEX function, exampleHelperVariableHeterogeneousPlanner_mex, from the MATLAB function exampleHelperVariableHeterogeneousPlanner, run this command:

codegen("exampleHelperVariableHeterogeneousPlanner", ...
    "-args",{startConfig,goalConfig,coder.typeof(geomStruct,[1 inf],[0 1])})

The variable_dims argument of coder.typeof (Fixed-Point Designer) specifies that the input row vector geomStruct of the entry-point planning function can have an unbounded and variable-sized second dimension.

Plan Collision-Free Path

Next, use the planner entry-point helper function to output a geometric collision-free plan.

planInEnv1 = exampleHelperVariableHeterogeneousPlanner(startConfig,goalConfig,env1);

You can also reuse the exampleHelperVariableHeterogeneousPlanner for a different perceived environment. Plan in a new environment, env2, created by removing cylinderGeom and sphereGeom from env1.

env2 = [boxGeom,sphere2Geom];
planInEnv2 = exampleHelperVariableHeterogeneousPlanner(startConfig,goalConfig,env2);

MEX function for planning in env2, and do not need to generate it again. It has the same behavior exampleHelperVariableHeterogeneousPlanner, but results in shorter planning times.

To use the MEX function instead, run this code:

planInEnv1 = exampleHelperVariableHeterogeneousPlanner_mex(startConfig,goalConfig,env1);
env2 = [boxGeom sphere2Geom];
planInEnv2 = exampleHelperVariableHeterogeneousPlanner_mex(startConfig,goalConfig,env2);

Visualize Planned Path

Visualize the planned output in both env1 and env2.

figure(Name="Planned path in env1",Visible="on",Units="normalized",OuterPosition=[0,0,1,1])
exampleHelperVisualizeVarSizeEnvironment(env1,kinova,planInEnv1);

figure(Name="Planned path in env2",Visible="on",Units="normalized",OuterPosition=[0,0,1,1])
exampleHelperVisualizeVarSizeEnvironment(env2,kinova,planInEnv2);

Supporting Functions

Planner Entry Point Function

exampleHelperVariableHeterogeneousPlanner plans motion in the perceived environment of a Kinova Gen 3 robot workspace. The helper function accepts the perceived environment as the geomStructs argument, which is an array of structures where each element contains the information used to represent a collision object. Make the rigid body tree of the robot persistent. This improves the speed of the mexed function because you only need to create the robot once when you use the function multiple times.

The input structure array geomStructs is variable in size and consists of structure elements that each represent the collision geometry defined by the Type field of the structure. The Type field stores an enumeration, exampleHelperCollisionEnum, specifying whether the perceived object is a Box, Sphere, Cylinder or Mesh. For more information on how to construct a collision object structure, see Create Environment.

The second manipulatorRRT input argument can hold up to 100 objects. Upper bounding is necessary to enable construction of collision objects collisionBox, collisionCylinder, collisionSphere, collisionMesh inside a for loop.

function interpolatedPlan = exampleHelperVariableHeterogeneousPlanner(start,goal,geomStructs)
%This function is for internal use only and may be removed in the future.
%exampleHelperVariableHeterogeneousPlanner Plan in a perceived environment of a Kinova Gen 3 robot
%   INTEPROLATEDPLAN=exampleHelperVariableHeterogeneousPlanner(START,GOAL,GEOMSTRUCTS)
%   Outputs a collision free geometric plan, INTEPROLATEDPLAN, of a Kinova
%   Gen3 robot in an environment defined by GEOMSTRUCTS which is a variable
%   sized array of struct elements that capture the information of a
%   collision geometry in the environment. Each struct element in
%   GEOMSTRUCTS is of the form:
%       geomStruct=struct("Type",exampleHelperCollisionEnum.Box,...
%                 "X",0, ...
%                 "Y",0, ...
%                 "Z",0, ...
%                 "Vertices",zeros(3),...
%                 "Radius",0, ...
%                 "Height",0, ...
%                 "Pose",eye(4));
%   START and GOAL are the start and goal joint configurations of the
%   robot, respectively, and are specified as a row vector.

%Copyright 2021-2023 The MathWorks, Inc.
    
    % Create a placeholder variable for environment which is a cell-array
    % of collision meshes with vertices that are variably sized. The size
    % of the environment is that of the input array of collision geometry
    % struct elements.
    coder.varsize("vertices",[inf 3],[1 0]);
    vertices = zeros(3);
    env = repmat({collisionMesh(vertices)},1,length(geomStructs));

    % Load the rigid body tree for which the planner will be defined and make it persistent
    persistent rbt  
    if isempty(rbt)
        rbt = loadrobot("kinovaGen3",DataFormat="row");
    end

    % Set up the environment. The maximum number of collision objects that
    % the environment can hold is 100.
    for i = coder.unroll(1:100)
        if (i <= length(geomStructs))

            % For each struct element, create the corresponding collision
            % object (collisonBox, collisionCylinder, collisionSphere, or
            % collisionMesh) and convert that to its corresponding mesh
            % equivalent thereby homogenizing the environment.
            s = geomStructs(i);
            if s.Type == exampleHelperCollisionEnum.Box
                env{i} = convertToCollisionMesh( ...
                    collisionBox(s.X,s.Y,s.Z));
            elseif s.Type == exampleHelperCollisionEnum.Sphere
                env{i} = convertToCollisionMesh( ...
                    collisionSphere(s.Radius));
            elseif s.Type == exampleHelperCollisionEnum.Cylinder
                env{i} = convertToCollisionMesh( ...
                    collisionCylinder(s.Radius,s.Height));
            elseif s.Type == exampleHelperCollisionEnum.Mesh
                env{i} = collisionMesh(s.Vertices);
            end

            % Assign the pose of the element.
            env{i}.Pose = s.Pose;
        end
    end

    % Create and set up the planner from the rigid body tree and
    % environment.
    planner = manipulatorRRT(rbt,env);
    planner.SkippedSelfCollisions = "parent";
    planner.MaxConnectionDistance = 0.4;
    planner.ValidationDistance = 0.05;

    % For repeatable results, seed the random number generator and store
    % the current seed value.
    prevseed = rng(0);

    % Plan and interpolate.
    planOut = planner.plan(start,goal);
    interpolatedPlan = planner.interpolate(planOut);

    % Restore the random number generator to the previously stored seed.
    rng(prevseed);
end