Building a Dynamic Walker in Matlab
Note: This example is compatible with OpenSim version 4.1
Objectives
Model development, reduction, and validation are key components in a meaningful simulation study. In this exercise, you will build a simple model for use in a passive dynamic simulation of walking. For the past few decades, passive dynamic systems have been used to study the fundamental principles of locomotion. By studying passive dynamic systems, researchers aim to understand how the geometric and inertial properties of a system influence the occurrence and stability of low-energy gaits. Proper selection of passive system dynamics can reduce the demand on a control system and allow locomotion to be achieved by modulating a small set of signals. The knowledge gained by studying passive dynamic walking can help improve the designs of autonomous legged robots and rehabilitation robots for locomotion.
In this exercise, you will create an un-actuated, four-link walker model using the OpenSim API through Matlab. This simple model could be useful for exploring control and optimization strategies in the OpenSim API. The objectives of this exercise are to learn how to instantiate and connect basic components of an OpenSim model (bodies and joints) and to add more complicated components like Forces and Contact Geometry.
Below are some useful (and necessary) resources that you should read before you begin and keep handy while you build your model;
Read the Introduction to the OpenSim API section
Setup your Matlab environment using instructions from the Scripting with Matlab page.
Bookmark the Common Scripting Commands page for helpful tips and code snippets
Read the Guide to Using Doxygen pages and bookmark the OpenSim Doxygen documentation
Bookmark the API Guide on Doxygen.
Setup a working folder where you will save and test your model.
Model Description
The model you will create in this exercise consists of a rigid platform and a four-link walker. The angle of the platform is used to control the amount of potential energy converted to kinetic energy with each stride. To maintain a stable motion, energy must be added into the system to overcome losses from ground contact. The pelvis of the walker is connected to the platform by a planar joint, which allows three degrees of motion between the pelvis and the platform. Pin joints are used to connect the thighs to the pelvis and the shanks to the thighs. Contact Spheres and a Contact Half Space are attached—using a Hunt–Crossley contact model—to generate reaction forces.
Setup
The script for this exercise is included in the OpenSim 4.1 distribution and can be found in your OpenSim resources directory; <Resources-Dir>/Code/Matlab/Dynamic_Walker_Builder/.
You will complete the empty code blocks in the sections that follow, run the script, and inspect the generated OpenSim model. The default units for OpenSim are Newtons, kilograms, meters, and radians. However, certain functions do expect angles in degrees rather than radians, so you should view each class in the Doxygen Documentation to check the input type.
You will be opening and viewing the model in the OpenSim GUI. In the GUI, it will be useful to select to not see a floor so that you will be able to see the entire model motion.
%% Import OpenSim Libraries into Matlab
import org.opensim.modeling.*
%% Define key model variables
pelvisWidth = 0.20;
thighLength = 0.40;
shankLength = 0.435;
%% Instantiate an (empty) OpenSim Model
osimModel = Model();
osimModel.setName('DynamicWalkerModel');
% Get a reference to the ground object
ground = osimModel.getGround();
% Define the acceleration of gravity
osimModel.setGravity(Vec3(0, -9.80665, 0));
% TODO: Construct Bodies and Joints Here
% ********** BEGIN CODE **********
% ********** END CODE **********
% TODO: Construct ContactGeometry and HuntCrossleyForces Here
% ********** BEGIN CODE **********
% ********** END CODE **********
% TODO: Construct CoordinateLimitForces Here
% ********** BEGIN CODE **********
% ********** END CODE **********
%% Initialize the System (checks model consistency).
osimModel.initSystem();
% Save the model to a file
osimModel.print('DynamicWalkerModel.osim');
display(['DynamicWalkerModel.osim printed!']);
One of the advantages of building Models with API functions vs with XML is the ability to store model parameters in variables. Here, we create variables for the pelvis width and the lengths of the thighs and shanks. These three variables are used throughout the exercise, such as when defining the joints, the display geometry, and the contact elements.
A Model object called "osimModel" is created and the name of the model is set to "DynamicWalkerModel." In the final model file, this has the effect of creating a Model XML element and setting the "name" attribute to DynamicWalkerModel (i.e., <Model name="DynamicWalkerModel"> ... </Model>).
The setGravity function sets the direction and magnitude of the acceleration due to gravity using a SimTK::Vec3, and adds the XML element <gravity> 0 -9.80665 0 </gravity> to the model.
The print command creates the resulting OpenSim model file. The print command is called after all of the Model Components (bodies, constraints, contact geometry, controllers, etc.) are added to the Model.
Add Bodies to the Model
In this section, you will add the platform, pelvis, thigh, and shank segments to the model.
Create the Platform Body
We will create our first Body, the platform. After initializing the Body object, we will attach visual Geometry to it so we can visualize it in the GUI.
To create a body, you must specify the following:
Mass
Location of the center of mass from the body's origin, expressed in the body frame
The moments and products of inertia of the body about its center of mass, expressed in the body frame
The following API functions are used to create the body:
The Geometry class allows you to specify the location (Frame), color, opacity, etc. of visual geometry displayed in the GUI and in the OpenSim API Visualizer. Visual Geometry can be added by using Geometry files, such as .vtp files, and using analytical geometry types for common shapes (Brick, Sphere, Ellipsoid, and Mesh).
In the following code block, we add a Platform body to the model and define a Brick Geometry for display. Copy the code into your source file, placed after the setGravity function and before the print function, as shown in the block of code above.
% TODO: Construct Bodies and Joints Here
% ********** BEGIN CODE **********
% Make and add a Platform Body
platform = Body();
platform.setName('Platform');
platform.setMass(1);
platform.setInertia( Inertia(1,1,1,0,0,0) );
% Add geometry to the body
platformGeometry = Brick( Vec3(10,0.05,1) );
platformGeometry.setColor( Vec3(0.8, 0.1, 0.1) );
platform.attachGeometry(platformGeometry);
% Add Body to the Model
osimModel.addBody(platform);
% TODO: Construct Joint Here
% ********** BEGIN CODE **********
% ********** END CODE **********
% TODO: Construct the next body and joint
% ********** BEGIN CODE **********
% ********** END CODE **********
Note: These functions are assuming that you are using units of kilograms, meters, and seconds.
The constructor for Brick takes a Vec3 that specifies the half-lengths of the brick in the x, y, and z directions. By specifying a small half-length in the Y-direction, this brick will look like a flat platform.
Adding the Joint for the Platform Body
The figure below is Figure 2 is from Seth, A., Sherman, M., Eastman, P., Delp, S. Minimal formulation of joint motion for biomechanisms. Nonlinear Dyn 62:291–303.
To connect your model's bodies together to form a multibody graph, you will need joints. Every model in OpenSim begins with a Ground body. A joint is a set of degrees of freedom that describes the kinematic relationship between two bodies, termed the parent and child bodies.
To allow flexibility in how joints are defined, OpenSim 4.0 introduced the Frame class hierarchy. There are three main types of Frames: Ground (each model starts with one), Body, and PhysicalOffsetFrame. All three of these frames are called PhysicalFrames because they either are a rigid body or are fixed to a rigid body. In OpenSim 4.x, a joint connects two PhysicalFrames (parent and child).
As an example, OpenSim's pin joint has one degree of freedom between its parent and child frames that allows rotation about the mutual Z-axis of the parent and child frames while restricting all other motion, including translation.
The OpenSim API provides two commonly-used constructors for Joints:
Provide the parent and child bodies along with offsets to the parent and child frames for the joint:
Provide the parent and child frames directly (new in 4.0):
When providing the parent and child bodies, as seen at the link above the signature above, the transformation for each of the offsets is represented by a pair of three-element "vectors" Vec3's. The first Vec3 holds the measures of the translation vector from the body origin to the joint frame origin. The second Vec3 holds the Euler angles describing the orientation of the joint frame with respect to body frame, using an X-Y-Z body-fixed rotation sequence. (Note: parent and child do not need to be bodies; they can be a PhysicalOffsetFrame or Ground.)
% Section: Create the Platform Joint
% Make and add a Pin joint for the Platform Body
locationInParent = Vec3(0,0,0);
orientationInParent = Vec3(0,0,0);
locationInChild = Vec3(0,0,0);
orientationInChild = Vec3(0,0,0);
platformToGround = PinJoint('PlatformToGround',... % Joint Name
ground,... % Parent Frame
locationInParent,... % Translation in Parent Frame
orientationInParent,...% Orientation in Parent Frame
platform,... % Child Frame
locationInChild,... % Translation in Child Frame
orientationInChild); % Orientation in Child Frame
% TODO: Set the coordinate properties of the Pin Joint
% Add the PlatformToGround Joint to the Model
osimModel.addJoint(platformToGround);
% TODO: Construct the next body and joint
% ********** BEGIN CODE **********
% ********** END CODE **********
Note: These functions are assuming that you are using units of kilograms, meters, and seconds.
Check your model in the OpenSim GUI.
Setting the Properties of the Coordinates in the Pin Joint
The Coordinate objects that define a joint are held in the Joint. Coordinates are ordered and can be accessed using an integer index value. Use the "get" variants if you do not want to edit the returned coordinate, and use the "upd" variants otherwise (e.g., if you want to set the name of a coordinate). The various OpenSim Joints have different numbers of degrees of freedom; the number of Coordinates in a Joint is equal to the number of degrees of freedom of the joint. For example, a PinJoint has only one Coordinate (Rotation about Z-axis).
Execute the code to create your model; it should be located in your build directory. Check your model in the OpenSim GUI.
Create the Pelvis Body
In this section, you will create the Pelvis body and connect the platform to the pelvis via a PlanarJoint, which is a 3-degree-of-freedom joint that enables rotation about the Z-axis and translation in the X-Y plane. The starter code below sets up the construction of the Pelvis and Joint between the Pelvis and Platform. Copy the following block of code into your source file directly after the code creating the platform creating the platform and before the print command.
A modeling choice was made here to connect the pelvis to the platform so that pelvis height in relation to the contact surface does not change when the platform rotates. OpenSim can handle any tree topology for the bodies in the system. For example, an alternative model could be created where both the pelvis and platform are connected to Ground.
Execute the code to create your model. Check your model in the OpenSim GUI.
Create the Thigh and Shank Bodies
Assemble the remaining six segments (right and left thigh, shank, and foot segments) and joints (right and left hip, knee, and ankle joints) with the properties found below.
Make sure to set the joints' "locationInParent" and "locationInChild" so that the mass center for the thigh is at the midpoint between the hip and knee joints, and the mass center for the shank is between the center of the shank. Also, consider that, in the default pose, the model should resemble the screenshot on the home page for this example.
For each of the new segments, attach an Ellipsoid Geometry with dimensions such that the length along the long axis is the segment length, and the length of the other two axes is one-tenth the segment length.
When you are finished, execute the code, produce a model, and verify the model in the OpenSim GUI.
Desired Properties for the Remaining Bodies:
Right Thigh: Name: RightThigh, CoM Location: (0,0,0) m, Mass: 1 kg, BodyInertia: (2,2,0.02,0,0,0) kg-m2
Right Thigh Geometry: Ellipsoid where X & Z radii = thighLength/10, Y radii = thighLength/2
Right Hip Joint: JointName: RightThighToPelvis, CoordinateName: RHip_rz, DefaultValue: 30 deg, Range: -100 deg to 100 deg
Left Thigh: Name: LeftThigh, CoM Location: (0,0,0) m, Mass: 1 kg, BodyInertia: (2,2,0.02,0,0,0) kg-m2
Left Thigh Geometry: Ellipsoid where X & Z radii = thighLength/10, Y radii = thighLength/2
Left Hip Joint: JointName: LeftThighToPelvis, CoordinateName: LHip_rz, DefaultValue: -10 deg, Range: -100 deg to 100 deg
Right Shank: Name: RightShank, CoM Location: (0,0,0) m, Mass: 0.1 kg, BodyInertia: (2,2,0.002,0,0,0) kg-m2
Right Shank Geometry: Ellipsoid where X & Z radii = shankLength/10, Y radii = shankLength/2
Right Knee Joint: JointName: RightShankToThigh, CoordinateName: RKnee_rz, DefaultValue: -30 deg, Range: -100 deg to 0 deg
Left Shank: Name: LeftShank, CoM Location: (0,0,0) m, Mass: 0.1 kg, BodyInertia: (2,2,0.002,0,0,0) kg-m2
Left Shank Geometry: Ellipsoid where X & Z radii = shankLength/10, Y radii = shankLength/2
Left Knee Joint: JointName: LeftShankToThigh, CoordinateName: LKnee_rz, DefaultValue: -30 deg, Range: -100 deg to 0 deg