Add mujoco/experimental and move USD work there.
PiperOrigin-RevId: 740730698 Change-Id: If9c68798cda502d6d7632906a034437e2557802e
This commit is contained in:
committed by
Copybara-Service
parent
5c955b8fe2
commit
644b42bee3
@@ -0,0 +1,201 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mjcf/mjcf_file_format.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include "mjcf/mujoco_to_usd.h"
|
||||
#include <pxr/base/tf/diagnostic.h>
|
||||
#include <pxr/base/tf/enum.h>
|
||||
#include <pxr/base/tf/pathUtils.h>
|
||||
#include <pxr/base/tf/registryManager.h>
|
||||
#include <pxr/base/tf/staticTokens.h>
|
||||
#include <pxr/base/tf/type.h>
|
||||
#include <pxr/base/work/loops.h>
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/ar/asset.h>
|
||||
#include <pxr/usd/ar/resolvedPath.h>
|
||||
#include <pxr/usd/ar/resolver.h>
|
||||
#include <pxr/usd/sdf/declareHandles.h>
|
||||
#include <pxr/usd/sdf/fileFormat.h>
|
||||
#include <pxr/usd/sdf/layer.h>
|
||||
#include <pxr/usd/usd/usdaFileFormat.h>
|
||||
#include "tinyxml2.h"
|
||||
|
||||
PXR_NAMESPACE_OPEN_SCOPE
|
||||
|
||||
TF_DEFINE_PUBLIC_TOKENS(UsdMjcfFileFormatTokens, USD_MJCF_FILE_FORMAT_TOKENS);
|
||||
|
||||
TF_REGISTRY_FUNCTION(TfType) {
|
||||
SDF_DEFINE_FILE_FORMAT(UsdMjcfFileFormat, SdfFileFormat);
|
||||
}
|
||||
|
||||
enum ErrorCodes { XmlParsingError };
|
||||
TF_REGISTRY_FUNCTION(TfEnum) {
|
||||
TF_ADD_ENUM_NAME(XmlParsingError, "Error when parsing XML.");
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
void ResolveMjcfDependencies(const std::string &xml_string,
|
||||
const std::string &resolved_path);
|
||||
|
||||
void AccumulateFilesRecursive(std::unordered_set<std::string> &files,
|
||||
tinyxml2::XMLElement *elem,
|
||||
const std::string &resolved_path) {
|
||||
// get filename
|
||||
const char *file = elem->Attribute("file");
|
||||
|
||||
if (file != nullptr) {
|
||||
auto identifier = pxr::ArGetResolver().CreateIdentifier(
|
||||
std::string(file), pxr::ArResolvedPath(resolved_path));
|
||||
if (!strcasecmp(elem->Value(), "include") ||
|
||||
!strcasecmp(elem->Value(), "model")) {
|
||||
auto include_resolved_path = pxr::ArGetResolver().Resolve(identifier);
|
||||
auto asset = pxr::ArGetResolver().OpenAsset(include_resolved_path);
|
||||
ResolveMjcfDependencies(asset->GetBuffer().get(), include_resolved_path);
|
||||
|
||||
// Neither of these elements should have children.
|
||||
return;
|
||||
}
|
||||
|
||||
files.insert(identifier);
|
||||
}
|
||||
|
||||
if (!strcasecmp(elem->Value(), "texture")) {
|
||||
static const char *attributes[] = {"fileright", "fileup", "fileleft",
|
||||
"filedown", "filefront", "fileback"};
|
||||
for (const auto &attribute : attributes) {
|
||||
const char *file = elem->Attribute(attribute);
|
||||
if (file != nullptr) {
|
||||
auto identifier = pxr::ArGetResolver().CreateIdentifier(
|
||||
std::string(file), pxr::ArResolvedPath(resolved_path));
|
||||
files.insert(identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tinyxml2::XMLElement *child = elem->FirstChildElement();
|
||||
for (; child; child = child->NextSiblingElement()) {
|
||||
AccumulateFilesRecursive(files, child, resolved_path);
|
||||
}
|
||||
}
|
||||
|
||||
void ResolveMjcfDependencies(const std::string &xml_string,
|
||||
const std::string &resolved_path) {
|
||||
// load XML file or parse string
|
||||
tinyxml2::XMLDocument doc;
|
||||
doc.Parse(xml_string.c_str());
|
||||
|
||||
// error checking
|
||||
if (doc.Error()) {
|
||||
TF_ERROR(XmlParsingError, "%d:\n%s\n", doc.ErrorID(), doc.ErrorStr());
|
||||
return;
|
||||
}
|
||||
|
||||
// get top-level element
|
||||
tinyxml2::XMLElement *root = doc.RootElement();
|
||||
if (!root) {
|
||||
TF_ERROR(XmlParsingError, "XML root element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Accumulate file dependencies.
|
||||
std::unordered_set<std::string> files = {};
|
||||
AccumulateFilesRecursive(files, root, resolved_path);
|
||||
|
||||
auto open_asset = [resolved_path](const std::string &identifier) {
|
||||
pxr::ArGetResolver().OpenAsset(pxr::ArGetResolver().Resolve(identifier));
|
||||
};
|
||||
// Open all assets in parallel.
|
||||
pxr::WorkParallelForEach(files.begin(), files.end(), open_asset);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
UsdMjcfFileFormat::UsdMjcfFileFormat()
|
||||
: SdfFileFormat(
|
||||
UsdMjcfFileFormatTokens->Id, UsdMjcfFileFormatTokens->Version,
|
||||
UsdMjcfFileFormatTokens->Target, UsdMjcfFileFormatTokens->Id) {}
|
||||
|
||||
UsdMjcfFileFormat::~UsdMjcfFileFormat() {}
|
||||
|
||||
bool UsdMjcfFileFormat::CanRead(const std::string &filePath) const {
|
||||
auto extension = pxr::TfGetExtension(filePath);
|
||||
if (extension.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return extension == this->GetFormatId();
|
||||
}
|
||||
|
||||
bool UsdMjcfFileFormat::ReadImpl(pxr::SdfLayer *layer, mjSpec *spec) const {
|
||||
auto data = InitData(layer->GetFileFormatArguments());
|
||||
|
||||
auto success = mujoco::usd::WriteSpecToData(spec, data);
|
||||
mj_deleteSpec(spec);
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_SetLayerData(layer, data);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UsdMjcfFileFormat::ReadFromString(pxr::SdfLayer *layer,
|
||||
const std::string &str) const {
|
||||
std::array<char, 1024> error;
|
||||
mjSpec *spec =
|
||||
mj_parseXMLString(str.c_str(), nullptr, error.data(), error.size());
|
||||
if (spec == nullptr) {
|
||||
TF_WARN(XmlParsingError, "%s", error.data());
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReadImpl(layer, spec);
|
||||
}
|
||||
|
||||
bool UsdMjcfFileFormat::Read(pxr::SdfLayer *layer,
|
||||
const std::string &resolved_path,
|
||||
bool metadata_only) const {
|
||||
// Resolved all dependencies so that they are accessible when parsing
|
||||
// the XML.
|
||||
std::shared_ptr<pxr::ArAsset> asset =
|
||||
pxr::ArGetResolver().OpenAsset(pxr::ArResolvedPath(resolved_path));
|
||||
auto buffer = asset->GetBuffer();
|
||||
ResolveMjcfDependencies(buffer.get(), resolved_path);
|
||||
|
||||
// Parse to USD.
|
||||
std::array<char, 1024> error;
|
||||
mjSpec *spec =
|
||||
mj_parseXML(resolved_path.c_str(), nullptr, error.data(), error.size());
|
||||
if (spec == nullptr) {
|
||||
TF_WARN(XmlParsingError, "%s", error.data());
|
||||
return false;
|
||||
}
|
||||
return ReadImpl(layer, spec);
|
||||
}
|
||||
|
||||
bool UsdMjcfFileFormat::WriteToString(const SdfLayer &layer, std::string *str,
|
||||
const std::string &comment) const {
|
||||
return SdfFileFormat::FindById(pxr::UsdUsdaFileFormatTokens->Id)
|
||||
->WriteToString(layer, str, comment);
|
||||
}
|
||||
|
||||
PXR_NAMESPACE_CLOSE_SCOPE
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_MJCF_FILE_FORMAT_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_MJCF_FILE_FORMAT_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include <pxr/base/tf/declarePtrs.h>
|
||||
#include <pxr/base/tf/staticTokens.h>
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/sdf/declareHandles.h>
|
||||
#include <pxr/usd/sdf/fileFormat.h>
|
||||
#include <pxr/usd/usd/api.h>
|
||||
|
||||
PXR_NAMESPACE_OPEN_SCOPE
|
||||
|
||||
// The Id should realistically be mjcf, but the id and extension need to match.
|
||||
// So near term it just assumes the only .xml file we would import is MJCF.
|
||||
#define USD_MJCF_FILE_FORMAT_TOKENS \
|
||||
((Id, "xml"))((Version, "1.0"))((Target, "usd"))
|
||||
|
||||
TF_DECLARE_PUBLIC_TOKENS(UsdMjcfFileFormatTokens, USD_MJCF_FILE_FORMAT_TOKENS);
|
||||
|
||||
TF_DECLARE_WEAK_AND_REF_PTRS(UsdMjcfFileFormat);
|
||||
|
||||
class UsdMjcfFileFormat : public SdfFileFormat {
|
||||
public:
|
||||
using SdfFileFormat::FileFormatArguments;
|
||||
|
||||
// Returns true if 'file' can be read by this format plugin.
|
||||
USD_API
|
||||
bool CanRead(const std::string &file) const override;
|
||||
|
||||
// Reads scene description from the asset specified by resolved_path into
|
||||
// 'layer'.
|
||||
//
|
||||
// metadataOnly is a flag that asks for only the layer metadata to be read in,
|
||||
// which can be much faster if that is all that is required but currently we
|
||||
// ignore it.
|
||||
//
|
||||
// Returns true if the asset is successfully read into layer, false otherwise.
|
||||
USD_API
|
||||
bool Read(pxr::SdfLayer *layer, const std::string &resolved_path,
|
||||
bool metadata_only) const override;
|
||||
|
||||
// Reads data in the string 'str' into 'layer'.
|
||||
//
|
||||
// If the file is successfully read, this method returns true. Otherwise,
|
||||
// false is returned and errors are posted.
|
||||
USD_API
|
||||
bool ReadFromString(SdfLayer *layer, const std::string &str) const override;
|
||||
|
||||
// Writes the contents in 'layer' to 'str'. This just forwards to the usda
|
||||
// implementation.
|
||||
USD_API
|
||||
bool WriteToString(const SdfLayer &layer, std::string *str,
|
||||
const std::string &comment) const override;
|
||||
|
||||
protected:
|
||||
SDF_FILE_FORMAT_FACTORY_ACCESS;
|
||||
|
||||
UsdMjcfFileFormat();
|
||||
virtual ~UsdMjcfFileFormat();
|
||||
|
||||
private:
|
||||
// Function delegated to by Read and ReadFromString.
|
||||
bool ReadImpl(SdfLayer *layer, mjSpec *spec) const;
|
||||
};
|
||||
|
||||
PXR_NAMESPACE_CLOSE_SCOPE
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_MJCF_FILE_FORMAT_H_
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_MUJOCO_TO_USD_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_MUJOCO_TO_USD_H_
|
||||
|
||||
#include <mujoco/mujoco.h>
|
||||
#include <pxr/usd/sdf/abstractData.h>
|
||||
|
||||
namespace mujoco {
|
||||
namespace usd {
|
||||
// Given an mjSpec, write it to a SdfAbstractData.
|
||||
//
|
||||
// Args:
|
||||
// spec: mjSpec built programmatically or via parsed XML.
|
||||
// data: SdfAbstractDataRefPtr that will be written to.
|
||||
bool WriteSpecToData(mjSpec* spec, pxr::SdfAbstractDataRefPtr& data);
|
||||
} // namespace usd
|
||||
} // namespace mujoco
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_MUJOCO_TO_USD_H_
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"Plugins": [
|
||||
{
|
||||
"Info": {
|
||||
"Types": {
|
||||
"UsdMjcfFileFormat": {
|
||||
"bases": ["SdfFileFormat"],
|
||||
"displayName": "MJCF USD File Format",
|
||||
"extensions": ["xml"],
|
||||
"formatId": "xml",
|
||||
"primary": true,
|
||||
"supportsReading": true,
|
||||
"supportsWriting": false,
|
||||
"target": "usd"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LibraryPath": "",
|
||||
"Name": "usdMjcf",
|
||||
"ResourcePath": "",
|
||||
"Root": ".",
|
||||
"Type": "library"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "mjcf/utils.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/usd/sdf/abstractData.h>
|
||||
#include <pxr/usd/sdf/listOp.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/sdf/reference.h>
|
||||
#include <pxr/usd/sdf/schema.h>
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
#include <pxr/usd/usd/tokens.h>
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
void AppendChild(pxr::SdfAbstractDataRefPtr& data, const pxr::SdfPath& specPath,
|
||||
const pxr::TfToken& childKey, const T& child) {
|
||||
// Get existing children.
|
||||
std::vector<T> children;
|
||||
pxr::SdfAbstractDataTypedValue<std::vector<T>> getter(&children);
|
||||
data->Has(specPath, childKey, &getter);
|
||||
|
||||
children.push_back(child);
|
||||
data->Set(specPath, childKey,
|
||||
pxr::SdfAbstractDataConstTypedValue<std::vector<T>>(&children));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AppendListOp(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& spec_path, const pxr::TfToken& field,
|
||||
const T& item) {
|
||||
// Get existing list op.
|
||||
pxr::SdfListOp<T> list_op;
|
||||
pxr::SdfAbstractDataTypedValue<pxr::SdfListOp<T>> getter(&list_op);
|
||||
data->Has(spec_path, field, &getter);
|
||||
|
||||
auto items = list_op.GetExplicitItems();
|
||||
items.push_back(item);
|
||||
list_op.SetExplicitItems(items);
|
||||
data->Set(spec_path, field,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::SdfListOp<T>>(&list_op));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PrependListOp(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& spec_path, const pxr::TfToken& field,
|
||||
const T& item) {
|
||||
// Get existing list op.
|
||||
pxr::SdfListOp<T> listOp;
|
||||
pxr::SdfAbstractDataTypedValue<pxr::SdfListOp<T>> getter(&listOp);
|
||||
data->Has(spec_path, field, &getter);
|
||||
|
||||
auto prependedItems = listOp.GetPrependedItems();
|
||||
prependedItems.insert(prependedItems.begin(), item);
|
||||
listOp.SetPrependedItems(prependedItems);
|
||||
data->Set(spec_path, field,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::SdfListOp<T>>(&listOp));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace mujoco {
|
||||
namespace usd {
|
||||
|
||||
pxr::SdfPath CreatePrimSpec(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& parent_path,
|
||||
const pxr::TfToken& name, const pxr::TfToken& type,
|
||||
pxr::SdfSpecifier specifier) {
|
||||
const pxr::SdfPath prim_path = parent_path.AppendChild(name);
|
||||
data->CreateSpec(prim_path, pxr::SdfSpecTypePrim);
|
||||
data->Set(prim_path, pxr::SdfFieldKeys->Specifier,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::SdfSpecifier>(&specifier));
|
||||
if (!type.IsEmpty()) {
|
||||
data->Set(prim_path, pxr::SdfFieldKeys->TypeName,
|
||||
pxr::SdfAbstractDataConstTypedValue<const pxr::TfToken>(&type));
|
||||
}
|
||||
|
||||
AppendChild(data, parent_path, pxr::SdfChildrenKeys->PrimChildren, name);
|
||||
|
||||
return prim_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath CreateAttributeSpec(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& name,
|
||||
const pxr::SdfValueTypeName& type_name,
|
||||
pxr::SdfVariability variability) {
|
||||
const pxr::SdfPath propertyPath = prim_path.AppendProperty(name);
|
||||
data->CreateSpec(propertyPath, pxr::SdfSpecTypeAttribute);
|
||||
|
||||
pxr::TfToken typeNameToken = type_name.GetAsToken();
|
||||
data->Set(propertyPath, pxr::SdfFieldKeys->TypeName,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::TfToken>(&typeNameToken));
|
||||
if (variability != pxr::SdfVariabilityVarying) {
|
||||
data->Set(
|
||||
propertyPath, pxr::SdfFieldKeys->Variability,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::SdfVariability>(&variability));
|
||||
}
|
||||
|
||||
AppendChild(data, prim_path, pxr::SdfChildrenKeys->PropertyChildren, name);
|
||||
|
||||
return propertyPath;
|
||||
}
|
||||
|
||||
pxr::SdfPath CreateRelationshipSpec(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& relationship_name,
|
||||
const pxr::SdfPath& relationship_path,
|
||||
pxr::SdfVariability variability) {
|
||||
pxr::SdfPath prop_path = prim_path.AppendProperty(relationship_name);
|
||||
data->CreateSpec(prop_path, pxr::SdfSpecTypeRelationship);
|
||||
if (variability != pxr::SdfVariabilityVarying) {
|
||||
data->Set(
|
||||
prop_path, pxr::SdfFieldKeys->Variability,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::SdfVariability>(&variability));
|
||||
}
|
||||
|
||||
AppendChild(data, prim_path, pxr::SdfChildrenKeys->PropertyChildren,
|
||||
relationship_name);
|
||||
|
||||
AppendChild(data, prop_path, pxr::SdfChildrenKeys->RelationshipTargetChildren,
|
||||
relationship_path);
|
||||
AppendListOp(data, prop_path, pxr::SdfFieldKeys->TargetPaths,
|
||||
relationship_path);
|
||||
|
||||
pxr::SdfPath target_path = prop_path.AppendTarget(relationship_path);
|
||||
data->CreateSpec(target_path, pxr::SdfSpecTypeRelationshipTarget);
|
||||
|
||||
return prop_path;
|
||||
}
|
||||
|
||||
pxr::SdfPath CreateClassSpec(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& class_name) {
|
||||
pxr::SdfPath class_path = prim_path.AppendChild(class_name);
|
||||
pxr::SdfSpecifier class_specifier = pxr::SdfSpecifier::SdfSpecifierClass;
|
||||
data->CreateSpec(class_path, pxr::SdfSpecTypePrim);
|
||||
data->Set(
|
||||
class_path, pxr::SdfFieldKeys->Specifier,
|
||||
pxr::SdfAbstractDataConstTypedValue<pxr::SdfSpecifier>(&class_specifier));
|
||||
|
||||
AppendChild(data, prim_path, pxr::SdfChildrenKeys->PrimChildren, class_name);
|
||||
|
||||
return class_path;
|
||||
}
|
||||
|
||||
void AddAttributeConnection(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& attribute_path,
|
||||
const pxr::SdfPath& target_attribute_path) {
|
||||
AppendChild(data, attribute_path, pxr::SdfChildrenKeys->ConnectionChildren,
|
||||
target_attribute_path);
|
||||
AppendListOp(data, attribute_path, pxr::SdfFieldKeys->ConnectionPaths,
|
||||
target_attribute_path);
|
||||
|
||||
data->CreateSpec(attribute_path.AppendTarget(target_attribute_path),
|
||||
pxr::SdfSpecTypeConnection);
|
||||
}
|
||||
|
||||
void AddPrimReference(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::SdfPath& referenced_prim_path) {
|
||||
PrependListOp(data, prim_path, pxr::SdfFieldKeys->References,
|
||||
pxr::SdfReference("", referenced_prim_path));
|
||||
}
|
||||
|
||||
void AddPrimInherit(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::SdfPath& class_path) {
|
||||
PrependListOp(data, prim_path, pxr::SdfFieldKeys->InheritPaths, class_path);
|
||||
}
|
||||
|
||||
void ApplyApiSchema(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& schema_name) {
|
||||
PrependListOp(data, prim_path, pxr::UsdTokens->apiSchemas, schema_name);
|
||||
}
|
||||
|
||||
void SetPrimKind(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path, pxr::TfToken kind) {
|
||||
SetPrimMetadata(data, prim_path, pxr::TfToken("kind"), kind);
|
||||
}
|
||||
|
||||
} // namespace usd
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,127 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_UTILS_H_
|
||||
#define MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_UTILS_H_
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/imaging/hd/primTypeIndex.h>
|
||||
#include <pxr/usd/sdf/abstractData.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/sdf/schema.h>
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/sdf/valueTypeName.h>
|
||||
|
||||
namespace mujoco {
|
||||
namespace usd {
|
||||
|
||||
// Create a prim spec and append it as a child of parent_path.
|
||||
pxr::SdfPath CreatePrimSpec(
|
||||
pxr::SdfAbstractDataRefPtr& data, const pxr::SdfPath& parent_path,
|
||||
const pxr::TfToken& name, const pxr::TfToken& type = pxr::TfToken(),
|
||||
pxr::SdfSpecifier specifier = pxr::SdfSpecifier::SdfSpecifierDef);
|
||||
|
||||
// Create an attribute spec and append it as a child of parent_path.
|
||||
// By default the attribute will be varying.
|
||||
pxr::SdfPath CreateAttributeSpec(
|
||||
pxr::SdfAbstractDataRefPtr& data, const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& name, const pxr::SdfValueTypeName& type_name,
|
||||
pxr::SdfVariability variability = pxr::SdfVariabilityVarying);
|
||||
|
||||
// Create a relationship spec and append it as a child of prim_path.
|
||||
pxr::SdfPath CreateRelationshipSpec(
|
||||
pxr::SdfAbstractDataRefPtr& data, const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& relationship_name,
|
||||
const pxr::SdfPath& relationship_path,
|
||||
pxr::SdfVariability variability = pxr::SdfVariabilityVarying);
|
||||
|
||||
pxr::SdfPath CreateClassSpec(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& class_name);
|
||||
|
||||
void AddAttributeConnection(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& attribute_path,
|
||||
const pxr::SdfPath& target_attribute_path);
|
||||
|
||||
void AddPrimReference(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::SdfPath& referenced_prim_path);
|
||||
|
||||
void AddPrimInherit(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::SdfPath& class_path);
|
||||
|
||||
void ApplyApiSchema(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path,
|
||||
const pxr::TfToken& schema_name);
|
||||
|
||||
void SetPrimKind(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path, pxr::TfToken kind);
|
||||
|
||||
// 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,
|
||||
const pxr::TfToken key, T&& value) {
|
||||
using Deduced = typename std::remove_reference_t<T>;
|
||||
const auto typed_val = pxr::SdfAbstractDataConstTypedValue<Deduced>(&value);
|
||||
const pxr::SdfAbstractDataConstValue& untyped_val = typed_val;
|
||||
|
||||
data->Set(field_path, key, untyped_val);
|
||||
}
|
||||
|
||||
// Set the value specified by key on an attribute spec at attribute_path.
|
||||
template <typename T>
|
||||
void SetAttribute(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& attribute_path, const pxr::TfToken key,
|
||||
T&& value) {
|
||||
SetField(data, attribute_path, key, value);
|
||||
}
|
||||
|
||||
// Set the value specified by key on a prim spec at prim_path.
|
||||
template <typename T>
|
||||
void SetPrimMetadata(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& prim_path, const pxr::TfToken key,
|
||||
T&& value) {
|
||||
SetAttribute(data, prim_path, key, value);
|
||||
}
|
||||
|
||||
// Set the value specified by key on an attribute spec at attribute_path.
|
||||
template <typename T>
|
||||
void SetAttributeMetadata(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& attribute_path,
|
||||
const pxr::TfToken key, T&& value) {
|
||||
SetAttribute(data, attribute_path, key, value);
|
||||
}
|
||||
|
||||
// Set the default value on an attribute spec at attribute_path.
|
||||
template <typename T>
|
||||
void SetAttributeDefault(pxr::SdfAbstractDataRefPtr& data,
|
||||
const pxr::SdfPath& attribute_path,
|
||||
T&& default_value) {
|
||||
SetAttribute(data, attribute_path, pxr::SdfFieldKeys->Default, default_value);
|
||||
}
|
||||
|
||||
// Set the value specified by key on the root layer.
|
||||
template <typename T>
|
||||
void SetLayerMetadata(pxr::SdfAbstractDataRefPtr& data, const pxr::TfToken& key,
|
||||
T&& value) {
|
||||
SetAttribute(data, pxr::SdfPath::AbsoluteRootPath(), key, value);
|
||||
}
|
||||
|
||||
} // namespace usd
|
||||
} // namespace mujoco
|
||||
|
||||
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_PLUGINS_MJCF_UTILS_H_
|
||||
@@ -0,0 +1,64 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "test/experimental/usd/plugins/mjcf/fixture.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/log/check.h>
|
||||
#include <pxr/usd/sdf/assetPath.h>
|
||||
#include <pxr/usd/sdf/declareHandles.h>
|
||||
#include <pxr/usd/sdf/fileFormat.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
#include <pxr/usd/usd/modelAPI.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
namespace mujoco {
|
||||
|
||||
using pxr::SdfPath;
|
||||
|
||||
pxr::SdfLayerRefPtr LoadLayer(const std::string& xml) {
|
||||
auto layer = pxr::SdfLayer::CreateAnonymous(
|
||||
"test_layer", pxr::SdfFileFormat::FindByExtension("xml"));
|
||||
layer->ImportFromString(xml);
|
||||
EXPECT_THAT(layer, testing::NotNull());
|
||||
return layer;
|
||||
}
|
||||
|
||||
template <>
|
||||
void ExpectAttributeEqual<pxr::SdfAssetPath>(pxr::UsdStageRefPtr stage,
|
||||
const char* path,
|
||||
const pxr::SdfAssetPath& value) {
|
||||
auto attr = stage->GetAttributeAtPath(pxr::SdfPath(path));
|
||||
EXPECT_TRUE(attr.IsValid());
|
||||
pxr::SdfAssetPath attr_value;
|
||||
attr.Get(&attr_value);
|
||||
EXPECT_EQ(attr_value.GetAssetPath(), value.GetAssetPath());
|
||||
}
|
||||
|
||||
void ExpectAttributeHasConnection(pxr::UsdStageRefPtr stage, const char* path,
|
||||
const char* connection_path) {
|
||||
auto attr = stage->GetAttributeAtPath(SdfPath(path));
|
||||
EXPECT_TRUE(attr.IsValid());
|
||||
pxr::SdfPathVector sources;
|
||||
attr.GetConnections(&sources);
|
||||
EXPECT_EQ(sources.size(), 1);
|
||||
EXPECT_EQ(sources[0], SdfPath(connection_path));
|
||||
}
|
||||
//
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef MUJOCO_TEST_EXPERIMENTAL_USD_PLUGINS_MJCF_FIXTURE_H_
|
||||
#define MUJOCO_TEST_EXPERIMENTAL_USD_PLUGINS_MJCF_FIXTURE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include "test/fixture.h"
|
||||
#include <pxr/usd/sdf/assetPath.h>
|
||||
#include <pxr/usd/sdf/declareHandles.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
#include <pxr/usd/usd/modelAPI.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
|
||||
#define EXPECT_PRIM_VALID(stage, path) \
|
||||
EXPECT_TRUE((stage)->GetPrimAtPath(SdfPath(path)).IsValid());
|
||||
|
||||
#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); \
|
||||
}
|
||||
namespace mujoco {
|
||||
|
||||
pxr::SdfLayerRefPtr LoadLayer(const std::string& xml);
|
||||
|
||||
template <typename T>
|
||||
void ExpectAttributeEqual(pxr::UsdStageRefPtr stage, const char* path,
|
||||
const T& value) {
|
||||
auto attr = stage->GetAttributeAtPath(pxr::SdfPath(path));
|
||||
EXPECT_TRUE(attr.IsValid());
|
||||
T attr_value;
|
||||
attr.Get(&attr_value);
|
||||
EXPECT_EQ(attr_value, value);
|
||||
}
|
||||
|
||||
// Specialization for SdfAssetPath, so that we can compare only the asset path
|
||||
// and not care about whatever the resolved path is.
|
||||
// Otherwise the default operator== would fail because it tests for equality of
|
||||
// the asset path AND the resolved path.
|
||||
template <>
|
||||
void ExpectAttributeEqual<pxr::SdfAssetPath>(pxr::UsdStageRefPtr stage,
|
||||
const char* path,
|
||||
const pxr::SdfAssetPath& value);
|
||||
|
||||
void ExpectAttributeHasConnection(pxr::UsdStageRefPtr stage, const char* path,
|
||||
const char* connection_path);
|
||||
|
||||
using MjcfSdfFileFormatPluginTest = MujocoTest;
|
||||
|
||||
} // namespace mujoco
|
||||
#endif // MUJOCO_TEST_EXPERIMENTAL_USD_PLUGINS_MJCF_FIXTURE_H_
|
||||
@@ -0,0 +1,387 @@
|
||||
// Copyright 2025 DeepMind Technologies Limited
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <absl/strings/string_view.h>
|
||||
#include "test/experimental/usd/plugins/mjcf/fixture.h"
|
||||
#include "test/fixture.h"
|
||||
#include <pxr/base/gf/vec2f.h>
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/base/tf/staticData.h>
|
||||
#include <pxr/base/tf/staticTokens.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/base/vt/array.h>
|
||||
#include <pxr/pxr.h>
|
||||
#include <pxr/usd/kind/registry.h>
|
||||
#include <pxr/usd/sdf/assetPath.h>
|
||||
#include <pxr/usd/sdf/declareHandles.h>
|
||||
#include <pxr/usd/sdf/fileFormat.h>
|
||||
#include <pxr/usd/sdf/path.h>
|
||||
#include <pxr/usd/usd/common.h>
|
||||
#include <pxr/usd/usd/modelAPI.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usd/stage.h>
|
||||
#include <pxr/usd/usdGeom/mesh.h>
|
||||
#include <pxr/usd/usdGeom/primvar.h>
|
||||
#include <pxr/usd/usdGeom/primvarsAPI.h>
|
||||
|
||||
PXR_NAMESPACE_OPEN_SCOPE
|
||||
// clang-format off
|
||||
TF_DEFINE_PRIVATE_TOKENS(_tokens,
|
||||
(st)
|
||||
);
|
||||
// clang-format on
|
||||
PXR_NAMESPACE_CLOSE_SCOPE
|
||||
|
||||
namespace mujoco {
|
||||
namespace {
|
||||
|
||||
using pxr::SdfPath;
|
||||
|
||||
static const char* kMaterialsPath =
|
||||
"experimental/usd/plugins/mjcf/testdata/materials.xml";
|
||||
static const char* kMeshObjPath =
|
||||
"experimental/usd/plugins/mjcf/testdata/mesh_obj.xml";
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestClassAuthored) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="test">
|
||||
<default>
|
||||
<default class="test">
|
||||
</default>
|
||||
</default>
|
||||
<worldbody>
|
||||
<body name="test_body" pos="0 0 0">
|
||||
<geom class="test" type="sphere" size="2 2 2"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
|
||||
|
||||
auto stage = pxr::UsdStage::Open(layer);
|
||||
EXPECT_PRIM_VALID(stage, "/__class__");
|
||||
EXPECT_PRIM_VALID(stage, "/__class__/test");
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestBasicMeshSources) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="mesh test">
|
||||
<asset>
|
||||
<mesh name="tetrahedron" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="test_body">
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestMaterials) {
|
||||
const std::string xml_path = GetTestDataFilePath(kMaterialsPath);
|
||||
|
||||
auto stage = pxr::UsdStage::Open(xml_path);
|
||||
EXPECT_THAT(stage, testing::NotNull());
|
||||
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test");
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test/Materials");
|
||||
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test/Materials/material_red");
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test/Materials/material_red/PreviewSurface");
|
||||
ExpectAttributeEqual(
|
||||
stage,
|
||||
"/mesh_test/Materials/material_red/PreviewSurface.inputs:diffuseColor",
|
||||
pxr::GfVec3f(0.8, 0, 0));
|
||||
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test/Materials/material_texture");
|
||||
EXPECT_PRIM_VALID(stage,
|
||||
"/mesh_test/Materials/material_texture/PreviewSurface");
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test/Materials/material_texture/uvmap");
|
||||
EXPECT_PRIM_VALID(stage, "/mesh_test/Materials/material_texture/texture");
|
||||
ExpectAttributeHasConnection(
|
||||
stage,
|
||||
"/mesh_test/Materials/material_texture/"
|
||||
"PreviewSurface.inputs:diffuseColor",
|
||||
"/mesh_test/Materials/material_texture/texture.outputs:rgb");
|
||||
ExpectAttributeEqual(
|
||||
stage, "/mesh_test/Materials/material_texture/texture.inputs:file",
|
||||
pxr::SdfAssetPath("textures/cube.png"));
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestFaceVaryingMeshSourcesSimpleMjcfMesh) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="mesh test">
|
||||
<asset>
|
||||
<mesh
|
||||
name="tetrahedron"
|
||||
face="0 3 2 0 1 3 0 2 1 1 2 3"
|
||||
vertex="0 1 0 0 0 0 1 0 1 1 0 -1"
|
||||
normal="1 0 0 0 1 0 0 0 1 -1 0 0"
|
||||
texcoord="0.5 0.5 0 0.5 1 1 1 0"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="test_body">
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
|
||||
|
||||
auto stage = pxr::UsdStage::Open(layer);
|
||||
|
||||
auto mesh = pxr::UsdGeomMesh::Get(
|
||||
stage, SdfPath("/mesh_test/test_body/test_body/tetrahedron/Mesh"));
|
||||
ASSERT_TRUE(mesh);
|
||||
pxr::VtArray<int> face_vertex_counts;
|
||||
mesh.GetFaceVertexCountsAttr().Get(&face_vertex_counts);
|
||||
EXPECT_EQ(face_vertex_counts.size(), 4);
|
||||
EXPECT_EQ(face_vertex_counts, pxr::VtArray<int>({3, 3, 3, 3}));
|
||||
|
||||
pxr::VtArray<int> face_vertex_indices;
|
||||
mesh.GetFaceVertexIndicesAttr().Get(&face_vertex_indices);
|
||||
EXPECT_EQ(face_vertex_indices.size(), 12);
|
||||
EXPECT_EQ(face_vertex_indices,
|
||||
pxr::VtArray<int>({0, 3, 2, 0, 1, 3, 0, 2, 1, 1, 2, 3}));
|
||||
|
||||
pxr::VtArray<pxr::GfVec3f> normals;
|
||||
mesh.GetNormalsAttr().Get(&normals);
|
||||
EXPECT_EQ(normals.size(), face_vertex_indices.size());
|
||||
// We can't directly check the normals values because they are altered by
|
||||
// Mujoco's compiling step. So we at least check that normals with the same
|
||||
// original index are the same.
|
||||
for (int i = 0; i < face_vertex_indices.size(); ++i) {
|
||||
for (int j = i + 1; j < face_vertex_indices.size(); ++j) {
|
||||
if (face_vertex_indices[i] == face_vertex_indices[j]) {
|
||||
EXPECT_EQ(normals[i], normals[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto primvars_api = pxr::UsdGeomPrimvarsAPI(mesh.GetPrim());
|
||||
|
||||
pxr::VtArray<pxr::GfVec2f> texcoords;
|
||||
EXPECT_TRUE(primvars_api.HasPrimvar(pxr::_tokens->st));
|
||||
auto primvar_st = primvars_api.GetPrimvar(pxr::_tokens->st);
|
||||
primvar_st.Get(&texcoords);
|
||||
EXPECT_EQ(texcoords.size(), face_vertex_indices.size());
|
||||
|
||||
// Check the faceVarying texcoords against the manually indexed source
|
||||
// texcoords.
|
||||
pxr::VtArray<pxr::GfVec2f> source_texcoords{
|
||||
{0.5, 0.5}, {0, 0.5}, {1, 0}, {1, 1}};
|
||||
for (int i = 0; i < face_vertex_indices.size(); ++i) {
|
||||
EXPECT_EQ(texcoords[i], source_texcoords[face_vertex_indices[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest,
|
||||
TestFaceVaryingMeshSourcesObjWithIndexedNormals) {
|
||||
const std::string xml_path = GetTestDataFilePath(kMeshObjPath);
|
||||
|
||||
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"));
|
||||
ASSERT_TRUE(mesh);
|
||||
pxr::VtArray<int> face_vertex_counts;
|
||||
mesh.GetFaceVertexCountsAttr().Get(&face_vertex_counts);
|
||||
EXPECT_EQ(face_vertex_counts.size(), 4);
|
||||
EXPECT_EQ(face_vertex_counts, pxr::VtArray<int>({3, 3, 3, 3}));
|
||||
|
||||
pxr::VtArray<int> face_vertex_indices;
|
||||
mesh.GetFaceVertexIndicesAttr().Get(&face_vertex_indices);
|
||||
EXPECT_EQ(face_vertex_indices.size(), 12);
|
||||
EXPECT_EQ(face_vertex_indices,
|
||||
pxr::VtArray<int>({0, 3, 2, 0, 1, 3, 0, 2, 1, 1, 2, 3}));
|
||||
|
||||
pxr::VtArray<pxr::GfVec3f> normals;
|
||||
mesh.GetNormalsAttr().Get(&normals);
|
||||
EXPECT_EQ(normals.size(), face_vertex_indices.size());
|
||||
// We can't directly check the normals values because they are altered by
|
||||
// Mujoco's compiling step.
|
||||
// We also can't access the normals indexing data, and can't use the vertex
|
||||
// indexing data here because they are separate.
|
||||
// So we check that the first half of the normals are the same, then the
|
||||
// second half, as set in the OBJ file.
|
||||
pxr::GfVec3f first_half_normal = normals[0];
|
||||
pxr::GfVec3f second_half_normal = normals[face_vertex_indices.size() / 2];
|
||||
EXPECT_NE(first_half_normal, second_half_normal);
|
||||
int i = 0;
|
||||
for (; i < face_vertex_indices.size() / 2; ++i) {
|
||||
EXPECT_EQ(normals[i], first_half_normal);
|
||||
}
|
||||
for (; i < face_vertex_indices.size(); ++i) {
|
||||
EXPECT_EQ(normals[i], second_half_normal);
|
||||
}
|
||||
|
||||
auto primvars_api = pxr::UsdGeomPrimvarsAPI(mesh.GetPrim());
|
||||
|
||||
pxr::VtArray<pxr::GfVec2f> texcoords;
|
||||
EXPECT_TRUE(primvars_api.HasPrimvar(pxr::_tokens->st));
|
||||
auto primvar_st = primvars_api.GetPrimvar(pxr::_tokens->st);
|
||||
primvar_st.Get(&texcoords);
|
||||
EXPECT_EQ(texcoords.size(), face_vertex_indices.size());
|
||||
|
||||
// Check the faceVarying texcoords against the manually indexed source
|
||||
// texcoords.
|
||||
// NOTE: For OBJ we must use different indices for the texcoords than for the
|
||||
// vertices!
|
||||
std::vector<int> source_face_texcoord_indices{0, 1, 2, 1, 2, 3,
|
||||
2, 3, 0, 3, 0, 1};
|
||||
pxr::VtArray<pxr::GfVec2f> source_texcoords{
|
||||
{0.5, 0.5}, {0, 0.5}, {1, 0}, {1, 1}};
|
||||
// NOTE: The v component of the texcoords is flipped when Mujoco loads the
|
||||
// OBJ.
|
||||
for (auto& uv : source_texcoords) {
|
||||
uv[1] = 1 - uv[1];
|
||||
}
|
||||
for (int i = 0; i < source_face_texcoord_indices.size(); ++i) {
|
||||
EXPECT_EQ(texcoords[i], source_texcoords[source_face_texcoord_indices[i]]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestBody) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="body test">
|
||||
<asset>
|
||||
<mesh name="tetrahedron" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="test_body" pos="0 1 0">
|
||||
<joint type="free" />
|
||||
<frame pos="0 0 1">
|
||||
<frame pos="0 0 1">
|
||||
<body name="test_body_2" pos="1 0 0">
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
</body>
|
||||
</frame>
|
||||
</frame>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestBasicParenting) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="test">
|
||||
<worldbody>
|
||||
<body name="root" pos="0 1 0">
|
||||
<body name="root/body_1" pos="1 0 0" />
|
||||
<body name="root/body_2" pos="1 0 0">
|
||||
<body name="root/body_3" pos="1 0 0" />
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestJointsDoNotAffectParenting) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="test">
|
||||
<asset>
|
||||
<mesh name="tetrahedron" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="root" pos="0 1 0">
|
||||
<joint type="free" />
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
<body name="middle">
|
||||
<body name="tet">
|
||||
<joint type="hinge" />
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
TEST_F(MjcfSdfFileFormatPluginTest, TestKindAuthoring) {
|
||||
static constexpr char kXml[] = R"(
|
||||
<mujoco model="test">
|
||||
<asset>
|
||||
<mesh name="tetrahedron" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="root" pos="0 1 0">
|
||||
<joint type="free" />
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
<body name="middle">
|
||||
<body name="tet">
|
||||
<joint type="hinge" />
|
||||
<geom type="mesh" mesh="tetrahedron"/>
|
||||
</body>
|
||||
</body>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
)";
|
||||
|
||||
pxr::SdfLayerRefPtr layer = LoadLayer(kXml);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace mujoco
|
||||
@@ -0,0 +1,15 @@
|
||||
<mujoco model="mesh test">
|
||||
<asset>
|
||||
<texture name="texture" file="textures/cube.png" type="2d" />
|
||||
<material name="material_red" rgba="0.8 0 0 1" />
|
||||
<material name="material_texture" texture="texture" />
|
||||
<mesh name="tetrahedron" vertex="0 0 0 1 0 0 0 1 0 0 0 1"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="test_body">
|
||||
<geom material="material_red" type="mesh" mesh="tetrahedron"/>
|
||||
<geom material="material_texture" type="mesh" mesh="tetrahedron"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<mujoco model="mesh test">
|
||||
<asset>
|
||||
<mesh name="mesh" file="meshes/tetrahedron.obj"/>
|
||||
</asset>
|
||||
<worldbody>
|
||||
<body name="test_body">
|
||||
<geom type="mesh" mesh="mesh"/>
|
||||
</body>
|
||||
</worldbody>
|
||||
</mujoco>
|
||||
@@ -0,0 +1,16 @@
|
||||
v 0 1 0
|
||||
v 0 0 0
|
||||
v 1 0 1
|
||||
v 1 0 -1
|
||||
vn 1 0 0
|
||||
vn -1 0 0
|
||||
vt 0.5 0.5
|
||||
vt 0 0.5
|
||||
vt 1 1
|
||||
vt 1 0
|
||||
|
||||
f 1/1/1 4/2/1 3/3/1
|
||||
f 1/2/1 2/3/1 4/4/1
|
||||
f 1/3/2 3/4/2 2/1/2
|
||||
f 2/4/2 3/1/2 4/2/2
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
Reference in New Issue
Block a user