How to Configure Joint Limits and Motor Constraints in DART
To configure joint limits and motor constraints in DART, set limits on each DegreeOfFreedom using setPositionLimits() and setVelocityLimits(), assign an actuator type (SERVO, FORCE, or MIMIC) to the Joint, and add the corresponding constraint object (JointLimitConstraint, ServoMotorConstraint, or MimicMotorConstraint) to the World.
This guide covers the complete workflow for configuring joint limits and motor constraints in DART (Dynamic Animation and Robotics Toolkit). The dartsim/dart repository uses a two-level architecture where Degrees of Freedom (DoFs) store raw limits while Joints manage actuator behavior, requiring specific constraint objects to bridge the gap between configuration and physics simulation.
Understanding the DART Joint Architecture
In DART, a joint is a collection of one or more degrees of freedom (DoFs). Hard limits—position, velocity, acceleration, and force—are stored on each individual DegreeOfFreedom object, while motor-type behavior (servo, force, velocity, mimic) is stored on the joint itself as an actuator type. The physics engine enforces limits through the JointLimitConstraint class and executes motor commands through ServoMotorConstraint (for position/velocity servo) or MimicMotorConstraint (for coupled joints).
Step-by-Step Configuration Workflow
Follow this sequence to properly configure limits and motors:
- Obtain the joint via
Skeleton::getJoint()or through aBodyNodepointer. - Set DoF limits using the APIs in
dart::dynamics::DegreeOfFreedom(defined indart/dynamics/degree_of_freedom.hpp). - Choose an actuator type on the joint with
Joint::setActuatorType()(defined indart/dynamics/joint.hpp). - Create the appropriate constraint (
JointLimitConstraint,ServoMotorConstraint, orMimicMotorConstraint) and add it to the world or aConstrainedGroup. - Step the simulation—the constraint solver automatically applies limits and motor commands each timestep.
Setting Joint Limits on Degrees of Freedom
Individual DoFs store their own limits. Access each DoF via joint->getDof(index) and configure bounds before adding constraints.
auto* joint = skel->getJoint("elbow");
// Configure limits on each DoF
for (std::size_t i = 0; i < joint->getNumDofs(); ++i) {
// Position limits: +/- 90 degrees in radians
joint->getDof(i)->setPositionLimits(-M_PI_2, M_PI_2);
// Velocity limits: +/- 5 rad/s
joint->getDof(i)->setVelocityLimits(-5.0, 5.0);
}
// Add constraint to enforce these limits
auto limitConstraint = std::make_shared<dart::constraint::JointLimitConstraint>(joint);
world->addConstraint(limitConstraint);
The JointLimitConstraint (defined in dart/constraint/joint_limit_constraint.hpp) reads these limits each timestep and applies corrective impulses to prevent violations.
Implementing Motor Constraints
Motor behavior depends on the joint's actuator type and the specific constraint class added to the world.
Position and Velocity Control with ServoMotorConstraint
For position or velocity servo control, set the actuator type to Joint::SERVO and add a ServoMotorConstraint (defined in dart/constraint/servo_motor_constraint.hpp):
// Set actuator type
joint->setActuatorType(dart::dynamics::Joint::SERVO);
// Create servo constraint
auto servo = std::make_shared<dart::constraint::ServoMotorConstraint>(joint);
world->addConstraint(servo);
// Command the desired position (in radians)
joint->getDof(0)->setCommand(desiredAngle);
The constraint generates forces to drive the joint toward the command value each simulation step.
Direct Force Control
For direct force or torque control without a servo loop, use Joint::FORCE. This mode applies generalized forces directly to the DoFs and does not require a motor constraint object, though you must still add a JointLimitConstraint if you want to enforce position/velocity limits.
Mimic Joints with MimicMotorConstraint
To make one joint copy another's motion (useful for coupled joints like gripper fingers), use Joint::MIMIC and MimicMotorConstraint (defined in dart/constraint/mimic_motor_constraint.hpp):
auto* leader = skel->getJoint("shoulder");
auto* follower = skel->getJoint("elbow");
// Configure actuator types
leader->setActuatorType(dart::dynamics::Joint::FORCE);
follower->setActuatorType(dart::dynamics::Joint::MIMIC);
// follower = leader * multiplier + offset
follower->setMimicJoint(leader, 1.0, 0.0);
// Add mimic constraint for the follower
auto mimic = std::make_shared<dart::constraint::MimicMotorConstraint>(follower);
world->addConstraint(mimic);
The MimicMotorConstraint reads the leader joint's generalized forces and applies a scaled copy to the follower.
Tuning Global Constraint Parameters
All constraint instances share global error reduction parameter (ERP) and constraint force mixing (CFM) settings that affect stability and responsiveness. Tune these static setters before creating constraints:
// Joint limit tuning: fast error correction with slight softness
dart::constraint::JointLimitConstraint::setErrorReductionParameter(0.02);
dart::constraint::JointLimitConstraint::setConstraintForceMixing(1e-6);
// Servo motor tuning
dart::constraint::ServoMotorConstraint::setConstraintForceMixing(1e-5);
These parameters affect every instance of the respective constraint type in the simulation.
Summary
- Limits live on DoFs: Use
DegreeOfFreedom::setPositionLimits()andsetVelocityLimits()(fromdart/dynamics/degree_of_freedom.hpp) to define bounds. - Actuator types live on Joints: Use
Joint::setActuatorType()(fromdart/dynamics/joint.hpp) to selectSERVO,FORCE, orMIMICbehavior. - Constraints enforce behavior: You must instantiate
JointLimitConstraint,ServoMotorConstraint, orMimicMotorConstraintand add them to theWorldfor the limits and motors to take effect. - Global tuning: Adjust ERP and CFM via static setters on constraint classes to control solver behavior across all instances.
Frequently Asked Questions
Where are joint limits physically stored in DART?
Joint limits are stored on individual DegreeOfFreedom objects, not on the Joint itself. Access them via joint->getDof(index) and call setPositionLimits() or setVelocityLimits(). The JointLimitConstraint reads these stored values each frame to calculate constraint forces.
What is the difference between Joint::SERVO and Joint::FORCE actuator types?
Joint::SERVO enables closed-loop position or velocity control through the ServoMotorConstraint, which generates forces to reach the target set via setCommand(). Joint::FORCE applies direct generalized forces to the DoFs without a feedback loop, giving you raw torque control but requiring you to handle stabilization manually.
How do I make one joint automatically follow another joint's movement?
Set the follower joint's actuator type to Joint::MIMIC using setActuatorType(), define the relationship with setMimicJoint(leader, multiplier, offset), and add a MimicMotorConstraint to the world. This constraint copies forces from the leader joint to the follower based on the scaling factor you specify.
Why are my joint limits not being enforced during simulation?
You likely forgot to add a JointLimitConstraint to the world. Setting limits on the DegreeOfFreedom only stores the boundary values; the physics engine requires an active constraint object to apply corrective impulses. Create the constraint with std::make_shared<dart::constraint::JointLimitConstraint>(joint) and call world->addConstraint() to enable enforcement.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →