Repository: RobotLocomotion/drake (C++)

Issue:
Collision Filtering Supports Deformables
**Is your feature request related to a problem? Please describe.**
I have created a simulation where I am essentially modeling a series of "cubby-holes" / "bins" (see [slack thread](https://drakedevelopers.slack.com/archives/C2WBPQDB7/p1745953883917969) or google "Amazon fabric pod" for illustrations) as a series of anchored rigid bodies. To make my sim more closely match reality, I'd like to model the front "lip" (small strip preventing items from sliding out of each bin) as deformable. I can model this scenario by creating a deformable cuboid and using [SetWallBoundaryCondition](https://drake.mit.edu/doxygen_cxx/classdrake_1_1multibody_1_1_deformable_model.html#a8e4f2d02a025433b059374ae0023881c) to fix the deformable lip where I want it. However, collisions are still reported where the wall boundary condition is applied to the mesh (i.e. between the deformable geometry and surrounding rigid geometries corresponding to the side walls of the bin, etc.). I'd like to filter such collisions out, but collision filtering currently does not support deformable geometries. 

To express this in a more general sense: I'd like to affix portions of a deformable body to an anchored body. I don't want to model collisions at the "attachment" point. 

**Describe the solution you'd like**
I would like the set of APIs related to collision filters (e.g. `ExcludeCollisionGeometriesWithCollisionFilterGroupPair()`) to also support deformables geometries. 

**Describe alternatives you've considered**
- Potentially there is a way to infer the appropriate filter to apply whenever `SetWallBoundaryCondition` is called. And Drake applies this filter automatically. 

**Additional context**
[Slack thread](https://drakedevelopers.slack.com/archives/C2WBPQDB7/p1745953883917969) where this question originated. 


Discussion: We should be able to support the collision filtering  API based on geometry ids in the short term. In the long term, we should also support filtering specified in the xml model files.

Automatically filtering contacts based on boundary conditions is trickier and I’m not sure if that’s the direction we want to pursue.



Fix (patch):
diff --git a/multibody/parsing/BUILD.bazel b/multibody/parsing/BUILD.bazel
index 28bdd2223e8b..4699b2335239 100644
--- a/multibody/parsing/BUILD.bazel
+++ b/multibody/parsing/BUILD.bazel
@@ -500,7 +500,7 @@ drake_cc_googletest(
 filegroup(
     name = "process_model_directives_test_models",
     testonly = True,
-    data = glob(["test/process_model_directives_test/**"]),
+    data = glob(["test/process_model_directives_test/**"]) + [":test_models"],
     visibility = ["//bindings/pydrake/multibody:__pkg__"],
 )
 
diff --git a/multibody/parsing/detail_collision_filter_group_resolver.cc b/multibody/parsing/detail_collision_filter_group_resolver.cc
index a5b235cacd19..89c4ff3f0f15 100644
--- a/multibody/parsing/detail_collision_filter_group_resolver.cc
+++ b/multibody/parsing/detail_collision_filter_group_resolver.cc
@@ -62,7 +62,11 @@ void CollisionFilterGroupResolver::AddGroup(
     const ScopedName scoped_body_name =
         ScopedName::Parse(FullyQualify(body_name, model_instance));
 
+    // The body name may refer to a rigid body or a deformable body. If the same
+    // scoped name exists as rigid and deformable in the model, then this code
+    // simply selects the rigid body.
     const RigidBody<double>* body{};
+    const DeformableBody<double>* deformable_body{};
     if (plant_->HasModelInstanceNamed(scoped_body_name.get_namespace())) {
       ModelInstanceIndex body_model =
           plant_->GetModelInstanceByName(scoped_body_name.get_namespace());
@@ -73,14 +77,28 @@ void CollisionFilterGroupResolver::AddGroup(
         continue;
       }
       body = FindBody(scoped_body_name.get_element(), body_model);
+      deformable_body = FindDeformableBody(
+          std::string(scoped_body_name.get_element()), body_model);
     }
-    if (!body) {
+
+    if (body && deformable_body) {
+      diagnostic.Error(
+          fmt::format("body name '{}' is ambiguous, refers to both a rigid and "
+                      "deformable body",
+                      scoped_body_name));
+      continue;
+    } else if (!body && !deformable_body) {
       diagnostic.Error(
           fmt::format("body with name '{}' not found", scoped_body_name));
       continue;
     }
+
     full_names.insert(std::string(scoped_body_name.get_full()));
-    geometry_set.Add(plant_->GetBodyFrameIdOrThrow(body->index()));
+    if (body) {
+      geometry_set.Add(plant_->GetBodyFrameIdOrThrow(body->index()));
+    } else if (deformable_body) {
+      geometry_set.Add(deformable_body->geometry_id());
+    }
   }
   groups_.insert({full_group_name, {full_names, geometry_set}});
 
@@ -277,6 +295,16 @@ const RigidBody<double>* CollisionFilterGroupResolver::FindBody(
   return {};
 }
 
+const DeformableBody<double>* CollisionFilterGroupResolver::FindDeformableBody(
+    std::string name, ModelInstanceIndex model_instance) const {
+  const auto& deformable_model = plant_->deformable_model();
+
+  if (deformable_model.HasBodyNamed(name, model_instance)) {
+    return &deformable_model.GetBodyByName(name, model_instance);
+  }
+  return {};
+}
+
 }  // namespace internal
 }  // namespace multibody
 }  // namespace drake
diff --git a/multibody/parsing/detail_collision_filter_group_resolver.h b/multibody/parsing/detail_collision_filter_group_resolver.h
index 27eb02ffb1d0..aea5f5d83966 100644
--- a/multibody/parsing/detail_collision_filter_group_resolver.h
+++ b/multibody/parsing/detail_collision_filter_group_resolver.h
@@ -72,8 +72,8 @@ class CollisionFilterGroupResolver {
   }
 
   // Adds a collision filter group. Locates bodies by name immediately, and
-  // emits errors for illegal names, empty groups, missing bodies, or duplicate
-  // group definition attempts.
+  // emits errors for illegal or ambiguous names, empty groups, missing bodies,
+  // or duplicate group definition attempts.
   //
   // @param diagnostic          The error-reporting channel.
   // @param group_name          The name of the group being defined.
@@ -134,6 +134,8 @@ class CollisionFilterGroupResolver {
 
   const RigidBody<double>* FindBody(std::string_view name,
                                     ModelInstanceIndex model_instance) const;
+  const DeformableBody<double>* FindDeformableBody(
+      std::string name, ModelInstanceIndex model_instance) const;
 
   MultibodyPlant<double>* const plant_;
 
diff --git a/multibody/plant/multibody_plant.cc b/multibody/plant/multibody_plant.cc
index 79551fe15d81..a0a378535e70 100644
--- a/multibody/plant/multibody_plant.cc
+++ b/multibody/plant/multibody_plant.cc
@@ -1753,11 +1753,11 @@ void MultibodyPlant<T>::ExcludeCollisionGeometriesWithCollisionFilterGroupPair(
 
   if (collision_filter_group_a.first == collision_filter_group_b.first) {
     scene_graph_->collision_filter_manager().Apply(
-        CollisionFilterDeclaration(CollisionFilterScope::kOmitDeformable)
+        CollisionFilterDeclaration(CollisionFilterScope::kAll)
             .ExcludeWithin(collision_filter_group_a.second));
   } else {
     scene_graph_->collision_filter_manager().Apply(
-        CollisionFilterDeclaration(CollisionFilterScope::kOmitDeformable)
+        CollisionFilterDeclaration(CollisionFilterScope::kAll)
             .ExcludeBetween(collision_filter_group_a.second,
                             collision_filter_group_b.second));
   }
diff --git a/multibody/plant/multibody_plant.h b/multibody/plant/multibody_plant.h
index 9d4e31f29550..9967b6f941f1 100644
--- a/multibody/plant/multibody_plant.h
+++ b/multibody/plant/multibody_plant.h
@@ -947,8 +947,11 @@ call to Finalize() must be performed. This call will:
 - declare collision filters to ignore collisions among rigid bodies:
   - between rigid bodies connected by a joint,
   - within subgraphs of welded rigid bodies.
-Note that MultibodyPlant will *not* introduce *any* collision filters
-on deformable bodies.
+
+Note that MultibodyPlant will *not* introduce any automatic collision filters on
+deformable bodies. Collision filters for deformable bodies can be explicitly
+applied via ExcludeCollisionGeometriesWithCollisionFilterGroupPair() or during
+parsing.
 
 <!-- TODO(amcastro-tri): Consider making the actual geometry registration
      with GS AFTER Finalize() so that we can tell if there are any bodies
@@ -2402,9 +2405,8 @@ class MultibodyPlant final : public internal::MultibodyTreeSystem<T> {
   const std::vector<geometry::GeometryId>& GetCollisionGeometriesForBody(
       const RigidBody<T>& body) const;
 
-  /// Excludes the rigid collision geometries between two given collision filter
-  /// groups. Note that collisions involving deformable geometries are not
-  /// filtered by this function.
+  /// Excludes the collision geometries between two given collision filter
+  /// groups.
   /// @pre RegisterAsSourceForSceneGraph() has been called.
   /// @pre Finalize() has *not* been called.
   void ExcludeCollisionGeometriesWithCollisionFilterGroupPair(