Skip to content

Commit 590c611

Browse files
authored
Allow floating bodies to use RpyFloating joints. (#21973)
Also supports using Weld joints instead of floating joints for unattached bodies.
1 parent 2702a84 commit 590c611

24 files changed

+1238
-216
lines changed

multibody/plant/multibody_plant.cc

+44-1
Original file line numberDiff line numberDiff line change
@@ -1187,7 +1187,7 @@ void MultibodyPlant<T>::SetFreeBodyPoseInAnchoredFrame(
11871187
}
11881188

11891189
// Pose of frame F in its parent body frame P.
1190-
const RigidTransform<T> X_PF = frame_F.GetFixedPoseInBodyFrame();
1190+
const RigidTransform<T>& X_PF = frame_F.EvalPoseInBodyFrame(*context);
11911191
// Pose of frame F's parent body P in the world.
11921192
const RigidTransform<T>& X_WP = EvalBodyPoseInWorld(*context, frame_F.body());
11931193
// Pose of "body" B in the world frame.
@@ -1278,6 +1278,49 @@ void MultibodyPlant<T>::RenameModelInstance(ModelInstanceIndex model_instance,
12781278
}
12791279
}
12801280

1281+
template <typename T>
1282+
void MultibodyPlant<T>::SetBaseBodyJointType(
1283+
BaseBodyJointType joint_type,
1284+
std::optional<ModelInstanceIndex> model_instance) {
1285+
std::optional<internal::ForestBuildingOptions> options;
1286+
switch (joint_type) {
1287+
case BaseBodyJointType::kQuaternionFloatingJoint:
1288+
options = internal::ForestBuildingOptions::kDefault;
1289+
break;
1290+
case BaseBodyJointType::kRpyFloatingJoint:
1291+
options = internal::ForestBuildingOptions::kUseRpyFloatingJoints;
1292+
break;
1293+
case BaseBodyJointType::kWeldJoint:
1294+
options = internal::ForestBuildingOptions::kUseFixedBase;
1295+
break;
1296+
}
1297+
DRAKE_DEMAND(options.has_value());
1298+
DRAKE_THROW_UNLESS(!is_finalized());
1299+
internal::LinkJointGraph& graph = mutable_tree().mutable_graph();
1300+
if (model_instance.has_value()) {
1301+
graph.SetForestBuildingOptions(*model_instance, *options);
1302+
} else {
1303+
graph.SetGlobalForestBuildingOptions(*options);
1304+
}
1305+
}
1306+
1307+
template <typename T>
1308+
BaseBodyJointType MultibodyPlant<T>::GetBaseBodyJointType(
1309+
std::optional<ModelInstanceIndex> model_instance) const {
1310+
const internal::LinkJointGraph& graph = internal_tree().graph();
1311+
const internal::ForestBuildingOptions options =
1312+
model_instance.has_value()
1313+
? graph.get_forest_building_options_in_use(*model_instance)
1314+
: graph.get_global_forest_building_options();
1315+
if (static_cast<bool>(options &
1316+
internal::ForestBuildingOptions::kUseRpyFloatingJoints))
1317+
return BaseBodyJointType::kRpyFloatingJoint;
1318+
if (static_cast<bool>(options &
1319+
internal::ForestBuildingOptions::kUseFixedBase))
1320+
return BaseBodyJointType::kWeldJoint;
1321+
return BaseBodyJointType::kQuaternionFloatingJoint;
1322+
}
1323+
12811324
template <typename T>
12821325
void MultibodyPlant<T>::Finalize() {
12831326
// After finalizing the base class, tree is read-only.

multibody/plant/multibody_plant.h

+128-22
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ enum class DiscreteContactApproximation {
228228
kLagged,
229229
};
230230

231+
/// The kind of joint to be used to connect base bodies to world at Finalize().
232+
/// See @ref mbp_working_with_free_bodies "Working with free bodies"
233+
/// for definitions and discussion.
234+
/// @see SetBaseBodyJointType() for details.
235+
enum class BaseBodyJointType {
236+
kQuaternionFloatingJoint, ///< 6 dofs, unrestricted orientation.
237+
kRpyFloatingJoint, ///< 6 dofs using 3 angles; has singularity.
238+
kWeldJoint, ///< 0 dofs, fixed to World.
239+
};
240+
231241
/// @cond
232242
// Helper macro to throw an exception within methods that should not be called
233243
// post-finalize.
@@ -1667,6 +1677,53 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
16671677
void RenameModelInstance(ModelInstanceIndex model_instance,
16681678
const std::string& name);
16691679

1680+
/// Sets the type of joint to be used by Finalize() to connect any otherwise
1681+
/// unconnected bodies to World. Bodies connected by a joint to World are
1682+
/// called _base bodies_ and are determined during Finalize() when we build
1683+
/// a forest structure to model the multibody system for efficient
1684+
/// computation.
1685+
/// See @ref mbp_working_with_free_bodies "Working with free bodies"
1686+
/// for a longer discussion.
1687+
///
1688+
/// This can be set globally or for a particular model instance. Global
1689+
/// options are used for any model elements that belong to model instances for
1690+
/// which no options have been set explicitly. The default is to use a
1691+
/// quaternion floating joint.
1692+
///
1693+
/// | BaseBodyJointType:: | Notes |
1694+
/// | ------------------------ | -------------------------------------- |
1695+
/// | kQuaternionFloatingJoint | 6 dofs, unrestricted orientation |
1696+
/// | kRpyFloatingJoint † | 6 dofs, uses 3 angles for orientation |
1697+
/// | kWeldJoint | 0 dofs, welded to World ("anchored") |
1698+
///
1699+
/// † The 3-angle orientation representation used by RpyFloatingJoint can be
1700+
/// easier to work with than a quaternion (especially for optimization) but
1701+
/// has a singular orientation which must be avoided (pitch angle near 90°).
1702+
///
1703+
/// @note Reminder: if you aren't satisfied with the particular selection of
1704+
/// joints here, you can always add an explicit joint to World with
1705+
/// AddJoint().
1706+
///
1707+
/// @param[in] joint_type The joint type to be used for base bodies.
1708+
/// @param[in] model_instance (optional) the index of the model instance to
1709+
/// which `joint_type` is to be applied.
1710+
/// @throws std::exception if called after Finalize().
1711+
void SetBaseBodyJointType(
1712+
BaseBodyJointType joint_type,
1713+
std::optional<ModelInstanceIndex> model_instance = {});
1714+
1715+
/// Returns the currently-set choice for base body joint type, either for
1716+
/// the global setting or for a specific model instance if provided.
1717+
/// If a model instance is provided for which no explicit choice has been
1718+
/// made, the global setting is returned. Any model instance index is
1719+
/// acceptable here; if not recognized then the global setting is returned.
1720+
/// This can be called any time -- pre-finalize it returns the joint type
1721+
/// that will be used by Finalize(); post-finalize it returns the joint type
1722+
/// that _was_ used if there were any base bodies in need of a joint.
1723+
/// @see SetBaseBodyJointType()
1724+
BaseBodyJointType GetBaseBodyJointType(
1725+
std::optional<ModelInstanceIndex> model_instance = {}) const;
1726+
16701727
/// This method must be called after all elements in the model (joints,
16711728
/// bodies, force elements, constraints, etc.) are added and before any
16721729
/// computations are performed.
@@ -3101,22 +3158,26 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
31013158
/// A %MultibodyPlant user adds sets of RigidBody and Joint objects to `this`
31023159
/// plant to build a physical representation of a mechanical model.
31033160
/// At Finalize(), %MultibodyPlant builds a mathematical representation of
3104-
/// such system, consisting of a tree representation. In this
3105-
/// representation each body is assigned a Mobilizer, which grants a certain
3161+
/// such system as a forest of trees, with each tree's root (which we call
3162+
/// that tree's _base body_) connected directly to World by a joint defining
3163+
/// the base body's mobility. If no input joint is provided for a base body,
3164+
/// one is added automatically. The function SetBaseBodyJointType() can be
3165+
/// used to change the type of joint used.
3166+
///
3167+
/// In this representation each input Joint (including those added for base
3168+
/// bodies) is modeled internally using a Mobilizer, which grants a certain
31063169
/// number of degrees of freedom in accordance to the physical specification.
3107-
/// In this regard, the modeling representation can be seen as a forest of
3108-
/// tree structures each of which contains a single body at the root of the
3109-
/// tree. The root body's parent is always the world body. If a body has _six_
3110-
/// degrees of freedom with respect to its parent, it is called a "free body".
3111-
/// If it also the root of a tree, such that its parent is the world body,
3112-
/// it is a "floating base" body. Free bodies that are added to the plant
3113-
/// without specifying a joint become floating base bodies after
3114-
/// finalization. It is possible (and sometimes recommended) to explicitly
3115-
/// create a 6-dof joint between two bodies. The child body would be free,
3116-
/// because it has six degrees of freedom, but it would _not_ be a "floating
3117-
/// base body" because its parent is not the world. The effects of the various
3118-
/// APIs below depend on the distinction between "free" and "floating base
3119-
/// bodies". Read carefully.
3170+
/// If a body has _six_ degrees of freedom with respect to its parent, it is
3171+
/// called a _free body_. If it also the root of a tree, such that its parent
3172+
/// is the world body, it is a _floating base body_. Bodies that are added to
3173+
/// the plant without specifying a joint normally become floating base bodies
3174+
/// after finalization.
3175+
///
3176+
/// It is possible (and sometimes recommended) to explicitly create a 6-dof
3177+
/// joint between two bodies. The child body would be free, because it has six
3178+
/// degrees of freedom, but it would _not_ be a floating base body because its
3179+
/// parent is not the world. The effects of the various APIs below depend on
3180+
/// the distinction between "free" and "floating base bodies". Read carefully.
31203181
/// A user can request the set of all floating base bodies with a call to
31213182
/// GetFloatingBaseBodies(). Alternatively, a user can query whether a
31223183
/// RigidBody is a floating base body or not with RigidBody::is_floating().
@@ -3128,8 +3189,8 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
31283189
/// _not_ 6-dof free bodies generally.
31293190
///
31303191
/// It is sometimes convenient for users to perform operations on RigidBodies
3131-
/// uniformly through the APIs of the Joint class. For that reason the
3132-
/// plant implicitly constructs a 6-dof joint, QuaternionFloatingJoint,
3192+
/// uniformly through the APIs of the Joint class. For that reason the plant
3193+
/// implicitly constructs a 6-dof joint, typically a QuaternionFloatingJoint,
31333194
/// between the body and the world for all bodies otherwise without declared
31343195
/// inboard joints at the time of Finalize(). Using Joint APIs to affect a
31353196
/// free body (setting state, changing parameters, etc.) has the same effect
@@ -3262,11 +3323,24 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
32623323
}
32633324

