Add basic site support to Mjcf SdfFileFormat plugin.
We will still need to tag these with a custom API schema to ease round-tripping but that will follow creation of such a schema. PiperOrigin-RevId: 746421793 Change-Id: I2df2209b4bc90d763718fb4bea8158b8598755c6
This commit is contained in:
committed by
Copybara-Service
parent
440ce5eb7a
commit
446a8f1742
@@ -122,6 +122,7 @@ using mujoco::usd::SetAttributeMetadata;
|
||||
using mujoco::usd::SetLayerMetadata;
|
||||
using mujoco::usd::SetPrimKind;
|
||||
using mujoco::usd::SetPrimMetadata;
|
||||
using mujoco::usd::SetPrimPurpose;
|
||||
|
||||
pxr::GfMatrix4d MujocoPosQuatToTransform(double *pos, double *quat) {
|
||||
pxr::GfQuatd quaternion = pxr::GfQuatd::GetIdentity();
|
||||
@@ -598,34 +599,57 @@ class ModelWriter {
|
||||
return subcomponent_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteBoxGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath WriteSiteGeom(const mjsSite *site,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name =
|
||||
GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Cube, body_path);
|
||||
GetAvailablePrimName(*site->name, pxr::UsdGeomTokens->Cube, body_path);
|
||||
|
||||
int site_idx = mjs_getId(site->element);
|
||||
const mjtNum *size = &model_->site_size[site_idx * 3];
|
||||
pxr::SdfPath site_path;
|
||||
switch (site->type) {
|
||||
case mjGEOM_BOX:
|
||||
site_path = WriteBox(name, size, body_path);
|
||||
break;
|
||||
case mjGEOM_SPHERE:
|
||||
site_path = WriteSphere(name, size, body_path);
|
||||
break;
|
||||
case mjGEOM_CAPSULE:
|
||||
site_path = WriteCapsule(name, size, body_path);
|
||||
break;
|
||||
case mjGEOM_CYLINDER:
|
||||
site_path = WriteCylinder(name, size, body_path);
|
||||
break;
|
||||
case mjGEOM_ELLIPSOID:
|
||||
site_path = WriteEllipsoid(name, size, body_path);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return site_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteBox(const pxr::TfToken &name, const mjtNum *size,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath box_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Cube);
|
||||
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
|
||||
// MuJoCo uses half sizes.
|
||||
pxr::SdfPath size_attr_path =
|
||||
CreateAttributeSpec(data_, box_path, pxr::UsdGeomTokens->size,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
pxr::GfVec3f scale(static_cast<float>(geom_size[0]),
|
||||
static_cast<float>(geom_size[1]),
|
||||
static_cast<float>(geom_size[2]));
|
||||
pxr::GfVec3f scale(static_cast<float>(size[0]), static_cast<float>(size[1]),
|
||||
static_cast<float>(size[2]));
|
||||
SetAttributeDefault(data_, size_attr_path, 2.0);
|
||||
|
||||
pxr::SdfPath extent_attr_path =
|
||||
CreateAttributeSpec(data_, box_path, pxr::UsdGeomTokens->extent,
|
||||
pxr::SdfValueTypeNames->Float3Array);
|
||||
SetAttributeDefault(
|
||||
data_, extent_attr_path,
|
||||
pxr::VtArray<pxr::GfVec3f>({
|
||||
pxr::GfVec3f(-geom_size[0], -geom_size[1], -geom_size[2]),
|
||||
pxr::GfVec3f(geom_size[0], geom_size[1], geom_size[2]),
|
||||
}));
|
||||
SetAttributeDefault(data_, extent_attr_path,
|
||||
pxr::VtArray<pxr::GfVec3f>({
|
||||
pxr::GfVec3f(-size[0], -size[1], -size[2]),
|
||||
pxr::GfVec3f(size[0], size[1], size[2]),
|
||||
}));
|
||||
|
||||
WriteScaleXformOp(box_path, scale);
|
||||
WriteXformOpOrder(box_path,
|
||||
@@ -633,65 +657,80 @@ class ModelWriter {
|
||||
return box_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteCapsuleGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name = GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Capsule,
|
||||
body_path);
|
||||
pxr::SdfPath capsule_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Capsule);
|
||||
pxr::SdfPath WriteBoxGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name =
|
||||
GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Cube, body_path);
|
||||
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
return WriteBox(name, geom_size, body_path);
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteCapsule(const pxr::TfToken name, const mjtNum *size,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath capsule_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Capsule);
|
||||
|
||||
// MuJoCo uses half sizes.
|
||||
pxr::SdfPath radius_attr_path =
|
||||
CreateAttributeSpec(data_, capsule_path, pxr::UsdGeomTokens->radius,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, radius_attr_path, geom_size[0] * 2);
|
||||
SetAttributeDefault(data_, radius_attr_path, size[0] * 2);
|
||||
|
||||
pxr::SdfPath height_attr_path =
|
||||
CreateAttributeSpec(data_, capsule_path, pxr::UsdGeomTokens->height,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, height_attr_path, geom_size[1] * 2);
|
||||
SetAttributeDefault(data_, height_attr_path, size[1] * 2);
|
||||
return capsule_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteCapsuleGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name = GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Capsule,
|
||||
body_path);
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
|
||||
return WriteCapsule(name, geom_size, body_path);
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteCylinder(const pxr::TfToken name, const mjtNum *size,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath cylinder_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Cylinder);
|
||||
|
||||
// MuJoCo uses half sizes.
|
||||
pxr::SdfPath radius_attr_path =
|
||||
CreateAttributeSpec(data_, cylinder_path, pxr::UsdGeomTokens->radius,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, radius_attr_path, size[0] * 2);
|
||||
|
||||
pxr::SdfPath height_attr_path =
|
||||
CreateAttributeSpec(data_, cylinder_path, pxr::UsdGeomTokens->height,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, height_attr_path, size[1] * 2);
|
||||
return cylinder_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteCylinderGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name = GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Cylinder,
|
||||
body_path);
|
||||
pxr::SdfPath cylinder_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Cylinder);
|
||||
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
|
||||
// MuJoCo uses half sizes.
|
||||
pxr::SdfPath radius_attr_path =
|
||||
CreateAttributeSpec(data_, cylinder_path, pxr::UsdGeomTokens->radius,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, radius_attr_path, geom_size[0] * 2);
|
||||
|
||||
pxr::SdfPath height_attr_path =
|
||||
CreateAttributeSpec(data_, cylinder_path, pxr::UsdGeomTokens->height,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, height_attr_path, geom_size[1] * 2);
|
||||
return cylinder_path;
|
||||
return WriteCylinder(name, geom_size, body_path);
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteEllipsoidGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name = GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Sphere,
|
||||
body_path);
|
||||
pxr::SdfPath WriteEllipsoid(const pxr::TfToken name, const mjtNum *size,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath ellipsoid_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Sphere);
|
||||
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
|
||||
pxr::GfVec3f scale = {static_cast<float>(geom_size[0] * 2),
|
||||
static_cast<float>(geom_size[1] * 2),
|
||||
static_cast<float>(geom_size[2] * 2)};
|
||||
pxr::GfVec3f scale = {static_cast<float>(size[0] * 2),
|
||||
static_cast<float>(size[1] * 2),
|
||||
static_cast<float>(size[2] * 2)};
|
||||
|
||||
// MuJoCo uses half sizes.
|
||||
pxr::SdfPath radius_attr_path =
|
||||
@@ -705,28 +744,61 @@ class ModelWriter {
|
||||
return ellipsoid_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteSphereGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath WriteEllipsoidGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name = GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Sphere,
|
||||
body_path);
|
||||
pxr::SdfPath sphere_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Sphere);
|
||||
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
|
||||
return WriteEllipsoid(name, geom_size, body_path);
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteSphere(const pxr::TfToken name, const mjtNum *size,
|
||||
const pxr::SdfPath &body_path) {
|
||||
pxr::SdfPath sphere_path =
|
||||
CreatePrimSpec(data_, body_path, name, pxr::UsdGeomTokens->Sphere);
|
||||
|
||||
// MuJoCo uses half sizes.
|
||||
pxr::SdfPath radius_attr_path =
|
||||
CreateAttributeSpec(data_, sphere_path, pxr::UsdGeomTokens->radius,
|
||||
pxr::SdfValueTypeNames->Float);
|
||||
SetAttributeDefault(data_, radius_attr_path, geom_size[0] * 2);
|
||||
SetAttributeDefault(data_, radius_attr_path, size[0] * 2);
|
||||
return sphere_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath WriteSphereGeom(const mjsGeom *geom,
|
||||
const pxr::SdfPath &body_path) {
|
||||
auto name = GetAvailablePrimName(*geom->name, pxr::UsdGeomTokens->Sphere,
|
||||
body_path);
|
||||
int geom_idx = mjs_getId(geom->element);
|
||||
mjtNum *geom_size = &model_->geom_size[geom_idx * 3];
|
||||
return WriteSphere(name, geom_size, body_path);
|
||||
}
|
||||
|
||||
void WriteSite(mjsSite *site, const mjsBody *body) {
|
||||
const int body_id = mjs_getId(body->element);
|
||||
const auto &body_path = body_paths_[body_id];
|
||||
auto name =
|
||||
GetAvailablePrimName(*site->name, pxr::UsdGeomTokens->Xform, body_path);
|
||||
|
||||
// Create a geom primitive and set its purpose to guide so it won't be
|
||||
// rendered.
|
||||
pxr::SdfPath site_path = WriteSiteGeom(site, body_path);
|
||||
SetPrimPurpose(data_, site_path, pxr::UsdGeomTokens->guide);
|
||||
|
||||
int site_id = mjs_getId(site->element);
|
||||
auto transform = MujocoPosQuatToTransform(&model_->site_pos[3 * site_id],
|
||||
&model_->site_quat[4 * site_id]);
|
||||
WriteTransformXformOp(site_path, transform);
|
||||
|
||||
PrependToXformOpOrder(
|
||||
site_path, pxr::VtArray<pxr::TfToken>{kTokens->xformOpTransform});
|
||||
}
|
||||
|
||||
void WriteGeom(mjsGeom *geom, const mjsBody *body) {
|
||||
const int body_id = mjs_getId(body->element);
|
||||
const auto &body_path = body_paths_[body_id];
|
||||
auto name = GetAvailablePrimName(*geom->name, kTokens->geom, body_path);
|
||||
|
||||
pxr::SdfPath geom_path;
|
||||
int geom_id = mjs_getId(geom->element);
|
||||
@@ -798,6 +870,14 @@ class ModelWriter {
|
||||
geom_path, pxr::VtArray<pxr::TfToken>{kTokens->xformOpTransform});
|
||||
}
|
||||
|
||||
void WriteSites(mjsBody *body) {
|
||||
mjsSite *site = mjs_asSite(mjs_firstChild(body, mjOBJ_SITE, false));
|
||||
while (site) {
|
||||
WriteSite(site, body);
|
||||
site = mjs_asSite(mjs_nextChild(body, site->element, false));
|
||||
}
|
||||
}
|
||||
|
||||
void WriteGeoms(mjsBody *body) {
|
||||
mjsGeom *geom = mjs_asGeom(mjs_firstChild(body, mjOBJ_GEOM, false));
|
||||
while (geom) {
|
||||
@@ -957,6 +1037,7 @@ class ModelWriter {
|
||||
if (mjs_getId(body->element) != kWorldIndex) {
|
||||
WriteBody(body);
|
||||
}
|
||||
WriteSites(body);
|
||||
WriteGeoms(body);
|
||||
WriteCameras(body);
|
||||
WriteLights(body);
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
#include <pxr/usd/usd/tokens.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
@@ -195,5 +196,13 @@ void SetPrimKind(pxr::SdfAbstractDataRefPtr& data,
|
||||
SetPrimMetadata(data, prim_path, pxr::TfToken("kind"), kind);
|
||||
}
|
||||
|
||||
void SetPrimPurpose(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path, pxr::TfToken purpose) {
|
||||
const pxr::SdfPath attr_path = CreateAttributeSpec(
|
||||
data, prim_path, pxr::UsdGeomTokens->purpose,
|
||||
pxr::SdfValueTypeNames->Token, pxr::SdfVariabilityUniform);
|
||||
SetAttributeDefault(data, attr_path, purpose);
|
||||
}
|
||||
|
||||
} // namespace usd
|
||||
} // namespace mujoco
|
||||
|
||||
@@ -71,6 +71,9 @@ void ApplyApiSchema(pxr::SdfAbstractDataRefPtr& data,
|
||||
void SetPrimKind(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path, pxr::TfToken kind);
|
||||
|
||||
void SetPrimPurpose(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path, pxr::TfToken purpose);
|
||||
|
||||
// Set the value specified by key on any field at field_path.
|
||||
template <typename T>
|
||||
void SetField(pxr::SdfAbstractDataRefPtr& data, const pxr::SdfPath& field_path,
|
||||
|
||||
@@ -29,12 +29,26 @@
|
||||
#define EXPECT_PRIM_VALID(stage, path) \
|
||||
EXPECT_TRUE((stage)->GetPrimAtPath(SdfPath(path)).IsValid());
|
||||
|
||||
#define EXPECT_PRIM_IS_A(stage, path, type) \
|
||||
{ \
|
||||
EXPECT_TRUE((stage)->GetPrimAtPath(SdfPath(path)).IsA<type>()); \
|
||||
}
|
||||
|
||||
#define EXPECT_PRIM_KIND(stage, path, kind) \
|
||||
{ \
|
||||
pxr::TfToken prim_kind; \
|
||||
pxr::UsdModelAPI::Get(stage, SdfPath(path)).GetKind(&prim_kind); \
|
||||
EXPECT_EQ(kind, prim_kind); \
|
||||
}
|
||||
|
||||
#define EXPECT_PRIM_PURPOSE(stage, path, purpose) \
|
||||
{ \
|
||||
pxr::TfToken prim_purpose; \
|
||||
pxr::UsdGeomImageable::Get(stage, SdfPath(path)) \
|
||||
.GetPurposeAttr() \
|
||||
.Get(&prim_purpose); \
|
||||
EXPECT_EQ(prim_purpose, purpose); \
|
||||
}
|
||||
namespace mujoco {
|
||||
|
||||
pxr::SdfLayerRefPtr LoadLayer(const std::string& xml);
|
||||
|
||||
@@ -36,9 +36,14 @@
|
||||
#include <pxr/usd/usd/modelAPI.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usdGeom/capsule.h>
|
||||
#include <pxr/usd/usdGeom/cube.h>
|
||||
#include <pxr/usd/usdGeom/cylinder.h>
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
#include <pxr/usd/usdGeom/primvar.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
#include <pxr/usd/usdGeom/sphere.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
|
||||
PXR_NAMESPACE_OPEN_SCOPE
|
||||
// clang-format off
|
||||
@@ -383,5 +388,52 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestKindAuthoring) {
|
||||
EXPECT_PRIM_KIND(stage, "/test/root/tet", pxr::KindTokens->subcomponent);
|
||||
}
|
||||
|
||||
static constexpr char kSiteXml[] = R"(
|
||||
<mujoco model="test">
|
||||
<worldbody>
|
||||
<site type="box" name="box_site"/>
|
||||
<body name="ball">
|
||||
<site type="sphere" name="sphere_site"/>
|
||||
<site type="capsule" name="capsule_site"/>
|
||||
<site type="cylinder" name="cylinder_site"/>
|
||||
<site type="ellipsoid" name="ellipsoid_site"/>
|
||||
<geom type="sphere" size="1 1 1"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestSitePrimsAuthored) {
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kSiteXml);
|
||||
|
||||
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_VALID(stage, "/test/ball/ball/sphere_site");
|
||||
EXPECT_PRIM_IS_A(stage, "/test/ball/ball/sphere_site", pxr::UsdGeomSphere);
|
||||
EXPECT_PRIM_VALID(stage, "/test/ball/ball/capsule_site");
|
||||
EXPECT_PRIM_IS_A(stage, "/test/ball/ball/capsule_site", pxr::UsdGeomCapsule);
|
||||
EXPECT_PRIM_VALID(stage, "/test/ball/ball/cylinder_site");
|
||||
EXPECT_PRIM_IS_A(stage, "/test/ball/ball/cylinder_site",
|
||||
pxr::UsdGeomCylinder);
|
||||
EXPECT_PRIM_VALID(stage, "/test/ball/ball/ellipsoid_site");
|
||||
EXPECT_PRIM_IS_A(stage, "/test/ball/ball/ellipsoid_site", pxr::UsdGeomSphere);
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestSitePrimsPurpose) {
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kSiteXml);
|
||||
|
||||
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",
|
||||
pxr::UsdGeomTokens->guide);
|
||||
EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/capsule_site",
|
||||
pxr::UsdGeomTokens->guide);
|
||||
EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/cylinder_site",
|
||||
pxr::UsdGeomTokens->guide);
|
||||
EXPECT_PRIM_PURPOSE(stage, "/test/ball/ball/ellipsoid_site",
|
||||
pxr::UsdGeomTokens->guide);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
|
||||
Reference in New Issue
Block a user