You are viewing the documentation for OpenSim 3.x. Are you looking for the latest OpenSim 4.0 Documentation?

Part II: Building a Passive Dynamic Walker Model

A. Add Bodies to the Model

In this section, you will add the platform, pelvis, thigh, and shank segments to the model.

Setup

There are several functions in the SimTK library that can help you as you begin to write your code. The default units for OpenSim are Newtons, kilograms, meters, and radians. However, certain functions do expect angles in degrees rather than radians. Some functions that you might find useful for this exercise are:

int main()
{
    try {
		// Code to the construct the model will go here
		// Section: Setup
		// Define key model variables 
		double pelvisWidth = 0.20, thighLength = 0.40, shankLength = 0.435;

		// Create an OpenSim Model
		Model osimModel = Model();
		osimModel.setName("DynamicWalkerModel");

		// Get a reference to the ground object
        OpenSim::Body& ground = osimModel.getGroundBody();

		// 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 Heres
		// ********** BEGIN CODE **********


		// **********  END CODE  **********

		// Save the model to a file
		osimModel.print("DynamicWalkerModel.osim");
 	}
	...
}

One of the advantages of building Models in the API vs. through XML is the ability to store model parameters in variables. Here, we create variables for the width of the pelvis 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 must be called after all of the ModelComponents (bodies, constraints, contact geometry, controllers, etc.) are added to the Model.

Create the Platform Body

In this section, we will create our first Body, the platform. After constructing the Body object, we will add a VisibleObject to it so we can visualize it in the GUI. The base objects (e.g., box and sphere) have lengths of 1 m; scale factors are used to change the dimensions of the objects shown in the OpenSim 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 following functions are used to create the visual representation of the Body in the GUI. Objects are added to the DisplayGeometry element of a Body object. The properties of the displayed object such as the color, scale, and display preference (e.g., solid, wire, shaded) can be accessed by getting a VisibleObject from the Body. The GUI looks for the geometry files (e.g., "box.vtp") in the Geometry folder of the OpenSim installation.

Copy the following block of code into your source file. Place the code after the setGravity function and before the print function, as shown in the block of code in the Setup section.

// Section: Create the Platform
double mass = 1;

// Location of the center of mass from the body origin, expressed in the body frame
Vec3 comLocInBody(0.0, 0.0, 0.0); 

// Inertia of the body expressed in the body frame
Inertia bodyInertia(1.0, 1.0, 1.0, 0.0, 0.0, 0.0);

// Create the body
OpenSim::Body* platform = new OpenSim::Body("Platform", 
                                            mass, comLocInBody, bodyInertia);

// TODO: Construct Joint Here 
// ********** BEGIN CODE **********


// **********  END CODE  **********

// Add and scale model for display in GUI
platform->addDisplayGeometry("box.vtp");
platform->updDisplayer()->setScaleFactors(Vec3(1, 0.05, 1));

// Add the platform to the Model
osimModel.addBody(platform);

// 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.



Adding the Joint for the Platform Body

The figure at left 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 describe the configuration of each body you create, you will need to create an OpenSim::Joint between your new Body and a parent Body. Every model in OpenSim begins with a Ground body. To create a model in OpenSim, you will iteratively create and connect bodies together in a tree topology.

Each body has its own origin point and a body-fixed reference frame. A joint is created by prescribing a set of mobilities that describe the kinematic relationship between a point and reference frame on the parent body and a point and reference frame on the child body.

For example, to create a pin joint, you would specify that between the parent and child frames, there is no translation as a result of the mobilizer (i.e., point P is collocated with point B), and that the orientation of the two bodies changes about a single shared axis as a function of the mobilizer parameter. In the figure to the left, this specification is the mobilizer (shown as a bold, dashed arrow).

Because the location and orientation of the mobilizer can be chosen freely, we also need to specify the transformation from the body's origin and reference frame to the joint's origin and reference frame in each of the two bodies.

In the OpenSim API, these transformations are represented by a series of three-element "Vectors" (SimTK::Vec3). The first SimTK::Vec3 holds the measures of the the translation vector from the Body origin to the Joint origin. The second SimTK::Vec3 holds the angular measures (Euler angles) describing the orientation, assuming an X-Y-Z body-fixed rotation sequence.

 

 

To create a joint, you must specify:

  • Parent body
  • Location of the joint origin in the parent body, as measured from the parent origin and expressed in the parent frame
  • Orientation of the joint frame in the parent body, as measured with respect to the parent frame
  • Child body
  • Location of the joint origin in the child body, as measured from the child origin and expressed in the child frame
  • Orientation of the joint frame in the child body, as measured with respect to the child frame