32643325
/// Sets the distribution used by SetRandomState() to populate the free
3265-
/// body's `rotation` with respect to its parent frame.
3326+
/// body's orientation with respect to its parent frame, expressed as a
3327+
/// quaternion. Requires that the free body is modeled using a
3328+
/// QuaternionFloatingJoint.
32663329
/// @note The parent frame is not necessarily the world frame. See
3267-
/// @ref mbp_working_with_free_bodies "above for details".
3330+
/// @ref mbp_working_with_free_bodies "above for details".
3331+
/// @note This distribution is not necessarily uniform over the sphere
3332+
/// reachable by the quaternion; that depends on the quaternion expression
3333+
/// provided in `rotation`. See
3334+
/// SetFreeBodyRandomRotationDistributionToUniform() for a uniform
3335+
/// alternative.
32683336
/// @throws std::exception if `body` is not a free body in the model.
3337+
/// @throws std::exception if the free body is not modeled with a
3338+
/// QuaternionFloatingJoint.
32693339
/// @throws std::exception if called pre-finalize.
3340+
/// @see SetFreeBodyRandomAnglesDistribution() for a free body that is
3341+
/// modeled using an RpyFloatingJoint.
3342+
/// @see SetBaseBodyJointType() for control over the type of automatically-
3343+
/// added joints.
32703344
void SetFreeBodyRandomRotationDistribution(
32713345
const RigidBody<T>& body,
32723346
const Eigen::Quaternion<symbolic::Expression>& rotation) {
@@ -3275,14 +3349,46 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
32753349
}
32763350

