Skip to content

Commit

Permalink
Query welded bodies pre-finalization (RobotLocomotion#11257)
Browse files Browse the repository at this point in the history
* Introduce MultibodyGraph and implement FindLinksWeldedTo().
  • Loading branch information
amcastro-tri authored Apr 25, 2019
1 parent 49b34e0 commit f44d069
Show file tree
Hide file tree
Showing 15 changed files with 964 additions and 37 deletions.
1 change: 1 addition & 0 deletions multibody/plant/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ drake_cc_library(
"//geometry:scene_graph",
"//math:geometric_transform",
"//math:orthonormal_basis",
"//multibody/topology:multibody_graph",
"//multibody/tree",
"//systems/framework:diagram_builder",
"//systems/framework:leaf_system",
Expand Down
22 changes: 7 additions & 15 deletions multibody/plant/multibody_plant.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <algorithm>
#include <limits>
#include <memory>
#include <set>
#include <stdexcept>
#include <vector>

Expand Down Expand Up @@ -253,6 +254,8 @@ MultibodyPlant<T>::MultibodyPlant(
DRAKE_THROW_UNLESS(time_step >= 0);
visual_geometries_.emplace_back(); // Entries for the "world" body.
collision_geometries_.emplace_back();
// Add the world body to the graph.
multibody_graph_.AddBody(world_body().name(), world_body().model_instance());
}

template<typename T>
Expand Down Expand Up @@ -402,23 +405,12 @@ geometry::GeometrySet MultibodyPlant<T>::CollectRegisteredGeometries(
template <typename T>
std::vector<const Body<T>*> MultibodyPlant<T>::GetBodiesWeldedTo(
const Body<T>& body) const {
DRAKE_MBP_THROW_IF_NOT_FINALIZED();
// TODO(eric.cousineau): This is much slower than it should be; this could be
// sped up by either (a) caching these results at finalization and store a
// mapping from body to a subgraph or (b) starting the search from the query
// body.
auto sub_graphs = internal_tree().get_topology().CreateListOfWeldedBodies();
// Find subgraph that contains this body.
auto predicate = [&body](auto& sub_graph) {
return sub_graph.count(body.index()) > 0;
};
auto sub_graph_iter = std::find_if(
sub_graphs.begin(), sub_graphs.end(), predicate);
DRAKE_THROW_UNLESS(sub_graph_iter != sub_graphs.end());
const std::set<BodyIndex> island =
multibody_graph_.FindBodiesWeldedTo(body.index());
// Map body indices to pointers.
std::vector<const Body<T>*> sub_graph_bodies;
for (BodyIndex sub_graph_body_index : *sub_graph_iter) {
sub_graph_bodies.push_back(&internal_tree().get_body(sub_graph_body_index));
for (BodyIndex body_index : island) {
sub_graph_bodies.push_back(&internal_tree().get_body(body_index));
}
return sub_graph_bodies;
}
Expand Down
43 changes: 36 additions & 7 deletions multibody/plant/multibody_plant.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "drake/multibody/plant/coulomb_friction.h"
#include "drake/multibody/plant/implicit_stribeck_solver.h"
#include "drake/multibody/plant/implicit_stribeck_solver_results.h"
#include "drake/multibody/topology/multibody_graph.h"
#include "drake/multibody/tree/force_element.h"
#include "drake/multibody/tree/multibody_tree-inl.h"
#include "drake/multibody/tree/multibody_tree_system.h"
Expand Down Expand Up @@ -708,6 +709,9 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
const std::string& name, ModelInstanceIndex model_instance,
const SpatialInertia<double>& M_BBo_B) {
DRAKE_MBP_THROW_IF_FINALIZED();
// Make note in the graph.
multibody_graph_.AddBody(name, model_instance);
// Add the actual rigid body to the model.
const RigidBody<T>& body = this->mutable_tree().AddRigidBody(
name, model_instance, M_BBo_B);
// Each entry of visual_geometries_, ordered by body index, contains a
Expand Down Expand Up @@ -778,6 +782,17 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
const JointType<T>& AddJoint(std::unique_ptr<JointType<T>> joint) {
static_assert(std::is_convertible<JointType<T>*, Joint<T>*>::value,
"JointType must be a sub-class of Joint<T>.");

const std::string type_name = joint->type_name();
if (!multibody_graph_.IsJointTypeRegistered(type_name)) {
multibody_graph_.RegisterJointType(type_name);
}

// Note changes in the graph.
multibody_graph_.AddJoint(joint->name(), joint->model_instance(), type_name,
joint->parent_body().index(),
joint->child_body().index());

return this->mutable_tree().AddJoint(std::move(joint));
}

Expand Down Expand Up @@ -859,8 +874,20 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
const optional<math::RigidTransform<double>>& X_PF, const Body<T>& child,
const optional<math::RigidTransform<double>>& X_BM, Args&&... args) {
DRAKE_MBP_THROW_IF_FINALIZED();
return this->mutable_tree().template AddJoint<JointType>(
name, parent, X_PF, child, X_BM, std::forward<Args>(args)...);
const JointType<T>& joint =
this->mutable_tree().template AddJoint<JointType>(
name, parent, X_PF, child, X_BM, std::forward<Args>(args)...);

const std::string type_name = joint.type_name();
if (!multibody_graph_.IsJointTypeRegistered(type_name)) {
multibody_graph_.RegisterJointType(type_name);
}

// Note changes in the graph.
multibody_graph_.AddJoint(joint.name(), joint.model_instance(), type_name,
parent.index(), child.index());

return joint;
}

/// Adds a new force element model of type `ForceElementType` to `this`
Expand Down Expand Up @@ -2438,6 +2465,9 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
/// presence of a weld joint, not by other constructs that prevent mobility
/// (e.g. constraints).
///
/// This method can be called at any time during the lifetime of `this` plant,
/// either pre- or post-finalize, see Finalize().
///
/// Meant to be used with `CollectRegisteredGeometries`.
///
/// The following example demonstrates filtering collisions between all
Expand All @@ -2456,7 +2486,6 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
///
/// @returns all bodies rigidly fixed to `body`. This does not return the
/// bodies in any prescribed order.
/// @throws std::exception if called pre-finalize.
/// @throws std::exception if `body` is not part of this plant.
std::vector<const Body<T>*> GetBodiesWeldedTo(const Body<T>& body) const;

Expand Down Expand Up @@ -2534,10 +2563,8 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {

/// If the body with `body_index` has geometry registered with it, it returns
/// the geometry::FrameId associated with it. Otherwise, it returns nullopt.
/// @throws std::exception if called pre-finalize.
optional<geometry::FrameId> GetBodyFrameIdIfExists(
BodyIndex body_index) const {
DRAKE_MBP_THROW_IF_NOT_FINALIZED();
const auto it = body_index_to_frame_id_.find(body_index);
if (it == body_index_to_frame_id_.end()) {
return {};
Expand All @@ -2550,9 +2577,7 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
/// an exception.
/// @throws std::exception if no geometry has been registered with the body
/// indicated by `body_index`.
/// @throws std::exception if called pre-finalize.
geometry::FrameId GetBodyFrameIdOrThrow(BodyIndex body_index) const {
DRAKE_MBP_THROW_IF_NOT_FINALIZED();
const auto it = body_index_to_frame_id_.find(body_index);
if (it == body_index_to_frame_id_.end()) {
throw std::logic_error(
Expand Down Expand Up @@ -3657,6 +3682,10 @@ class MultibodyPlant : public internal::MultibodyTreeSystem<T> {
std::vector<systems::OutputPortIndex>
instance_generalized_contact_forces_output_ports_;

// A graph representing the body/joint topology of the multibody plant (Not
// to be confused with the spanning-tree model we will build for analysis.)
internal::MultibodyGraph multibody_graph_;

// If the plant is modeled as a discrete system with periodic updates,
// time_step_ corresponds to the period of those updates. Otherwise, if the
// plant is modeled as a continuous system, it is exactly zero.
Expand Down
39 changes: 24 additions & 15 deletions multibody/plant/test/multibody_plant_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -392,14 +392,21 @@ class AcrobotPlantTests : public ::testing::Test {
"Pre-finalize calls to '.*' are not allowed; "
"you must call Finalize\\(\\) first.");

link1_ = &plant_->GetBodyByName(parameters_.link1_name());
link2_ = &plant_->GetBodyByName(parameters_.link2_name());

// Test we can call these methods pre-finalize.
const FrameId link1_frame_id =
plant_->GetBodyFrameIdOrThrow(link1_->index());
EXPECT_EQ(link1_frame_id, *plant_->GetBodyFrameIdIfExists(link1_->index()));
EXPECT_EQ(plant_->GetBodyFromFrameId(link1_frame_id), link1_);

// Finalize() the plant.
plant_->Finalize();

// And build the Diagram:
diagram_ = builder.Build();

link1_ = &plant_->GetBodyByName(parameters_.link1_name());
link2_ = &plant_->GetBodyByName(parameters_.link2_name());
shoulder_ = &plant_->GetMutableJointByName<RevoluteJoint>(
parameters_.shoulder_joint_name());
elbow_ = &plant_->GetMutableJointByName<RevoluteJoint>(
Expand Down Expand Up @@ -1082,23 +1089,25 @@ GTEST_TEST(MultibodyPlantTest, GetBodiesWeldedTo) {
using ::testing::UnorderedElementsAreArray;
// This test expects that the following model has a world body and a pair of
// welded-together bodies.
const std::string sdf_file = FindResourceOrThrow(
"drake/multibody/plant/test/split_pendulum.sdf");
const std::string sdf_file =
FindResourceOrThrow("drake/multibody/plant/test/split_pendulum.sdf");
MultibodyPlant<double> plant;
Parser(&plant).AddModelFromFile(sdf_file);
DRAKE_EXPECT_THROWS_MESSAGE(
plant.GetBodiesWeldedTo(plant.world_body()), std::logic_error,
"Pre-finalize calls to 'GetBodiesWeldedTo\\(\\)' are not "
"allowed; you must call Finalize\\(\\) first.");
plant.Finalize();
const Body<double>& upper = plant.GetBodyByName("upper_section");
const Body<double>& lower = plant.GetBodyByName("lower_section");
EXPECT_THAT(
plant.GetBodiesWeldedTo(plant.world_body()),
UnorderedElementsAreArray({&plant.world_body()}));
EXPECT_THAT(
plant.GetBodiesWeldedTo(lower),
UnorderedElementsAreArray({&upper, &lower}));

// Verify we can call GetBodiesWeldedTo() pre-finalize.
EXPECT_THAT(plant.GetBodiesWeldedTo(plant.world_body()),
UnorderedElementsAreArray({&plant.world_body()}));
EXPECT_THAT(plant.GetBodiesWeldedTo(lower),
UnorderedElementsAreArray({&upper, &lower}));

// And post-finalize.
plant.Finalize();
EXPECT_THAT(plant.GetBodiesWeldedTo(plant.world_body()),
UnorderedElementsAreArray({&plant.world_body()}));
EXPECT_THAT(plant.GetBodiesWeldedTo(lower),
UnorderedElementsAreArray({&upper, &lower}));
}

// Verifies the process of collision geometry registration with a
Expand Down
37 changes: 37 additions & 0 deletions multibody/topology/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# -*- python -*-
# This file contains rules for Bazel; see drake/doc/bazel.rst.

load(
"@drake//tools/skylark:drake_cc.bzl",
"drake_cc_googletest",
"drake_cc_library",
"drake_cc_package_library",
)
load("//tools/lint:lint.bzl", "add_lint_tests")

package(
default_visibility = ["//visibility:public"],
)

drake_cc_library(
name = "multibody_graph",
srcs = [
"multibody_graph.cc",
],
hdrs = [
"multibody_graph.h",
],
deps = [
"//multibody/tree:multibody_tree_indexes",
],
)

drake_cc_googletest(
name = "multibody_graph_test",
deps = [
":multibody_graph",
"//common/test_utilities:expect_throws_message",
],
)

add_lint_tests()
Loading

0 comments on commit f44d069

Please sign in to comment.