The following API function is used to create the pin joint:

 

// Section: Create the Platform Joint
// Create the joint connecting the platform to the ground
Vec3 locationInParent(0.0, 0.0, 0.0);
Vec3 orientationInParent(0.0, 0.0, 0.0);
Vec3 locationInChild(0.0, 0.0, 0.0);
Vec3 orientationInChild(0.0, 0.0, 0.0);
PinJoint* platformToGround = new PinJoint("PlatformToGround",
        ground, locationInParent, orientationInParent,
        *platform, locationInChild, orientationInChild);

// TODO: Set the properties of the coordinates in the Pin Joint here  
// ********** BEGIN CODE **********


// **********  END CODE  **********

Note: These functions are assuming that you are using units of kilograms, meters, and seconds.

 

Setting the Properties of the Coordinates in the Pin Joint

The Coordinate objects that define a joint are held in the CoordinateSet of the Joint. You can access the Coordinates inside the CoordinateSet using "get" functions or by indexing into the CoordinateSet like an array. The various OpenSim Joints have different numbers of degrees of freedom; the number of Coordinates in a Joint's CoordinateSet is equal to the number of degrees of freedom of the joint. For example, a PinJoint's CoordinateSet has only one Coordinate.

"get…" and "upd…" methods

Several naming conventions are used in the OpenSim API (e.g., see the table under "Coding Standards / Naming conventions" in the instructions for developers here). Note, in particular, the difference between "get…" and "upd…" methods. Many methods have both "get" and "upd" versions, such as Joint::getParentBody() and Joint::updParentBody(). In this example, the "get" version returns a const reference to the parent OpenSim::Body ("const" for constant) and would be used to read the properties of the parent body (e.g., its name); the "upd" version returns a writable reference to the parent OpenSim::Body and would be used if the parent body's properties were being changed.

 

The following API function can be used on a Joint to get access to its CoordinateSet:

The following API functions are used to set the properties of the coordinates in the joint:

  • OpenSim::CoordinateSet::setName(const std::string& name)
  • OpenSim::CoordinateSet::setRange(double* aRange)
  • OpenSim::CoordinateSet::setDefaultValue(double aDefaultValue_IN_RADIANS)
  • OpenSim::CoordinateSet::setDefaultLocked(bool aLocked)

 

// Section: Set the properties of the coordinates that define the joint
// A pin joint consists of a single coordinate describing a change in 
// orientation about the Z axis
CoordinateSet& platformCoords = platformToGround->upd_CoordinateSet();
platformCoords[0].setName("platform_rz");
double rotRangePlatform[2] = {-Pi/2.0, 0};
platformCoords[0].setRange(rotRangePlatform);
platformCoords[0].setDefaultValue(convertDegreesToRadians(-10.0));
platformCoords[0].setDefaultLocked(true);
 

Compile the code and run the project executable 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 FreeJoint, which is a 6-degree-of-freedom joint. The starter code below sets up the construction of the Pelvis and the display for the GUI. Copy the following block of code into your source file after the code creating the platform and before the print command. In the marked section, set up the coordinates of the joint.

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. The SimTK core 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.

The CoordinateSet for a FreeJoint contains 6 coordinates. The coordinates are ordered X-Y-Z, starting first with rotations and then translations.

Set up the six coordinates of the FreeJoint as follows:

  • Coordinate 0:  Name(pelvis_rx), Range(-pi, pi) rad, DefaultValue(0) rad, DefaultLocked(true)
  • Coordinate 1:  Name(pelvis_ry), Range(-pi, pi) rad, DefaultValue(0) rad, DefaultLocked(true)
  • Coordinate 2:  Name(pelvis_rz), Range(-pi, pi) rad, DefaultValue(0) rad, DefaultLocked(true)
  • Coordinate 3:  Name(pelvis_tx), Range(-10, 10) m, DefaultValue(0) m
  • Coordinate 4:  Name(pelvis_ty), Range(-1, 2) m, DefaultValue(1.0) m
  • Coordinate 5:  Name(pelvis_tz), Range(-1, 1) m, DefaultValue(0) m, DefaultLocked(true)

// Section: Create the Pelvis
mass = 1;

// Location of the center of mass from the body origin expressed in body frame
comLocInBody = Vec3(0.0, 0.0, 0.0);
 
// Inertia of the body expressed in the body frame
bodyInertia = Inertia(1.0, 1.0, 1.0, 0.0, 0.0, 0.0);

// Create the body
OpenSim::Body* pelvis = new OpenSim::Body("Pelvis", mass, comLocInBody, bodyInertia);