32773351
/// Sets the distribution used by SetRandomState() to populate the free
3278-
/// body's rotation with respect to its parent frame using uniformly random
3279-
/// rotations.
3352+
/// body's orientation with respect to its parent frame using uniformly random
3353+
/// rotations (expressed as a quaternion). Requires that the free body is
3354+
/// modeled using a QuaternionFloatingJoint.
32803355
/// @note The parent frame is not necessarily the world frame. See
3281-
/// @ref mbp_working_with_free_bodies "above for details".
3356+
/// @ref mbp_working_with_free_bodies "above for details".
32823357
/// @throws std::exception if `body` is not a free body in the model.
3358+
/// @throws std::exception if the free body is not modeled with a
3359+
/// QuaternionFloatingJoint.
32833360
/// @throws std::exception if called pre-finalize.
3361+
/// @see SetFreeBodyRandomAnglesDistribution() for a free body that is
3362+
/// modeled using an RpyFloatingJoint.
3363+
/// @see SetBaseBodyJointType() for control over the type of automatically-
3364+
/// added joints.
32843365
void SetFreeBodyRandomRotationDistributionToUniform(const RigidBody<T>& body);
32853366

3367+
/// Sets the distribution used by SetRandomState() to populate the free
3368+
/// body's orientation with respect to its parent frame, expressed with
3369+
/// roll-pitch-yaw angles. Requires that the free body is modeled using an
3370+
/// RpyFloatingJoint.
3371+
/// @note The parent frame is not necessarily the world frame. See
3372+
/// @ref mbp_working_with_free_bodies "above for details".
3373+
/// @note This distribution is not uniform over the sphere reachable by
3374+
/// the three angles. For a uniform alternative, switch the joint to
3375+
/// QuaternionFloatingJoint and use
3376+
/// SetFreeBodyRandomRotationDistributionToUniform().
3377+
/// @throws std::exception if `body` is not a free body in the model.
3378+
/// @throws std::exception if the free body is not modeled with an
3379+
/// RpyFloatingJoint.
3380+
/// @throws std::exception if called pre-finalize.
3381+
/// @see SetFreeBodyRandomRotationDistribution() for a free body that is
3382+
/// modeled using a QuaternionFloatingJoint.
3383+
/// @see SetBaseBodyJointType() for control over the type of automatically-
3384+
/// added joints.
3385+
void SetFreeBodyRandomAnglesDistribution(
3386+
const RigidBody<T>& body,
3387+
const math::RollPitchYaw<symbolic::Expression>& angles) {
3388+
this->mutable_tree().SetFreeBodyRandomAnglesDistributionOrThrow(body,
3389+
angles);
3390+
}
3391+
32863392
/// Sets `context` to store the pose `X_WB` of a given _floating base_ `body`
32873393
/// B in the world frame W.
32883394
/// @param[in] context

multibody/plant/test/floating_body_test.cc

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,17 @@ namespace internal {
1717
class MultibodyTreeTester {
1818
public:
1919
MultibodyTreeTester() = delete;
20+
2021
static const QuaternionFloatingMobilizer<double>& get_floating_mobilizer(
2122
const MultibodyTree<double>& model, const RigidBody<double>& body) {
22-
return model.GetFreeBodyMobilizerOrThrow(body);
23+
const Mobilizer<double>& mobilizer =
24+
model.GetFreeBodyMobilizerOrThrow(body);
25+
const QuaternionFloatingMobilizer<double>* const
26+
quaternion_floating_mobilizer =
27+
dynamic_cast<const QuaternionFloatingMobilizer<double>*>(
28+
&mobilizer);
29+
DRAKE_DEMAND(quaternion_floating_mobilizer != nullptr);
30+
return *quaternion_floating_mobilizer;
2331
}
2432
};
2533
} // namespace internal

0 commit comments

Comments
 (0)