diff --git a/superdex_robotics/src/utils/urdf_import_utils.cpp b/superdex_robotics/src/utils/urdf_import_utils.cpp index c816e9ee..3bf5245a 100644 --- a/superdex_robotics/src/utils/urdf_import_utils.cpp +++ b/superdex_robotics/src/utils/urdf_import_utils.cpp @@ -481,6 +481,9 @@ void PopulateFromParsedModel( ParsedMesh const& mesh = *parsedLink.collision; if (auto const resolved = ResolveMeshPath(mesh.filename, meshBasePath)) { link->shapeFile = DynamicString{*resolved}; + // Enable the collider for mesh-bearing links: ColliderType::None is + // the prefab default, which leaves the robot contactless. + link->colliderType = ColliderType::Auto; } if (meshRefs != nullptr) { meshRefs->links[iLink].collision = DynamicString{mesh.filename}; diff --git a/superdex_robotics/test/urdf_mesh_resolution_test.cpp b/superdex_robotics/test/urdf_mesh_resolution_test.cpp index 3eb82dad..ae4236c0 100644 --- a/superdex_robotics/test/urdf_mesh_resolution_test.cpp +++ b/superdex_robotics/test/urdf_mesh_resolution_test.cpp @@ -69,6 +69,37 @@ class UrdfMeshResolutionTest : public testing::Test { )"; } + // Same, but the mesh is the geometry instead of . + static std::string SingleMeshCollisionUrdf(std::string_view packageMeshUri) { + return std::string(R"( + + + + + + + + + +)"; + } + + // Primitive-only collision geometry (no mesh). + static std::string SinglePrimitiveCollisionUrdf() { + return std::string(R"( + + + + + + + + + +)"); + } + mochi::TempDirCleanup _tempDirCleanup = mochi::CreateTempDirectory("urdf_mesh_resolution_test", ExpectOK{}); fs::path _tempDir = _tempDirCleanup.Path(); @@ -164,3 +195,39 @@ TEST_F(UrdfMeshResolutionTest, UnresolvableMeshPreservesRawReference) { std::string(meshRefs.links[0].visual.c_str()), "package://ur_description/meshes/base.dae"); EXPECT_TRUE(meshRefs.links[0].collision.empty()); } + +// A mesh-bearing must enable the collider: ColliderType::None is the +// prefab default, which left imported robots contactless (contact queries and +// contact forces unavailable). +TEST_F(UrdfMeshResolutionTest, MeshCollisionEnablesColliderType) { + auto const packageDir = _tempDir / "ur_description"; + auto const urdfPath = packageDir / "urdf" / "robot.urdf"; + auto const meshPath = packageDir / "meshes" / "base.dae"; + + WriteFileText(meshPath, "dummy"); + WriteFileText( + urdfPath, SingleMeshCollisionUrdf("package://ur_description/meshes/base.dae")); + + BotPrefab const prefab = LoadBotPrefabFromUrdfFile(urdfPath.string(), ExpectOK{}); + + ASSERT_FALSE(prefab.links.empty()); + EXPECT_FALSE(prefab.links[0].shapeFile.empty()); + EXPECT_EQ(static_cast(prefab.links[0].colliderType), + static_cast(ColliderType::Auto)); +} + +// Primitive-only geometry keeps the prefab default: the field is not +// touched and the bot stays contactless. +TEST_F(UrdfMeshResolutionTest, PrimitiveCollisionKeepsDefaultColliderType) { + auto const packageDir = _tempDir / "some_other_dir"; + auto const urdfPath = packageDir / "urdf" / "robot.urdf"; + + WriteFileText(urdfPath, SinglePrimitiveCollisionUrdf()); + + BotPrefab const prefab = LoadBotPrefabFromUrdfFile(urdfPath.string(), ExpectOK{}); + + ASSERT_FALSE(prefab.links.empty()); + EXPECT_TRUE(prefab.links[0].shapeFile.empty()); + EXPECT_EQ(static_cast(prefab.links[0].colliderType), + static_cast(ColliderType::None)); +}