From 02ef43f79b501b34e6e86cfe2c8571f557e9ff7e Mon Sep 17 00:00:00 2001 From: castor639 <316019788+castor639@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:09:59 +0000 Subject: [PATCH] [multibody] Throw on mixed rigid/deformable body name collisions Body names must be unique within a model instance across both rigid and deformable bodies. Previously only same-type duplicates were rejected, leaving collision-filter and name lookup ambiguous for mixed types. Fixes #23257. --- multibody/plant/deformable_model.cc | 8 ++++++ multibody/plant/deformable_model.h | 4 +-- multibody/plant/multibody_plant.h | 21 ++++++++++---- multibody/plant/test/deformable_model_test.cc | 28 +++++++++++++++++++ 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/multibody/plant/deformable_model.cc b/multibody/plant/deformable_model.cc index 5b6086a9dddf..afb97333a28c 100644 --- a/multibody/plant/deformable_model.cc +++ b/multibody/plant/deformable_model.cc @@ -63,6 +63,14 @@ DeformableBodyId DeformableModel::RegisterDeformableBody( "given model.", model_instance_name, name)); } + if (this->plant().HasBodyNamed(name, model_instance)) { + const std::string& model_instance_name = + this->plant().GetModelInstanceName(model_instance); + throw std::logic_error(fmt::format( + "RegisterDeformableBody(): Model instance '{}' already contains a " + "body named '{}'. Body names must be unique within a given model.", + model_instance_name, name)); + } /* Register the geometry with SceneGraph. */ SceneGraph& scene_graph = this->mutable_scene_graph(); const ScopedName scoped_name( diff --git a/multibody/plant/deformable_model.h b/multibody/plant/deformable_model.h index 97277cb3859f..3634086b74d9 100644 --- a/multibody/plant/deformable_model.h +++ b/multibody/plant/deformable_model.h @@ -91,8 +91,8 @@ class DeformableModel final : public multibody::PhysicalModel { @throws std::exception if `this` %DeformableModel belongs to a continuous MultibodyPlant. @throws std::exception if the model instance does not exist. - @throws std::exception if a deformable body with the same name has already - been registered to the model instance. + @throws std::exception if a deformable body or rigid body with the same name + has already been registered to the model instance. @throws std::exception if Finalize() has been called on the multibody plant owning this deformable model. */ DeformableBodyId RegisterDeformableBody( diff --git a/multibody/plant/multibody_plant.h b/multibody/plant/multibody_plant.h index a173ad91ffcc..904b8459fe54 100644 --- a/multibody/plant/multibody_plant.h +++ b/multibody/plant/multibody_plant.h @@ -14,6 +14,8 @@ #include #include +#include + #include "drake/common/default_scalars.h" #include "drake/common/drake_export.h" #include "drake/common/random.h" @@ -1429,8 +1431,9 @@ class MultibodyPlant final : public internal::MultibodyTreeSystem { /// /// @param[in] name /// A string that identifies the new body to be added to `this` model. A - /// std::runtime_error is thrown if a body named `name` already is part of - /// @p model_instance. See HasBodyNamed(), RigidBody::name(). + /// std::logic_error is thrown if a rigid or deformable body named `name` + /// already is part of @p model_instance. See HasBodyNamed(), + /// RigidBody::name(). /// @param[in] model_instance /// A model instance index which this body is part of. /// @param[in] M_BBo_B @@ -1443,6 +1446,14 @@ class MultibodyPlant final : public internal::MultibodyTreeSystem { const std::string& name, ModelInstanceIndex model_instance, const SpatialInertia& M_BBo_B = SpatialInertia::Zero()) { DRAKE_MBP_THROW_IF_FINALIZED(); + // Body names must be unique within a model instance across both rigid and + // deformable bodies. + if (deformable_model().HasBodyNamed(name, model_instance)) { + throw std::logic_error(fmt::format( + "Model instance '{}' already contains a body named '{}'. Body names " + "must be unique within a given model.", + GetModelInstanceName(model_instance), name)); + } // Add the actual RigidBody (Link) to the model. const RigidBody& body = this->mutable_tree().AddLink(name, model_instance, M_BBo_B); @@ -1475,9 +1486,9 @@ class MultibodyPlant final : public internal::MultibodyTreeSystem { /// /// @param[in] name /// A string that identifies the new body to be added to `this` model. A - /// std::runtime_error is thrown if a body named `name` already is part of - /// the model in the default model instance. See HasBodyNamed(), - /// RigidBody::name(). + /// std::logic_error is thrown if a rigid or deformable body named `name` + /// already is part of the model in the default model instance. See + /// HasBodyNamed(), RigidBody::name(). /// @param[in] M_BBo_B /// The SpatialInertia of the new rigid body to be added to `this` /// %MultibodyPlant, computed about the body frame origin `Bo` and expressed diff --git a/multibody/plant/test/deformable_model_test.cc b/multibody/plant/test/deformable_model_test.cc index 95e507862bd4..9f52025a3e20 100644 --- a/multibody/plant/test/deformable_model_test.cc +++ b/multibody/plant/test/deformable_model_test.cc @@ -914,6 +914,34 @@ TEST_F(DeformableModelTest, DuplicatedNames) { EXPECT_EQ(body1.body_id(), body1_id); } +/* Rigid and deformable bodies must not share a name within a model instance. */ +TEST_F(DeformableModelTest, ConflictingRigidAndDeformableNames) { + const double kRezHint = 0.5; + const ModelInstanceIndex instance0 = plant_->AddModelInstance("instance0"); + const ModelInstanceIndex instance1 = plant_->AddModelInstance("instance1"); + + /* Rigid then deformable with the same name in one model instance throws. */ + plant_->AddRigidBody("sphere", instance0, + SpatialInertia::MakeUnitary()); + DRAKE_EXPECT_THROWS_MESSAGE( + RegisterSphere(deformable_model_ptr_, kRezHint, RigidTransformd{}, + instance0), + ".*instance0.*already contains a body named 'sphere'.*"); + + /* Deformable then rigid with the same name in one model instance throws. */ + RegisterSphere(deformable_model_ptr_, kRezHint, RigidTransformd{}, + instance1); + DRAKE_EXPECT_THROWS_MESSAGE( + plant_->AddRigidBody("sphere", instance1, + SpatialInertia::MakeUnitary()), + ".*instance1.*already contains a body named 'sphere'.*"); + + /* The same name is allowed across different model instances. */ + EXPECT_NO_THROW(plant_->AddRigidBody( + "sphere", plant_->AddModelInstance("instance2"), + SpatialInertia::MakeUnitary())); +} + } // namespace } // namespace internal } // namespace multibody