Search code examples
drake

Can the SceneGraphCollisionChecker add permanent collision filters to the scene graph?


The API for the SceneGraphCollisionChecker seems to indicate that it can make permanent changes/additions to the collision filter of the scene graph. Is this assumption correct?

I have a case where I manually add a collision body to the plant that collides with another body in a non-adjacent link. This collision is not added to the filter by MultibodyPlant::Finalize because the colliding bodies are not in adjacent links. I can use the SceneGraphCollisionChecker to identify and filter any "default" collisions. However, subsequent calls to a MathematicalProgram that uses the MinimumDistanceConstraint fail due to infeasible constraints (drake::multibody::MinimumDistanceConstraint[0]: -inf <= 3.000000 <= 1.000000), presumably because the scene graph is not actually filtering the collision pair. If I manually add the filter declaration to the scene graph, the constraint now becomes feasible.

Am I doing something incorrectly here, or does the SceneGraphCollisionChecker not actually permanently filter collisions in the scene graph? For reference, I'm working off of version 1.19.0.

resource_file = 'drake/manipulation/models/iiwa_description/urdf/iiwa14_spheres_dense_collision.urdf'
rdb = RobotDiagramBuilder(time_step=0)
resource = FindResourceOrThrow(resource_file)
model = rdb.parser().AddModelFromFile(resource)
n_pos = rdb.plant().num_positions()

# Add a body to the plant with collision geometry that collides by default
... 

# Finalize the plant
plant.Finalize()

# Build the diagram
diagram = rdb.Build()

# Check for possible collisions and add those collisions to the filter
sgcc = SceneGraphCollisionChecker(model=diagram, robot_model_instances=[model],
                                  configuration_distance_function=l2_dist, edge_step_size=0.01,
                                  env_collision_padding=0.0, self_collision_padding=0.0)

nominal_state = np.zeros((n_pos,))

# Check collisions
collision_free = sgcc.CheckConfigCollisionFree(nominal_state)  # -> reports False

clearance = sgcc.CalcRobotClearance(nominal_state, 0.0)
for idx in np.argwhere(clearance.distances() < 0.0).flatten():
    body_1 = sgcc.plant().get_body(clearance.robot_indices()[idx])
    body_2 = sgcc.plant().get_body(clearance.other_indices()[idx])
    dist = clearance.distances()[idx]
    print(f'Collision between \'{body_1.name()}\' and \'{body_2.name()}\' with distance {dist}')
    sgcc.SetCollisionFilteredBetween(body_1, body_2, True)

# Check collisions
collision_free = sgcc.CheckConfigCollisionFree(nominal_state)  # -> reports True

# Solve IK optimization with min distance constraint -> fails due to an infeasible minimum distance constraint
...

g1 = diagram.plant().CollectRegisteredGeometries([diagram.plant().GetBodyByName('body_a')])
g2 = diagram.plant().CollectRegisteredGeometries([diagram.plant().GetBodyByName('body_b')])
decl = CollisionFilterDeclaration()
decl.ExcludeBetween(g1, g2)
diagram.scene_graph().collision_filter_manager().Apply(decl)

# Solve IK optimization with min distance constraint -> no longer infeasible and succeeds

Update:

Expanding on the code I am using to solve IK. My assumption is that the scene graph object itself is holding the new collision filters

# Solve IK optimization with min distance constraint
plant_context = diagram.plant().GetMyContextFromRoot(diagram.CreateDefaultContext())
ik = InverseKinematics(diagram.plant(), plant_context)

# Add constraints
ik.AddMinimumDistanceConstraint(0.010, 0.1)
...

result = Solve(ik.prog(), initial_guess=...)  # -> fails due to an infeasible minimum distance constraint


Solution

  • ... MathematicalProgram that uses the MinimumDistanceConstraint fail ...

    I don't see the sample code for this?

    I have a guess at the problem, though.

    The CollisionChecker filtering API only configures its owned contexts; it does not configure the SceneGraph's model value filtering.

    To use MinimumDistanceConstraint with a CollisionChecker, you need to be sure to pass it plant_context that has been given to you by the CollisionChecker. You can't make a new context and expect to match the checker.