Skip to content

Commit 8df3614

Browse files
Google DeepMindcopybara-github
authored andcommitted
Add support for plane geometry.
PiperOrigin-RevId: 750994019 Change-Id: Ia3b085bed5b8e2fcdb00ffc69dd5551a7fa3c30d
1 parent 9e72874 commit 8df3614

File tree

3 files changed

+101
-2
lines changed

3 files changed

+101
-2
lines changed

src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,43 @@ class ModelWriter {
777777
return WriteSphere(name, geom_size, body_path);
778778
}
779779

780+
pxr::SdfPath WritePlane(const pxr::TfToken &name, const mjtNum *size,
781+
const pxr::SdfPath &body_path) {
782+
pxr::SdfPath plane_path =
783+
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Plane);
784+
785+
// MuJoCo uses half sizes.
786+
// Note that UsdGeomPlane is infinite for simulation purposes but can have
787+
// width/length for visualization, same as MuJoCo.
788+
double width = size[0] * 2.0;
789+
double length = size[1] * 2.0;
790+
791+
pxr::SdfPath width_attr_path =
792+
CreateAttributeSpec(data_, plane_path, pxr::UsdGeomTokens->width,
793+
pxr::SdfValueTypeNames->Double);
794+
SetAttributeDefault(data_, width_attr_path, width);
795+
796+
pxr::SdfPath length_attr_path =
797+
CreateAttributeSpec(data_, plane_path, pxr::UsdGeomTokens->length,
798+
pxr::SdfValueTypeNames->Double);
799+
SetAttributeDefault(data_, length_attr_path, length);
800+
801+
// MuJoCo plane is always a XY plane with +Z up.
802+
// UsdGeomPlane is also a XY plane if axis is 'Z', which is default.
803+
// So no need to set axis attribute explicitly.
804+
805+
return plane_path;
806+
}
807+
808+
pxr::SdfPath WritePlaneGeom(const mjsGeom *geom,
809+
const pxr::SdfPath &body_path) {
810+
auto name =
811+
GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Plane, body_path);
812+
int geom_idx = mjs_getId(geom->element);
813+
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
814+
return WritePlane(name, geom_size, body_path);
815+
}
816+
780817
void WriteSite(mjsSite *site, const mjsBody *body) {
781818
const int body_id = mjs_getId(body->element);
782819
const auto &body_path = body_paths_[body_id];
@@ -804,6 +841,9 @@ class ModelWriter {
804841
pxr::SdfPath geom_path;
805842
int geom_id = mjs_getId(geom->element);
806843
switch (geom->type) {
844+
case mjGEOM_PLANE:
845+
geom_path = WritePlaneGeom(geom, body_path);
846+
break;
807847
case mjGEOM_MESH:
808848
geom_path = WriteMeshGeom(geom, body_path);
809849
break;

test/experimental/usd/plugins/mjcf/fixture.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,11 @@ template <typename T>
7070
void ExpectAttributeEqual(pxr::UsdStageRefPtr stage, const char* path,
7171
const T& value) {
7272
auto attr = stage->GetAttributeAtPath(pxr::SdfPath(path));
73-
EXPECT_TRUE(attr.IsValid());
73+
EXPECT_TRUE(attr.IsValid()) << "Attribute " << path << " is not valid";
7474
T attr_value;
7575
attr.Get(&attr_value);
76-
EXPECT_EQ(attr_value, value);
76+
EXPECT_EQ(attr_value, value) << "Attribute " << path << " has value "
77+
<< attr_value << ". Expected: " << value;
7778
}
7879

7980
// Specialization for SdfAssetPath, so that we can compare only the asset path

test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
#include <pxr/usd/usdGeom/cube.h>
4141
#include <pxr/usd/usdGeom/cylinder.h>
4242
#include <pxr/usd/usdGeom/mesh.h>
43+
#include <pxr/usd/usdGeom/plane.h>
4344
#include <pxr/usd/usdGeom/primvar.h>
4445
#include <pxr/usd/usdGeom/primvarsAPI.h>
4546
#include <pxr/usd/usdGeom/sphere.h>
@@ -389,6 +390,62 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestKindAuthoring) {
389390
EXPECT_PRIM_KIND(stage, "/test/root/tet", pxr::KindTokens->subcomponent);
390391
}
391392

393+
TEST_F(MjcfSdfFileFormatPluginTest, TestGeomsPrims) {
394+
static constexpr char kXml[] = R"(
395+
<mujoco model="test">
396+
<worldbody>
397+
<geom type="plane" name="plane_geom" size="10 20 0.1"/>
398+
<geom type="box" name="box_geom" size="10 20 30"/>
399+
<geom type="sphere" name="sphere_geom" size="10 20 30"/>
400+
<geom type="capsule" name="capsule_geom" size="10 20 30"/>
401+
<geom type="cylinder" name="cylinder_geom" size="10 20 30"/>
402+
<geom type="ellipsoid" name="ellipsoid_geom" size="10 20 30"/>
403+
</worldbody>
404+
</mujoco>
405+
)";
406+
407+
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
408+
auto stage = pxr::UsdStage::Open(layer);
409+
410+
// Note that all sizes are multiplied by 2 because Mujoco uses half sizes.
411+
412+
// Plane
413+
EXPECT_PRIM_VALID(stage, "/test/plane_geom");
414+
EXPECT_PRIM_IS_A(stage, "/test/plane_geom", pxr::UsdGeomPlane);
415+
ExpectAttributeEqual(stage, "/test/plane_geom.width", 2 * 10.0);
416+
ExpectAttributeEqual(stage, "/test/plane_geom.length", 2 * 20.0);
417+
// Box
418+
EXPECT_PRIM_VALID(stage, "/test/box_geom");
419+
EXPECT_PRIM_IS_A(stage, "/test/box_geom", pxr::UsdGeomCube);
420+
// Box is a special case, it uses a UsdGeomCube and scales it with
421+
// xformOp:scale. The radius is always set to 2.
422+
ExpectAttributeEqual(stage, "/test/box_geom.size", 2.0);
423+
ExpectAttributeEqual(stage, "/test/box_geom.xformOp:scale",
424+
pxr::GfVec3f(10.0, 20.0, 30.0));
425+
// Sphere
426+
EXPECT_PRIM_VALID(stage, "/test/sphere_geom");
427+
EXPECT_PRIM_IS_A(stage, "/test/sphere_geom", pxr::UsdGeomSphere);
428+
ExpectAttributeEqual(stage, "/test/sphere_geom.radius", 2 * 10.0);
429+
// Capsule
430+
EXPECT_PRIM_VALID(stage, "/test/capsule_geom");
431+
EXPECT_PRIM_IS_A(stage, "/test/capsule_geom", pxr::UsdGeomCapsule);
432+
ExpectAttributeEqual(stage, "/test/capsule_geom.radius", 2 * 10.0);
433+
ExpectAttributeEqual(stage, "/test/capsule_geom.height", 2 * 20.0);
434+
// Cylinder
435+
EXPECT_PRIM_VALID(stage, "/test/cylinder_geom");
436+
EXPECT_PRIM_IS_A(stage, "/test/cylinder_geom", pxr::UsdGeomCylinder);
437+
ExpectAttributeEqual(stage, "/test/cylinder_geom.radius", 2 * 10.0);
438+
ExpectAttributeEqual(stage, "/test/cylinder_geom.height", 2 * 20.0);
439+
// Ellipsoid
440+
EXPECT_PRIM_VALID(stage, "/test/ellipsoid_geom");
441+
// Ellipsoid is a special case, it uses a UsdGeomSphere and scales it with
442+
// xformOp:scale. The radius is always set to 1.
443+
EXPECT_PRIM_IS_A(stage, "/test/ellipsoid_geom", pxr::UsdGeomSphere);
444+
ExpectAttributeEqual(stage, "/test/ellipsoid_geom.radius", 1.0);
445+
ExpectAttributeEqual(stage, "/test/ellipsoid_geom.xformOp:scale",
446+
pxr::GfVec3f(2.0 * 10.0, 2.0 * 20.0, 2.0 * 30.0));
447+
}
448+
392449
static constexpr char kSiteXml[] = R"(
393450
<mujoco model="test">
394451
<worldbody>
@@ -435,6 +492,7 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestSitePrimsPurpose) {
435492
EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/ellipsoid_site",
436493
pxr::UsdGeomTokens->guide);
437494
}
495+
438496
TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsToggleSdfFormatArg) {
439497
std::string xml_path = GetTestDataFilePath(kMeshObjPath);
440498

0 commit comments

Comments
 (0)