From 76ddc3007287f9650d61b3c500dfde7e3b02315a Mon Sep 17 00:00:00 2001 From: Sam Haves Date: Thu, 22 May 2025 14:03:52 -0700 Subject: [PATCH] Make MJCF SdfFileFormat plugin create hierarchy reflecting MJCF instead of sibling rigidbodies. PiperOrigin-RevId: 762117447 Change-Id: Id13f7a4f4e383a0c9e670e5272836f2fb1099830 --- .../usd/plugins/mjcf/mujoco_to_usd.cc | 79 +++---- .../usd/plugins/mjcf/mjcf_file_format_test.cc | 209 ++++++++---------- 2 files changed, 117 insertions(+), 171 deletions(-) diff --git a/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc b/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc index 289177b2..d8bf0e3e 100644 --- a/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc +++ b/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc @@ -154,7 +154,6 @@ class ModelWriter { ModelWriter(mjSpec *spec, mjModel *model, pxr::SdfAbstractDataRefPtr &data) : spec_(spec), model_(model), data_(data), class_path_("/Bad_Path") { body_paths_ = std::vector(model->nbody); - body_xforms_ = std::vector(model->nbody); } ~ModelWriter() { mj_deleteModel(model_); } @@ -165,7 +164,6 @@ class ModelWriter { // Create the world body. body_paths_[kWorldIndex] = WriteWorldBody(kWorldIndex); - body_xforms_[kWorldIndex] = pxr::GfMatrix4d().SetIdentity(); SetLayerMetadata(data_, pxr::SdfFieldKeys->Documentation, "Generated by mujoco model writer."); @@ -198,8 +196,6 @@ class ModelWriter { pxr::SdfPath class_path_; // Mapping from Mujoco body id to SdfPath. std::vector body_paths_; - // Mapping from Mujoco body id to world space transform. - std::vector body_xforms_; // Mapping from mesh names to Mesh prim path. std::unordered_map mesh_paths_; @@ -248,44 +244,6 @@ class ModelWriter { pxr::TfToken body_name; }; - pxr::SdfPath CreateParentIfNotExists(mjsBody *body, - const pxr::SdfPath &world_path, - pxr::SdfAbstractDataRefPtr &data) { - // To allow for easier scene authoring and modification, we want to - // place MJCF bodies belonging to the same kinematic chain under some - // identity parent Xform prim. This allows users to move the entire - // asset. - // - // We cannot simply recreate the MJCF kinematic tree structure - // because in USD it is assumed that children move rigidly with their - // parents. This is not true in MJCF if you have joints. We could perhaps - // use a more complex heuristic where we evaluate a common tree prefix - // in MJCF that is effectively welded together but for now we choose - // simplicity. - - // In the trivial case where the parent of body is already the world - // body we want to create a parent xform of the same name. - // So if the MJCF has a child of the world body called "root" we will - // create a parent Xform at /World/root and the actual body will be - // created at /World/root/root. - mjsBody *last_parent = body; - mjsBody *parent = mjs_getParent(body->element); - while (mjs_getId(parent->element) != kWorldIndex) { - last_parent = parent; - parent = mjs_getParent(parent->element); - } - - pxr::TfToken last_parent_name = GetValidPrimName(*last_parent->name); - pxr::SdfPath parent_xform_path = world_path.AppendChild(last_parent_name); - if (!data->HasSpec(parent_xform_path)) { - pxr::SdfPath prim_path = CreatePrimSpec( - data, world_path, last_parent_name, pxr::UsdGeomTokens->Xform); - - SetPrimKind(data_, prim_path, pxr::KindTokens->component); - } - return parent_xform_path; - } - void WriteScaleXformOp(const pxr::SdfPath &prim_path, const pxr::GfVec3f &scale) { pxr::SdfPath scale_attr_path = @@ -1263,8 +1221,10 @@ class ModelWriter { void WriteBody(mjsBody *body, bool write_physics) { int body_id = mjs_getId(body->element); - pxr::SdfPath parent_path = - CreateParentIfNotExists(body, body_paths_[kWorldIndex], data_); + // This should be safe as we process parent bodies before children. + mjsBody *parent = mjs_getParent(body->element); + int parent_id = mjs_getId(parent->element); + pxr::SdfPath parent_path = body_paths_[parent_id]; pxr::TfToken body_name = GetValidPrimName(*body->name); // Create Xform prim for body. @@ -1272,12 +1232,25 @@ class ModelWriter { pxr::UsdGeomTokens->Xform); // The parent_path will be a component which makes the actual articulated // bodies subcomponents. - SetPrimKind(data_, body_path, pxr::KindTokens->subcomponent); + auto kind = parent_id == kWorldIndex ? pxr::KindTokens->component + : pxr::KindTokens->subcomponent; + SetPrimKind(data_, body_path, kind); // Apply the PhysicsRigidBodyAPI schema if we are writing physics. if (write_physics) { ApplyApiSchema(data_, body_path, pxr::UsdPhysicsTokens->PhysicsRigidBodyAPI); + + // If the parent is not the world body, but is child of the world body + // then we need to apply the articulation root API. + if (parent_id != kWorldIndex) { + int parent_parent_id = + mjs_getId(mjs_getParent(parent->element)->element); + if (parent_parent_id == kWorldIndex) { + ApplyApiSchema(data_, parent_path, + pxr::UsdPhysicsTokens->PhysicsArticulationRootAPI); + } + } } // Create classes if necessary @@ -1290,17 +1263,15 @@ class ModelWriter { } // Create XformOp attribute for body transform. - // Make sure to account for the parent since UsdPhysics doesn't support - // nested bodies! - auto parent_xform = body_xforms_[model_->body_parentid[body_id]]; + + pxr::SdfPath xform_op_path = + CreateAttributeSpec(data_, body_path, kTokens->xformOpTransform, + pxr::SdfValueTypeNames->Matrix4d); // mjModel will have all frames already accounted for so no need to worry // about them here. - body_xforms_[body_id] = - MujocoPosQuatToTransform(&model_->body_pos[body_id * 3], - &model_->body_quat[body_id * 4]) * - parent_xform; - WriteUniformAttribute(body_path, pxr::SdfValueTypeNames->Matrix4d, - kTokens->xformOpTransform, body_xforms_[body_id]); + auto body_xform = MujocoPosQuatToTransform(&model_->body_pos[body_id * 3], + &model_->body_quat[body_id * 4]); + SetAttributeDefault(data_, xform_op_path, body_xform); // Create XformOpOrder attribute for body transform order. // For us this is simply the transform we authored above. diff --git a/test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc b/test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc index b1359540..9e2995de 100644 --- a/test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc +++ b/test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -116,8 +117,8 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestBasicMeshSources) { auto stage = pxr::UsdStage::Open(layer); EXPECT_PRIM_VALID(stage, "/mesh_test"); - EXPECT_PRIM_VALID(stage, "/mesh_test/test_body/test_body/tetrahedron"); - EXPECT_PRIM_VALID(stage, "/mesh_test/test_body/test_body/tetrahedron/Mesh"); + EXPECT_PRIM_VALID(stage, "/mesh_test/test_body/tetrahedron"); + EXPECT_PRIM_VALID(stage, "/mesh_test/test_body/tetrahedron/Mesh"); } TEST_F(MjcfSdfFileFormatPluginTest, TestMaterials) { @@ -222,7 +223,7 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestFaceVaryingMeshSourcesSimpleMjcfMesh) { auto stage = pxr::UsdStage::Open(layer); auto mesh = pxr::UsdGeomMesh::Get( - stage, SdfPath("/mesh_test/test_body/test_body/tetrahedron/Mesh")); + stage, SdfPath("/mesh_test/test_body/tetrahedron/Mesh")); ASSERT_TRUE(mesh); pxr::VtArray face_vertex_counts; mesh.GetFaceVertexCountsAttr().Get(&face_vertex_counts); @@ -273,8 +274,8 @@ TEST_F(MjcfSdfFileFormatPluginTest, auto stage = pxr::UsdStage::Open(xml_path); EXPECT_THAT(stage, testing::NotNull()); - auto mesh = pxr::UsdGeomMesh::Get( - stage, SdfPath("/mesh_test/test_body/test_body/mesh/Mesh")); + auto mesh = + pxr::UsdGeomMesh::Get(stage, SdfPath("/mesh_test/test_body/mesh/Mesh")); ASSERT_TRUE(mesh); pxr::VtArray face_vertex_counts; mesh.GetFaceVertexCountsAttr().Get(&face_vertex_counts); @@ -357,9 +358,9 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestBody) { pxr::SdfLayerRefPtr layer = LoadLayer(kXml); auto stage = pxr::UsdStage::Open(layer); + EXPECT_PRIM_VALID(stage, "/body_test"); EXPECT_PRIM_VALID(stage, "/body_test/test_body"); - EXPECT_PRIM_VALID(stage, "/body_test/test_body/test_body"); EXPECT_PRIM_VALID(stage, "/body_test/test_body/test_body_2"); } @@ -381,10 +382,9 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestBasicParenting) { auto stage = pxr::UsdStage::Open(layer); EXPECT_PRIM_VALID(stage, "/test/root"); - EXPECT_PRIM_VALID(stage, "/test/root/root"); EXPECT_PRIM_VALID(stage, "/test/root/root_body_1"); EXPECT_PRIM_VALID(stage, "/test/root/root_body_2"); - EXPECT_PRIM_VALID(stage, "/test/root/root_body_3"); + EXPECT_PRIM_VALID(stage, "/test/root/root_body_2/root_body_3"); } TEST_F(MjcfSdfFileFormatPluginTest, TestJointsDoNotAffectParenting) { @@ -412,9 +412,8 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestJointsDoNotAffectParenting) { auto stage = pxr::UsdStage::Open(layer); EXPECT_PRIM_VALID(stage, "/test/root"); - EXPECT_PRIM_VALID(stage, "/test/root/root"); EXPECT_PRIM_VALID(stage, "/test/root/middle"); - EXPECT_PRIM_VALID(stage, "/test/root/tet"); + EXPECT_PRIM_VALID(stage, "/test/root/middle/tet"); } TEST_F(MjcfSdfFileFormatPluginTest, TestKindAuthoring) { @@ -443,9 +442,9 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestKindAuthoring) { auto stage = pxr::UsdStage::Open(layer); EXPECT_PRIM_KIND(stage, "/test", pxr::KindTokens->group); EXPECT_PRIM_KIND(stage, "/test/root", pxr::KindTokens->component); - EXPECT_PRIM_KIND(stage, "/test/root/root", pxr::KindTokens->subcomponent); EXPECT_PRIM_KIND(stage, "/test/root/middle", pxr::KindTokens->subcomponent); - EXPECT_PRIM_KIND(stage, "/test/root/tet", pxr::KindTokens->subcomponent); + EXPECT_PRIM_KIND(stage, "/test/root/middle/tet", + pxr::KindTokens->subcomponent); } TEST_F(MjcfSdfFileFormatPluginTest, TestGeomsPrims) { @@ -936,28 +935,23 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestSitePrimsAuthored) { auto stage = pxr::UsdStage::Open(layer); EXPECT_PRIM_VALID(stage, "/test/box_site"); EXPECT_PRIM_IS_A(stage, "/test/box_site", pxr::UsdGeomCube); - EXPECT_PRIM_API_APPLIED(stage, "/test/box_site", MjcPhysicsSiteAPI); - - EXPECT_PRIM_VALID(stage, "/test/ball/ball/sphere_site"); - EXPECT_PRIM_IS_A(stage, "/test/ball/ball/sphere_site", pxr::UsdGeomSphere); - EXPECT_PRIM_API_APPLIED(stage, "/test/ball/ball/sphere_site", - MjcPhysicsSiteAPI); - - EXPECT_PRIM_VALID(stage, "/test/ball/ball/capsule_site"); - EXPECT_PRIM_IS_A(stage, "/test/ball/ball/capsule_site", pxr::UsdGeomCapsule); - EXPECT_PRIM_API_APPLIED(stage, "/test/ball/ball/capsule_site", - MjcPhysicsSiteAPI); - - EXPECT_PRIM_VALID(stage, "/test/ball/ball/cylinder_site"); - EXPECT_PRIM_IS_A(stage, "/test/ball/ball/cylinder_site", - pxr::UsdGeomCylinder); - EXPECT_PRIM_API_APPLIED(stage, "/test/ball/ball/cylinder_site", - MjcPhysicsSiteAPI); - - EXPECT_PRIM_VALID(stage, "/test/ball/ball/ellipsoid_site"); - EXPECT_PRIM_IS_A(stage, "/test/ball/ball/ellipsoid_site", pxr::UsdGeomSphere); - EXPECT_PRIM_API_APPLIED(stage, "/test/ball/ball/ellipsoid_site", - MjcPhysicsSiteAPI); + EXPECT_PRIM_API_APPLIED(stage, "/test/box_site", pxr::MjcPhysicsSiteAPI); + EXPECT_PRIM_VALID(stage, "/test/ball/sphere_site"); + EXPECT_PRIM_IS_A(stage, "/test/ball/sphere_site", pxr::UsdGeomSphere); + EXPECT_PRIM_API_APPLIED(stage, "/test/ball/sphere_site", + pxr::MjcPhysicsSiteAPI); + EXPECT_PRIM_VALID(stage, "/test/ball/capsule_site"); + EXPECT_PRIM_IS_A(stage, "/test/ball/capsule_site", pxr::UsdGeomCapsule); + EXPECT_PRIM_API_APPLIED(stage, "/test/ball/capsule_site", + pxr::MjcPhysicsSiteAPI); + EXPECT_PRIM_VALID(stage, "/test/ball/cylinder_site"); + EXPECT_PRIM_IS_A(stage, "/test/ball/cylinder_site", pxr::UsdGeomCylinder); + EXPECT_PRIM_API_APPLIED(stage, "/test/ball/cylinder_site", + pxr::MjcPhysicsSiteAPI); + EXPECT_PRIM_VALID(stage, "/test/ball/ellipsoid_site"); + EXPECT_PRIM_IS_A(stage, "/test/ball/ellipsoid_site", pxr::UsdGeomSphere); + EXPECT_PRIM_API_APPLIED(stage, "/test/ball/ellipsoid_site", + pxr::MjcPhysicsSiteAPI); } TEST_F(MjcfSdfFileFormatPluginTest, TestSitePrimsPurpose) { @@ -965,13 +959,13 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestSitePrimsPurpose) { auto stage = pxr::UsdStage::Open(layer); EXPECT_PRIM_PURPOSE(stage, "/test/box_site", pxr::UsdGeomTokens->guide); - EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/sphere_site", + EXPECT_PRIM_PURPOSE(stage, "/test/ball/sphere_site", pxr::UsdGeomTokens->guide); - EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/capsule_site", + EXPECT_PRIM_PURPOSE(stage, "/test/ball/capsule_site", pxr::UsdGeomTokens->guide); - EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/cylinder_site", + EXPECT_PRIM_PURPOSE(stage, "/test/ball/cylinder_site", pxr::UsdGeomTokens->guide); - EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/ellipsoid_site", + EXPECT_PRIM_PURPOSE(stage, "/test/ball/ellipsoid_site", pxr::UsdGeomTokens->guide); } @@ -981,9 +975,8 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsToggleSdfFormatArg) { // Test that the default is no physics. auto stage_no_physics = pxr::UsdStage::Open(xml_path); EXPECT_THAT(stage_no_physics, testing::NotNull()); - EXPECT_PRIM_VALID(stage_no_physics, "/mesh_test/test_body/test_body"); - EXPECT_PRIM_API_NOT_APPLIED(stage_no_physics, - "/mesh_test/test_body/test_body", + EXPECT_PRIM_VALID(stage_no_physics, "/mesh_test/test_body"); + EXPECT_PRIM_API_NOT_APPLIED(stage_no_physics, "/mesh_test/test_body", pxr::UsdPhysicsRigidBodyAPI); // Then test that the physics flag enables physics. @@ -992,8 +985,8 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsToggleSdfFormatArg) { auto stage_with_physics = pxr::UsdStage::Open(xml_path_physics_flag); EXPECT_THAT(stage_with_physics, testing::NotNull()); - EXPECT_PRIM_VALID(stage_with_physics, "/mesh_test/test_body/test_body"); - EXPECT_PRIM_API_APPLIED(stage_with_physics, "/mesh_test/test_body/test_body", + EXPECT_PRIM_VALID(stage_with_physics, "/mesh_test/test_body"); + EXPECT_PRIM_API_APPLIED(stage_with_physics, "/mesh_test/test_body", pxr::UsdPhysicsRigidBodyAPI); } @@ -1019,23 +1012,21 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsRigidBody) { EXPECT_THAT(stage, testing::NotNull()); EXPECT_PRIM_VALID(stage, "/physics_test"); EXPECT_PRIM_VALID(stage, "/physics_test/test_body"); - EXPECT_PRIM_VALID(stage, "/physics_test/test_body/test_body"); - // USD does not allow nested rigidbodies so we put them as siblings to the - // first body in the hierarchy. EXPECT_PRIM_VALID(stage, "/physics_test/test_body/test_body_2"); - // The parent containing the body should not have the RigidBodyAPI applied. - EXPECT_PRIM_API_NOT_APPLIED(stage, "/physics_test/test_body", - pxr::UsdPhysicsRigidBodyAPI); - - EXPECT_PRIM_API_APPLIED(stage, "/physics_test/test_body/test_body", + EXPECT_PRIM_API_APPLIED(stage, "/physics_test/test_body", pxr::UsdPhysicsRigidBodyAPI); + EXPECT_PRIM_API_APPLIED(stage, "/physics_test/test_body", + pxr::UsdPhysicsArticulationRootAPI); EXPECT_PRIM_API_APPLIED(stage, "/physics_test/test_body/test_body_2", pxr::UsdPhysicsRigidBodyAPI); + // Only the root body should have the articulation API applied. + EXPECT_PRIM_API_NOT_APPLIED(stage, "/physics_test/test_body/test_body_2", + pxr::UsdPhysicsArticulationRootAPI); + // Geoms should not have RigidBodyAPI applied either. - EXPECT_PRIM_API_NOT_APPLIED(stage, - "/physics_test/test_body/test_body/test_geom", + EXPECT_PRIM_API_NOT_APPLIED(stage, "/physics_test/test_body/test_geom", pxr::UsdPhysicsRigidBodyAPI); EXPECT_PRIM_API_NOT_APPLIED(stage, "/physics_test/test_body/test_body_2/test_geom_2", @@ -1093,22 +1084,22 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsColliders) { // // ground [collider] // - // body_0/body_0 [rigidbody] - // body_0/body_0/body_0_col [collider] + // body_0 [rigidbody] + // body_0/body_0_col [collider] // - // body_0/body_0_0 [rigidbody] <-- USD reparents nested rigid bodies - // body_0/body_0/body_0_0/body_0_0_col [collider] + // body_0/body_0_0 [rigidbody] + // body_0/body_0_0/body_0_0_col [collider] // - // body_1/body_1 [rigidbody] - // body_1/body_1/body_1_col_0 [collider] - // body_1/body_1/body_1_col_1 [collider] + // body_1 [rigidbody] + // body_1/body_1_col_0 [collider] + // body_1/body_1_col_1 [collider] // - // body_2/body_2 [rigidbody] - // body_2/body_2/body_2_nocol [] + // body_2 [rigidbody] + // body_2/body_2_nocol [] // - // body_3/body_3 [rigidbody] - // body_3/body_3/body_3_col [] <-- Intermediate prim for mesh instancing - // body_3/body_3/body_3_col/Mesh [collider, mesh collider] + // body_3 [rigidbody] + // body_3/body_3_col [] <-- Intermediate prim for mesh instancing + // body_3/body_3_col/Mesh [collider, mesh collider] // ground [collider] (Static collider) EXPECT_PRIM_VALID(stage, "/test/ground"); @@ -1116,19 +1107,6 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsColliders) { pxr::UsdPhysicsRigidBodyAPI); EXPECT_PRIM_API_APPLIED(stage, "/test/ground", pxr::UsdPhysicsCollisionAPI); - // body_0/body_0 [rigidbody] - EXPECT_PRIM_VALID(stage, "/test/body_0/body_0"); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_0/body_0", - pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_0/body_0", - pxr::UsdPhysicsCollisionAPI); - // body_0/body_0/body_0_col [collider] - EXPECT_PRIM_VALID(stage, "/test/body_0/body_0/body_0_col"); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_0/body_0/body_0_col", - pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_0/body_0/body_0_col", - pxr::UsdPhysicsCollisionAPI); - // body_0/body_0_0 [rigidbody] (Nested body - reparented) EXPECT_PRIM_VALID(stage, "/test/body_0/body_0_0"); EXPECT_PRIM_API_APPLIED(stage, "/test/body_0/body_0_0", @@ -1142,61 +1120,58 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsColliders) { EXPECT_PRIM_API_APPLIED(stage, "/test/body_0/body_0_0/body_0_0_col", pxr::UsdPhysicsCollisionAPI); - // body_1/body_1 [rigidbody] - EXPECT_PRIM_VALID(stage, "/test/body_1/body_1"); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_1/body_1", - pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_1/body_1", + // body_1 [rigidbody] + EXPECT_PRIM_VALID(stage, "/test/body_1"); + EXPECT_PRIM_API_APPLIED(stage, "/test/body_1", pxr::UsdPhysicsRigidBodyAPI); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_1", pxr::UsdPhysicsCollisionAPI); - // body_1/body_1/body_1_col_0 [collider] - EXPECT_PRIM_VALID(stage, "/test/body_1/body_1/body_1_col_0"); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_1/body_1/body_1_col_0", + // body_1/body_1_col_0 [collider] + EXPECT_PRIM_VALID(stage, "/test/body_1/body_1_col_0"); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_1/body_1_col_0", pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_1/body_1/body_1_col_0", + EXPECT_PRIM_API_APPLIED(stage, "/test/body_1/body_1_col_0", pxr::UsdPhysicsCollisionAPI); - // body_1/body_1/body_1_col_1 [collider] - EXPECT_PRIM_VALID(stage, "/test/body_1/body_1/body_1_col_1"); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_1/body_1/body_1_col_1", + // body_1/body_1_col_1 [collider] + EXPECT_PRIM_VALID(stage, "/test/body_1/body_1_col_1"); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_1/body_1_col_1", pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_1/body_1/body_1_col_1", + EXPECT_PRIM_API_APPLIED(stage, "/test/body_1/body_1_col_1", pxr::UsdPhysicsCollisionAPI); - // body_2/body_2 [rigidbody] - EXPECT_PRIM_VALID(stage, "/test/body_2/body_2"); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_2/body_2", - pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_2/body_2", + // body_2 [rigidbody] + EXPECT_PRIM_VALID(stage, "/test/body_2"); + EXPECT_PRIM_API_APPLIED(stage, "/test/body_2", pxr::UsdPhysicsRigidBodyAPI); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_2", pxr::UsdPhysicsCollisionAPI); - // body_2/body_2/body_2_nocol [] (No physics APIs applied) - EXPECT_PRIM_VALID(stage, "/test/body_2/body_2/body_2_nocol"); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_2/body_2/body_2_nocol", + // body_2/body_2_nocol [] (No physics APIs applied) + EXPECT_PRIM_VALID(stage, "/test/body_2/body_2_nocol"); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_2/body_2_nocol", pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_2/body_2/body_2_nocol", + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_2/body_2_nocol", pxr::UsdPhysicsCollisionAPI); - // body_3/body_3 [rigidbody] - EXPECT_PRIM_VALID(stage, "/test/body_3/body_3"); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_3/body_3", - pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3", + // body_3 [rigidbody] + EXPECT_PRIM_VALID(stage, "/test/body_3"); + EXPECT_PRIM_API_APPLIED(stage, "/test/body_3", pxr::UsdPhysicsRigidBodyAPI); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3", pxr::UsdPhysicsCollisionAPI); - // body_3/body_3/body_3_col [] (Intermediate prim for mesh instancing) - EXPECT_PRIM_VALID(stage, "/test/body_3/body_3/body_3_col"); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3/body_3_col", + // body_3/body_3_col [] (Intermediate prim for mesh instancing) + EXPECT_PRIM_VALID(stage, "/test/body_3/body_3_col"); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3_col", pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3/body_3_col", + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3_col", pxr::UsdPhysicsCollisionAPI); - // body_3/body_3/body_3_col/Mesh [collider, mesh collider] - EXPECT_PRIM_VALID(stage, "/test/body_3/body_3/body_3_col/Mesh"); - EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3/body_3_col/Mesh", + // body_3/body_3_col/Mesh [collider, mesh collider] + EXPECT_PRIM_VALID(stage, "/test/body_3/body_3_col/Mesh"); + EXPECT_PRIM_API_NOT_APPLIED(stage, "/test/body_3/body_3_col/Mesh", pxr::UsdPhysicsRigidBodyAPI); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_3/body_3/body_3_col/Mesh", + EXPECT_PRIM_API_APPLIED(stage, "/test/body_3/body_3_col/Mesh", pxr::UsdPhysicsCollisionAPI); - EXPECT_PRIM_API_APPLIED(stage, "/test/body_3/body_3/body_3_col/Mesh", + EXPECT_PRIM_API_APPLIED(stage, "/test/body_3/body_3_col/Mesh", pxr::UsdPhysicsMeshCollisionAPI); - ExpectAttributeEqual( - stage, "/test/body_3/body_3/body_3_col/Mesh.physics:approximation", - pxr::UsdPhysicsTokens->convexHull); + ExpectAttributeEqual(stage, + "/test/body_3/body_3_col/Mesh.physics:approximation", + pxr::UsdPhysicsTokens->convexHull); } } // namespace