// Create the joint which connects the pelvis to the platform
locationInParent    = Vec3(0.0, 0.0, 0.0);
orientationInParent = Vec3(0.0, 0.0, 0.0);
locationInChild     = Vec3(0.0, 0.0, 0.0);
orientationInChild  = Vec3(0.0, 0.0, 0.0);
FreeJoint* pelvisToPlatform = new FreeJoint("PelvisToPlatform",
        *platform, locationInParent, orientationInParent,
        *pelvis, locationInChild, orientationInChild);

// A Free joint has six coordinates, in the following order:
//     rot_x, rot_y, rot_z, trans_x, trans_y, trans_z
// Set the properties of the coordinates that define the joint
CoordinateSet& pelvisJointCoords = pelvisToPlatform->upd_CoordinateSet();

// TODO: Set the coordinate properties 
// ********** BEGIN CODE **********


// **********  END CODE  **********

// Add and scale model for display in GUI
pelvis->addDisplayGeometry("sphere.vtp");
pelvis->updDisplayer()->setScaleFactors(
        Vec3(pelvisWidth/2.0, pelvisWidth/2.0, pelvisWidth));

// Add the pelvis to the Model
osimModel.addBody(pelvis);
 

Compile the code and run the project executable to create your model. Check your model in the OpenSim GUI.

Create the Thigh and Shank Bodies

Assemble the remaining four segments of the model (LeftThigh, LeftShank, RightThigh, and RightShank) with the following properties:

  • Left Thigh: Name(LeftThigh), CoM Location (0,0,0) m, Mass (1 kg), BodyInertia (1,1,1,0,0,0) kg*m^2
  • Right Thigh: Name(RIghtThigh), CoM Location (0,0,0) m, Mass (1 kg), BodyInertia (1,1,1,0,0,0) kg*m^2
  • Left Shank: Name(LeftShank), CoM Location (0,0,0) m, Mass (1 kg), BodyInertia (1,1,1,0,0,0) kg*m^2
  • Right Shank: Name(RightShank), CoM Location (0,0,0) m, Mass (1 kg), BodyInertia (1,1,1,0,0,0) kg*m^2

Connect the thighs to the pelvis and the shanks to the thighs with PinJoints. Set up the joints with the following properties:

  • Left Hip Joint: JointName(LeftThighToPelvis), CoordinateName (LHip_rz), DefaultValue (-10 deg), Range(-100 deg to 100 deg)
  • Right Hip Joint: JointName(RightThighToPelvis), CoordinateName (RHip_rz), DefaultValue (30 deg), Range(-100 deg to 100 deg)
  • Left Knee Joint: JointName(LeftShankToThigh), CoordinateName (LKnee_rz), DefaultValue (-30 deg), Range(-100 deg to 0 deg)
  • Right Knee Joint: JointName(RightShankToThigh), CoordinateName (RKnee_rz), DefaultValue (-30 deg), Range(-100 deg to 0 deg)

Make sure to choose the joints' "locationInParent" and "locationInChild" so that the mass center for the thigh is at the midpoint of the hip and knee joints, and that the mass center for the shank is at the center of the shank. Also, note that, in the default pose, the model should resemble the screenshot on the home page for this example.

For each of the new segments, use the sphere.vtp for the geometry. Set the scaling factors of the displayed geometry to be the segment length along the longest axis, and set the remaining axes to one-tenth of the segment length.

When you are finished, compile, run the file to produce a model, and verify the model in the OpenSim GUI.

B. Add ContactGeometry and Forces to the Model

Forces can be applied between bodies in OpenSim by adding ContactGeometry and Force components to the model. When two contact objects come into contact, an OpenSim Force is responsible for calculating the magnitude and location of the resulting contact force and applying it to the appropriate bodies. For our model, we will model the platform as an infinite half-space for contact and use spheres positioned along the body to avoid penetration of the segments with the platform. For more complex geometries, there is a ContactMesh object that can create a triangular mesh contact surface on a body.

The following API functions can be used to create ContactGeometry objects:

  • OpenSim::ContactHalfSpace( const SimTK::Vec3& location, const SimTK::Vec3& orientation, OpenSim::Body& body, const std::string& name )
  • OpenSim::ContactSphere( double radius, const SimTK::Vec3& location, OpenSim::Body& body, const std::string& name )

Add Contact Geometry to the Platform and the Right Hip

// Section: Add Contact Geometry
// Add contact mesh for platform
// The initial orientation is defined by a normal pointing in the positive x direction
ContactHalfSpace* platformContact = new ContactHalfSpace(Vec3(0.0, 0.0, 0.0),
                                                         Vec3(0.0, 0.0, -Pi/2.0),
                                                         *platform, "PlatformContact");
osimModel.addContactGeometry(platformContact);
 
// Contact Sphere Properties
double contactSphereRadius = 0.05;
 
// Add Contact Sphere for Right Hip
Vec3 rightHipLocationInPelvis(0.0, 0.0, pelvisWidth/2.0);
ContactSphere* rightHipContact = new ContactSphere(contactSphereRadius,
                                                   rightHipLocationInPelvis,
                                                   *pelvis, "RHipContact");
osimModel.addContactGeometry(rightHipContact);
 

Add ContactSpheres to the Left Hip, Knees, and Feet

Finish the previous block of code by adding a contact sphere for the left hip, left and right knees, and left and right feet. Assign the knee contact spheres to the shank bodies and the hip contact spheres to the pelvis.

Simply adding ContactGeometry to a model is not sufficient to generate contact forces between bodies; we must also add a Force object that describes the forces that are generated when two ContactGeometry objects collide. If you are loading and visualizing your model as you go, you will not be able to see the ContactSpheres in the OpenSim GUI until a Force is created which uses them. Once the HuntCrossleyForce objects are created in the following section, you will be able to see your contact objects in the GUI.

 

Add ContactForces to the Model

The following API functions can be used to create HuntCrossleyForce objects:

// Section: Add HuntCrossleyForces
// Define contact parameters for all the spheres
double stiffness = 1E7, dissipation = 0.1;
double staticFriction = 0.6, dynamicFriction = 0.4, viscosity = 0.01;
 
// Right Hip Contact Parameters
OpenSim::HuntCrossleyForce::ContactParameters* rightHipContactParams = 
        new OpenSim::HuntCrossleyForce::ContactParameters(stiffness,
                                                          dissipation,
                                                          staticFriction,
                                                          dynamicFriction,
                                                          viscosity);
rightHipContactParams->addGeometry("RHipContact");
rightHipContactParams->addGeometry("PlatformContact");
 
// Right Hip Force
OpenSim::HuntCrossleyForce* rightHipForce = 
        new OpenSim::HuntCrossleyForce(rightHipContactParams);
rightHipForce->setName("RightHipForce");

// Add Force
osimModel.addForce(rightHipForce);
A common bug is misspelling the name of the contact element in the addGeometry call. The IDE has no idea if you have named the correct element, so the program will compile and run but the resulting model will generate an error when it is loaded into OpenSim.

Add HuntCrossleyForces Between the Remaining ContactSpheres and the Platform

For the remaining five spheres, create a new set of HuntCrossleyForce::ContactParameters and HuntCrossleyForce objects (using the same parameter values as above) and add them to the model.

Compile the code and run the project executable to create your model. Check your model in the OpenSim GUI.

C. Add Coordinate Limiting Forces

In order to enforce the coordinate limits set in the joint definitions, we will create a CoordinateLimitForce for the hip and knee joints that will apply a force similar to a spring and damper to enforce the joint limits.

Add a CoordinateLimitForce for the Hip and Knee Coordinates

Search for CoordinateLimitForce in the OpenSim API documentation and find the method that allows for the parameters to be set conveniently in the constructor. For each of the joints, set the force stiffness to 1e6, the force damping to 1e5, and the transition to 5 degrees. Note: For this OpenSim class, the min, max, and transition values must be expressed in degrees.

D. Simulate Your Model

We have finished writing the code to build the model. Compile your code and print your model. Load the model in the OpenSim GUI and run a forward simulation from 0 to 1 seconds. Make sure your model is in the default pose before running the forward simulation. To ensure you built the model correctly, compare your motion to that from the motion file 'DynamicWalkerForward.mot' (located in the downloaded ZIP file), using the plotter.

The DynamicWalkerForward.mot motion is not very interesting, though. Let's have some fun! Here are some settings to try before simulating:

  • Turn the model into two double pendulums by locking pelvis_tx and pelvis_ty.
  • Make the model do a flip by locking the hip and knee joints and setting the initial speed of the pelvis_ty coordinate to a large negative number.

 

OpenSim is supported by the Mobilize Center , an NIH Biomedical Technology Resource Center (grant P41 EB027060); the Restore Center , an NIH-funded Medical Rehabilitation Research Resource Network Center (grant P2C HD101913); and the Wu Tsai Human Performance Alliance through the Joe and Clara Tsai Foundation. See the People page for a list of the many people who have contributed to the OpenSim project over the years. ©2010-2024 OpenSim. All rights reserved.