diff --git a/include/mujoco/experimental/usd/mjcPhysics/materialAPI.h b/include/mujoco/experimental/usd/mjcPhysics/materialAPI.h new file mode 100644 index 00000000..3ae2be88 --- /dev/null +++ b/include/mujoco/experimental/usd/mjcPhysics/materialAPI.h @@ -0,0 +1,210 @@ +// 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 MJCPHYSICS_GENERATED_MATERIALAPI_H +#define MJCPHYSICS_GENERATED_MATERIALAPI_H + +/// \file mjcPhysics/materialAPI.h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +class SdfAssetPath; + +// -------------------------------------------------------------------------- // +// MJCMATERIALAPI // +// -------------------------------------------------------------------------- // + +/// \class MjcPhysicsMaterialAPI +/// +/// API providing extension attributes to represent physicsl MuJoCo materials. +/// +class MjcPhysicsMaterialAPI : public UsdAPISchemaBase { + public: + /// Compile time constant representing what kind of schema this class is. + /// + /// \sa UsdSchemaKind + static const UsdSchemaKind schemaKind = UsdSchemaKind::SingleApplyAPI; + + /// Construct a MjcPhysicsMaterialAPI on UsdPrim \p prim . + /// Equivalent to MjcPhysicsMaterialAPI::Get(prim.GetStage(), prim.GetPath()) + /// for a \em valid \p prim, but will not immediately throw an error for + /// an invalid \p prim + explicit MjcPhysicsMaterialAPI(const UsdPrim &prim = UsdPrim()) + : UsdAPISchemaBase(prim) {} + + /// Construct a MjcPhysicsMaterialAPI on the prim held by \p schemaObj . + /// Should be preferred over MjcPhysicsMaterialAPI(schemaObj.GetPrim()), + /// as it preserves SchemaBase state. + explicit MjcPhysicsMaterialAPI(const UsdSchemaBase &schemaObj) + : UsdAPISchemaBase(schemaObj) {} + + /// Destructor. + MJCPHYSICS_API + virtual ~MjcPhysicsMaterialAPI(); + + /// Return a vector of names of all pre-declared attributes for this schema + /// class and all its ancestor classes. Does not include attributes that + /// may be authored by custom/extended methods of the schemas involved. + MJCPHYSICS_API + static const TfTokenVector &GetSchemaAttributeNames( + bool includeInherited = true); + + /// Return a MjcPhysicsMaterialAPI holding the prim adhering to this + /// schema at \p path on \p stage. If no prim exists at \p path on + /// \p stage, or if the prim at that path does not adhere to this schema, + /// return an invalid schema object. This is shorthand for the following: + /// + /// \code + /// MjcPhysicsMaterialAPI(stage->GetPrimAtPath(path)); + /// \endcode + /// + MJCPHYSICS_API + static MjcPhysicsMaterialAPI Get(const UsdStagePtr &stage, + const SdfPath &path); + + /// Returns true if this single-apply API schema can be applied to + /// the given \p prim. If this schema can not be a applied to the prim, + /// this returns false and, if provided, populates \p whyNot with the + /// reason it can not be applied. + /// + /// Note that if CanApply returns false, that does not necessarily imply + /// that calling Apply will fail. Callers are expected to call CanApply + /// before calling Apply if they want to ensure that it is valid to + /// apply a schema. + /// + /// \sa UsdPrim::GetAppliedSchemas() + /// \sa UsdPrim::HasAPI() + /// \sa UsdPrim::CanApplyAPI() + /// \sa UsdPrim::ApplyAPI() + /// \sa UsdPrim::RemoveAPI() + /// + MJCPHYSICS_API + static bool CanApply(const UsdPrim &prim, std::string *whyNot = nullptr); + + /// Applies this single-apply API schema to the given \p prim. + /// This information is stored by adding "MjcMaterialAPI" to the + /// token-valued, listOp metadata \em apiSchemas on the prim. + /// + /// \return A valid MjcPhysicsMaterialAPI object is returned upon success. + /// An invalid (or empty) MjcPhysicsMaterialAPI object is returned upon + /// failure. See \ref UsdPrim::ApplyAPI() for conditions + /// resulting in failure. + /// + /// \sa UsdPrim::GetAppliedSchemas() + /// \sa UsdPrim::HasAPI() + /// \sa UsdPrim::CanApplyAPI() + /// \sa UsdPrim::ApplyAPI() + /// \sa UsdPrim::RemoveAPI() + /// + MJCPHYSICS_API + static MjcPhysicsMaterialAPI Apply(const UsdPrim &prim); + + protected: + /// Returns the kind of schema this class belongs to. + /// + /// \sa UsdSchemaKind + MJCPHYSICS_API + UsdSchemaKind _GetSchemaKind() const override; + + private: + // needs to invoke _GetStaticTfType. + friend class UsdSchemaRegistry; + MJCPHYSICS_API + static const TfType &_GetStaticTfType(); + + static bool _IsTypedSchema(); + + // override SchemaBase virtuals. + MJCPHYSICS_API + const TfType &_GetTfType() const override; + + public: + // --------------------------------------------------------------------- // + // TORSIONALFRICTION + // --------------------------------------------------------------------- // + /// Friction value acting around contact normal. + /// + /// | || + /// | -- | -- | + /// | Declaration | `uniform double mjc:torsionalfriction = 0.005` | + /// | C++ Type | double | + /// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double | + /// | \ref SdfVariability "Variability" | SdfVariabilityUniform | + MJCPHYSICS_API + UsdAttribute GetTorsionalFrictionAttr() const; + + /// See GetTorsionalFrictionAttr(), and also + /// \ref Usd_Create_Or_Get_Property for when to use Get vs Create. + /// If specified, author \p defaultValue as the attribute's default, + /// sparsely (when it makes sense to do so) if \p writeSparsely is \c true - + /// the default for \p writeSparsely is \c false. + MJCPHYSICS_API + UsdAttribute CreateTorsionalFrictionAttr( + VtValue const &defaultValue = VtValue(), + bool writeSparsely = false) const; + + public: + // --------------------------------------------------------------------- // + // ROLLINGFRICTION + // --------------------------------------------------------------------- // + /// Friction value acting around both axes on the contact tangent plane. + /// + /// | || + /// | -- | -- | + /// | Declaration | `uniform double mjc:rollingfriction = 0.0001` | + /// | C++ Type | double | + /// | \ref Usd_Datatypes "Usd Type" | SdfValueTypeNames->Double | + /// | \ref SdfVariability "Variability" | SdfVariabilityUniform | + MJCPHYSICS_API + UsdAttribute GetRollingFrictionAttr() const; + + /// See GetRollingFrictionAttr(), and also + /// \ref Usd_Create_Or_Get_Property for when to use Get vs Create. + /// If specified, author \p defaultValue as the attribute's default, + /// sparsely (when it makes sense to do so) if \p writeSparsely is \c true - + /// the default for \p writeSparsely is \c false. + MJCPHYSICS_API + UsdAttribute CreateRollingFrictionAttr( + VtValue const &defaultValue = VtValue(), + bool writeSparsely = false) const; + + public: + // ===================================================================== // + // Feel free to add custom code below this line, it will be preserved by + // the code generator. + // + // Just remember to: + // - Close the class declaration with }; + // - Close the namespace with PXR_NAMESPACE_CLOSE_SCOPE + // - Close the include guard with #endif + // ===================================================================== // + // --(BEGIN CUSTOM CODE)-- +}; + +PXR_NAMESPACE_CLOSE_SCOPE + +#endif diff --git a/include/mujoco/experimental/usd/mjcPhysics/tokens.h b/include/mujoco/experimental/usd/mjcPhysics/tokens.h index 5d697927..10ab4555 100644 --- a/include/mujoco/experimental/usd/mjcPhysics/tokens.h +++ b/include/mujoco/experimental/usd/mjcPhysics/tokens.h @@ -493,6 +493,10 @@ struct MjcPhysicsTokensType { /// /// MjcPhysicsActuator const TfToken mjcRefSite; + /// \brief "mjc:rollingfriction" + /// + /// MjcPhysicsMaterialAPI + const TfToken mjcRollingfriction; /// \brief "mjc:shellinertia" /// /// MjcPhysicsCollisionAPI @@ -533,6 +537,10 @@ struct MjcPhysicsTokensType { /// /// MjcPhysicsActuator const TfToken mjcTarget; + /// \brief "mjc:torsionalfriction" + /// + /// MjcPhysicsMaterialAPI + const TfToken mjcTorsionalfriction; /// \brief "muscle" /// /// Possible value for MjcPhysicsActuator::GetMjcBiasTypeAttr(), Possible @@ -602,6 +610,10 @@ struct MjcPhysicsTokensType { /// /// Schema identifier and family for MjcPhysicsKeyframe const TfToken MjcKeyframe; + /// \brief "MjcMaterialAPI" + /// + /// Schema identifier and family for MjcPhysicsMaterialAPI + const TfToken MjcMaterialAPI; /// \brief "MjcMeshCollisionAPI" /// /// Schema identifier and family for MjcPhysicsMeshCollisionAPI diff --git a/src/experimental/usd/CMakeLists.txt b/src/experimental/usd/CMakeLists.txt index c3b29730..536aa12a 100644 --- a/src/experimental/usd/CMakeLists.txt +++ b/src/experimental/usd/CMakeLists.txt @@ -176,6 +176,7 @@ target_sources(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PRIVATE mjcPhysics/collisionAPI.cpp mjcPhysics/jointAPI.cpp mjcPhysics/keyframe.cpp + mjcPhysics/materialAPI.cpp mjcPhysics/meshCollisionAPI.cpp mjcPhysics/sceneAPI.cpp mjcPhysics/siteAPI.cpp diff --git a/src/experimental/usd/mjcPhysics/generatedSchema.usda b/src/experimental/usd/mjcPhysics/generatedSchema.usda index d88ae061..e7e8166b 100644 --- a/src/experimental/usd/mjcPhysics/generatedSchema.usda +++ b/src/experimental/usd/mjcPhysics/generatedSchema.usda @@ -433,3 +433,17 @@ class "MjcJointAPI" ( ) } +class "MjcMaterialAPI" ( + doc = "API providing extension attributes to represent physicsl MuJoCo materials." +) +{ + uniform double mjc:rollingfriction = 0.0001 ( + displayName = "Rolling Friction" + doc = "Friction value acting around both axes on the contact tangent plane." + ) + uniform double mjc:torsionalfriction = 0.005 ( + displayName = "Torsional Friction" + doc = "Friction value acting around contact normal." + ) +} + diff --git a/src/experimental/usd/mjcPhysics/materialAPI.cpp b/src/experimental/usd/mjcPhysics/materialAPI.cpp new file mode 100644 index 00000000..20dc2560 --- /dev/null +++ b/src/experimental/usd/mjcPhysics/materialAPI.cpp @@ -0,0 +1,135 @@ +// 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 + +#include +#include +#include +#include + +PXR_NAMESPACE_OPEN_SCOPE + +// Register the schema with the TfType system. +TF_REGISTRY_FUNCTION(TfType) { + TfType::Define >(); +} + +/* virtual */ +MjcPhysicsMaterialAPI::~MjcPhysicsMaterialAPI() {} + +/* static */ +MjcPhysicsMaterialAPI MjcPhysicsMaterialAPI::Get(const UsdStagePtr &stage, + const SdfPath &path) { + if (!stage) { + TF_CODING_ERROR("Invalid stage"); + return MjcPhysicsMaterialAPI(); + } + return MjcPhysicsMaterialAPI(stage->GetPrimAtPath(path)); +} + +/* virtual */ +UsdSchemaKind MjcPhysicsMaterialAPI::_GetSchemaKind() const { + return MjcPhysicsMaterialAPI::schemaKind; +} + +/* static */ +bool MjcPhysicsMaterialAPI::CanApply(const UsdPrim &prim, std::string *whyNot) { + return prim.CanApplyAPI(whyNot); +} + +/* static */ +MjcPhysicsMaterialAPI MjcPhysicsMaterialAPI::Apply(const UsdPrim &prim) { + if (prim.ApplyAPI()) { + return MjcPhysicsMaterialAPI(prim); + } + return MjcPhysicsMaterialAPI(); +} + +/* static */ +const TfType &MjcPhysicsMaterialAPI::_GetStaticTfType() { + static TfType tfType = TfType::Find(); + return tfType; +} + +/* static */ +bool MjcPhysicsMaterialAPI::_IsTypedSchema() { + static bool isTyped = _GetStaticTfType().IsA(); + return isTyped; +} + +/* virtual */ +const TfType &MjcPhysicsMaterialAPI::_GetTfType() const { + return _GetStaticTfType(); +} + +UsdAttribute MjcPhysicsMaterialAPI::GetTorsionalFrictionAttr() const { + return GetPrim().GetAttribute(MjcPhysicsTokens->mjcTorsionalfriction); +} + +UsdAttribute MjcPhysicsMaterialAPI::CreateTorsionalFrictionAttr( + VtValue const &defaultValue, bool writeSparsely) const { + return UsdSchemaBase::_CreateAttr( + MjcPhysicsTokens->mjcTorsionalfriction, SdfValueTypeNames->Double, + /* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely); +} + +UsdAttribute MjcPhysicsMaterialAPI::GetRollingFrictionAttr() const { + return GetPrim().GetAttribute(MjcPhysicsTokens->mjcRollingfriction); +} + +UsdAttribute MjcPhysicsMaterialAPI::CreateRollingFrictionAttr( + VtValue const &defaultValue, bool writeSparsely) const { + return UsdSchemaBase::_CreateAttr( + MjcPhysicsTokens->mjcRollingfriction, SdfValueTypeNames->Double, + /* custom = */ false, SdfVariabilityUniform, defaultValue, writeSparsely); +} + +namespace { +static inline TfTokenVector _ConcatenateAttributeNames( + const TfTokenVector &left, const TfTokenVector &right) { + TfTokenVector result; + result.reserve(left.size() + right.size()); + result.insert(result.end(), left.begin(), left.end()); + result.insert(result.end(), right.begin(), right.end()); + return result; +} +} // namespace + +/*static*/ +const TfTokenVector &MjcPhysicsMaterialAPI::GetSchemaAttributeNames( + bool includeInherited) { + static TfTokenVector localNames = { + MjcPhysicsTokens->mjcTorsionalfriction, + MjcPhysicsTokens->mjcRollingfriction, + }; + static TfTokenVector allNames = _ConcatenateAttributeNames( + UsdAPISchemaBase::GetSchemaAttributeNames(true), localNames); + + if (includeInherited) + return allNames; + else + return localNames; +} + +PXR_NAMESPACE_CLOSE_SCOPE + +// ===================================================================== // +// Feel free to add custom code below this line. It will be preserved by +// the code generator. +// +// Just remember to wrap code in the appropriate delimiters: +// 'PXR_NAMESPACE_OPEN_SCOPE', 'PXR_NAMESPACE_CLOSE_SCOPE'. +// ===================================================================== // +// --(BEGIN CUSTOM CODE)-- diff --git a/src/experimental/usd/mjcPhysics/plugInfo.json b/src/experimental/usd/mjcPhysics/plugInfo.json index 6eee918a..fc1e68e9 100644 --- a/src/experimental/usd/mjcPhysics/plugInfo.json +++ b/src/experimental/usd/mjcPhysics/plugInfo.json @@ -43,6 +43,16 @@ ], "schemaKind": "concreteTyped" }, + "MjcPhysicsMaterialAPI": { + "alias": { + "UsdSchemaBase": "MjcMaterialAPI" + }, + "autoGenerated": true, + "bases": [ + "UsdAPISchemaBase" + ], + "schemaKind": "singleApplyAPI" + }, "MjcPhysicsMeshCollisionAPI": { "alias": { "UsdSchemaBase": "MjcMeshCollisionAPI" diff --git a/src/experimental/usd/mjcPhysics/schema.usda b/src/experimental/usd/mjcPhysics/schema.usda index f3e3ecc4..1d43cd15 100644 --- a/src/experimental/usd/mjcPhysics/schema.usda +++ b/src/experimental/usd/mjcPhysics/schema.usda @@ -829,3 +829,30 @@ class "MjcJointAPI" doc = "Friction loss due to dry friction. This value is the same for all degrees of freedom created by this joint. Semantically friction loss does not make sense for free joints, but the compiler allows it. To enable friction loss, set this attribute to a positive value." ) } + +class "MjcMaterialAPI" +( + customData = { + string className = "MaterialAPI" + } + doc = """API providing extension attributes to represent physicsl MuJoCo materials.""" + + inherits = +) +{ + uniform double mjc:torsionalfriction = 0.005 ( + customData = { + string apiName = "TorsionalFriction" + } + displayName = "Torsional Friction" + doc = """Friction value acting around contact normal.""" + ) + + uniform double mjc:rollingfriction = 0.0001 ( + customData = { + string apiName = "RollingFriction" + } + displayName = "Rolling Friction" + doc = """Friction value acting around both axes on the contact tangent plane.""" + ) +} diff --git a/src/experimental/usd/mjcPhysics/tokens.cpp b/src/experimental/usd/mjcPhysics/tokens.cpp index 4056b5a8..b5794aee 100644 --- a/src/experimental/usd/mjcPhysics/tokens.cpp +++ b/src/experimental/usd/mjcPhysics/tokens.cpp @@ -126,6 +126,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType() mjcQvel("mjc:qvel", TfToken::Immortal), mjcRef("mjc:ref", TfToken::Immortal), mjcRefSite("mjc:refSite", TfToken::Immortal), + mjcRollingfriction("mjc:rollingfriction", TfToken::Immortal), mjcShellinertia("mjc:shellinertia", TfToken::Immortal), mjcSliderSite("mjc:sliderSite", TfToken::Immortal), mjcSolimpfriction("mjc:solimpfriction", TfToken::Immortal), @@ -136,6 +137,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType() mjcSpringref("mjc:springref", TfToken::Immortal), mjcStiffness("mjc:stiffness", TfToken::Immortal), mjcTarget("mjc:target", TfToken::Immortal), + mjcTorsionalfriction("mjc:torsionalfriction", TfToken::Immortal), muscle("muscle", TfToken::Immortal), newton("newton", TfToken::Immortal), none("none", TfToken::Immortal), @@ -150,6 +152,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType() MjcCollisionAPI("MjcCollisionAPI", TfToken::Immortal), MjcJointAPI("MjcJointAPI", TfToken::Immortal), MjcKeyframe("MjcKeyframe", TfToken::Immortal), + MjcMaterialAPI("MjcMaterialAPI", TfToken::Immortal), MjcMeshCollisionAPI("MjcMeshCollisionAPI", TfToken::Immortal), MjcSceneAPI("MjcSceneAPI", TfToken::Immortal), MjcSiteAPI("MjcSiteAPI", TfToken::Immortal), @@ -259,6 +262,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType() mjcQvel, mjcRef, mjcRefSite, + mjcRollingfriction, mjcShellinertia, mjcSliderSite, mjcSolimpfriction, @@ -269,6 +273,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType() mjcSpringref, mjcStiffness, mjcTarget, + mjcTorsionalfriction, muscle, newton, none, @@ -283,6 +288,7 @@ MjcPhysicsTokensType::MjcPhysicsTokensType() MjcCollisionAPI, MjcJointAPI, MjcKeyframe, + MjcMaterialAPI, MjcMeshCollisionAPI, MjcSceneAPI, MjcSiteAPI}) {} diff --git a/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc b/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc index 019e96cf..1c0b66e5 100644 --- a/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc +++ b/src/experimental/usd/plugins/mjcf/mujoco_to_usd.cc @@ -84,6 +84,7 @@ TF_DEFINE_PRIVATE_TOKENS(kTokens, ((light, "Light")) ((meshScope, "MeshSources")) ((materialsScope, "Materials")) + ((physicsMaterialsScope, "PhysicsMaterials")) ((previewSurface, "PreviewSurface")) ((keyframesScope, "Keyframes")) ((actuatorsScope, "Actuators")) @@ -744,6 +745,48 @@ class ModelWriter { return texture_output_attrs; } + pxr::SdfPath WritePhysicsMaterial(mjsGeom *geom) { + pxr::SdfPath scope_path = + body_paths_[kWorldIndex].AppendChild(kTokens->physicsMaterialsScope); + + if (!data_->HasSpec(scope_path)) { + CreatePrimSpec(data_, body_paths_[kWorldIndex], + kTokens->physicsMaterialsScope, pxr::UsdGeomTokens->Scope); + } + + auto name = GetAvailablePrimName(*mjs_getName(geom->element), + pxr::UsdShadeTokens->Material, scope_path); + pxr::SdfPath material_path = + CreatePrimSpec(data_, scope_path, name, pxr::UsdShadeTokens->Material); + + ApplyApiSchema(data_, material_path, + pxr::UsdPhysicsTokens->PhysicsMaterialAPI); + ApplyApiSchema(data_, material_path, + MjcPhysicsTokens->MjcMaterialAPI); + + mjsGeom *geom_default = mjs_getDefault(geom->element)->geom; + if (geom->friction[0] != geom_default->friction[0]) { + WriteUniformAttribute(material_path, pxr::SdfValueTypeNames->Float, + pxr::UsdPhysicsTokens->physicsStaticFriction, + (float)geom->friction[0]); + WriteUniformAttribute(material_path, pxr::SdfValueTypeNames->Float, + pxr::UsdPhysicsTokens->physicsDynamicFriction, + (float)geom->friction[0]); + } + if (geom->friction[1] != geom_default->friction[1]) { + WriteUniformAttribute(material_path, pxr::SdfValueTypeNames->Double, + MjcPhysicsTokens->mjcTorsionalfriction, + geom->friction[1]); + } + if (geom->friction[2] != geom_default->friction[2]) { + WriteUniformAttribute(material_path, pxr::SdfValueTypeNames->Double, + MjcPhysicsTokens->mjcRollingfriction, + geom->friction[2]); + } + + return material_path; + } + void WriteMaterial(mjsMaterial *material, const pxr::SdfPath &parent_path) { // Create a Material prim. auto name = @@ -1551,6 +1594,20 @@ class ModelWriter { SetAttributeDefault(data_, density_attr, (float)geom->density); } + mjsDefault *geom_default = mjs_getDefault(geom->element); + + if (geom->friction[0] != geom_default->geom->friction[0] || + geom->friction[1] != geom_default->geom->friction[1] || + geom->friction[2] != geom_default->geom->friction[2]) { + pxr::SdfPath physics_material_path = WritePhysicsMaterial(geom); + ApplyApiSchema(data_, geom_path, + pxr::UsdShadeTokens->MaterialBindingAPI); + // Bind the material to this geom. + CreateRelationshipSpec( + data_, geom_path, pxr::UsdShadeTokens->materialBinding, + physics_material_path, pxr::SdfVariabilityUniform); + } + // For meshes, also apply PhysicsMeshCollisionAPI and set the // approximation attribute. if (geom->type == mjGEOM_MESH) { diff --git a/src/experimental/usd/usd_to_mjspec.cc b/src/experimental/usd/usd_to_mjspec.cc index 1f87ae52..2b07dedb 100644 --- a/src/experimental/usd/usd_to_mjspec.cc +++ b/src/experimental/usd/usd_to_mjspec.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -60,15 +61,23 @@ #include #include #include +#include #include #include #include #include +#include namespace { using pxr::MjcPhysicsTokens; using pxr::TfToken; +struct UsdCaches { + pxr::UsdGeomXformCache xform_cache; + pxr::UsdShadeMaterialBindingAPI::BindingsCache bindings_cache; + pxr::UsdShadeMaterialBindingAPI::CollectionQueryCache collection_query_cache; +}; + void SetDoubleArrFromGfVec3d(double* to, const pxr::GfVec3d& from) { to[0] = from[0]; to[1] = from[1]; @@ -967,10 +976,62 @@ void ParseMjcPhysicsJointAPI(mjsJoint* mj_joint, } } +void ParseUsdPhysicsMaterialAPI( + mjsGeom* geom, const pxr::UsdPhysicsMaterialAPI& material_api) { + auto static_friction_attr = material_api.GetStaticFrictionAttr(); + auto dynamic_friction_attr = material_api.GetDynamicFrictionAttr(); + if (static_friction_attr.HasAuthoredValue()) { + if (dynamic_friction_attr.HasAuthoredValue()) { + mju_warning( + "Material %s has both static and dynamic friction authored, taking " + "the static value.", + material_api.GetPath().GetString().c_str()); + } + float static_friction; + static_friction_attr.Get(&static_friction); + geom->friction[0] = static_friction; + } else if (dynamic_friction_attr.HasAuthoredValue()) { + float dynamic_friction; + dynamic_friction_attr.Get(&dynamic_friction); + geom->friction[0] = dynamic_friction; + } + + auto restitution_attr = material_api.GetRestitutionAttr(); + if (restitution_attr.HasAuthoredValue()) { + mju_warning( + "Material %s is trying to set the resitution coefficient, to control " + "restitution in MuJoCo use the direct method of setting solref to " + "(-stiffness, -damping). See " + "https://mujoco.readthedocs.io/en/latest/modeling.html#restitution for " + "examples.", + material_api.GetPath().GetString().c_str()); + } + + auto density_attr = material_api.GetDensityAttr(); + if (density_attr.HasAuthoredValue()) { + float density; + density_attr.Get(&density); + geom->density = density; + } +} + +void ParseMjcPhysicsMaterialAPI( + mjsGeom* geom, const pxr::MjcPhysicsMaterialAPI& material_api) { + auto torsional_friction_attr = material_api.GetTorsionalFrictionAttr(); + if (torsional_friction_attr.HasAuthoredValue()) { + torsional_friction_attr.Get(&geom->friction[1]); + } + + auto rolling_friction_attr = material_api.GetRollingFrictionAttr(); + if (rolling_friction_attr.HasAuthoredValue()) { + rolling_friction_attr.Get(&geom->friction[2]); + } +} + void ParseUsdPhysicsCollider(mjSpec* spec, const pxr::UsdPhysicsCollisionAPI& collision_api, const pxr::UsdPrim& body_prim, mjsBody* parent, - pxr::UsdGeomXformCache& xform_cache) { + UsdCaches& caches) { pxr::UsdPrim prim = collision_api.GetPrim(); // UsdPhysicsCollisionAPI can only be applied to gprim primitives. if (!prim.IsA()) { @@ -994,6 +1055,18 @@ void ParseUsdPhysicsCollider(mjSpec* spec, ParseMjcPhysicsCollisionAPI(geom, pxr::MjcPhysicsCollisionAPI(prim)); } + pxr::UsdShadeMaterial bound_material = + pxr::UsdShadeMaterialBindingAPI(prim).ComputeBoundMaterial( + &caches.bindings_cache, &caches.collection_query_cache); + if (bound_material) { + pxr::UsdPrim bound_material_prim = bound_material.GetPrim(); + if (bound_material_prim.HasAPI() || + bound_material_prim.HasAPI()) { + ParseUsdPhysicsMaterialAPI(geom, pxr::UsdPhysicsMaterialAPI(bound_material_prim)); + ParseMjcPhysicsMaterialAPI(geom, pxr::MjcPhysicsMaterialAPI(bound_material_prim)); + } + } + // Convert displayColor and displayOpacity to rgba. // We want to support primvar inheritance, hence FindPrimvarWithInheritance. pxr::UsdGeomPrimvarsAPI primvarsAPI(prim); @@ -1020,9 +1093,9 @@ void ParseUsdPhysicsCollider(mjSpec* spec, } } - SetLocalPoseFromPrim(prim, body_prim, geom, xform_cache); + SetLocalPoseFromPrim(prim, body_prim, geom, caches.xform_cache); - if (!MaybeParseGeomPrimitive(prim, geom, xform_cache)) { + if (!MaybeParseGeomPrimitive(prim, geom, caches.xform_cache)) { if (prim.IsA()) { geom->type = mjGEOM_MESH; pxr::UsdGeomMesh usd_mesh(prim); @@ -1303,8 +1376,7 @@ void PopulateSpecFromTree(pxr::UsdStageRefPtr stage, mjSpec* spec, mjsBody* parent_mj_body, const mujoco::usd::KinematicNode* parent_node, const mujoco::usd::KinematicNode& current_node, - pxr::UsdGeomXformCache& xform_cache, - const BodyPrimMap& body_to_prims) { + UsdCaches& caches, const BodyPrimMap& body_to_prims) { mjsBody* current_mj_body; if (current_node.body_path.IsEmpty()) { @@ -1322,11 +1394,12 @@ void PopulateSpecFromTree(pxr::UsdStageRefPtr stage, mjSpec* spec, current_mj_body = ParseUsdPhysicsRigidbody( spec, pxr::UsdPhysicsRigidBodyAPI(current_body_prim), - parent_prim_for_xform, parent_mj_body, xform_cache); + parent_prim_for_xform, parent_mj_body, caches.xform_cache); if (!current_node.joint_path.IsEmpty()) { pxr::UsdPrim joint_prim = stage->GetPrimAtPath(current_node.joint_path); - ParseUsdPhysicsJoint(spec, joint_prim, current_mj_body, xform_cache); + ParseUsdPhysicsJoint(spec, joint_prim, current_mj_body, + caches.xform_cache); } else if (parent_mj_body == mjs_findBody(spec, "world")) { // No joint to parent, and parent is world: this is a floating body. mjsJoint* free_joint = mjs_addJoint(current_mj_body, nullptr); @@ -1345,12 +1418,11 @@ void PopulateSpecFromTree(pxr::UsdStageRefPtr stage, mjSpec* spec, pxr::UsdPrim prim = stage->GetPrimAtPath(gprim_path); if (prim.HasAPI()) { ParseUsdPhysicsCollider(spec, pxr::UsdPhysicsCollisionAPI(prim), - body_prim_for_xform, current_mj_body, - xform_cache); + body_prim_for_xform, current_mj_body, caches); } if (prim.HasAPI()) { ParseMjcPhysicsSite(spec, pxr::MjcPhysicsSiteAPI(prim), - body_prim_for_xform, current_mj_body, xform_cache); + body_prim_for_xform, current_mj_body, caches.xform_cache); } } } @@ -1358,7 +1430,7 @@ void PopulateSpecFromTree(pxr::UsdStageRefPtr stage, mjSpec* spec, // Recurse through children. for (const auto& child_node : current_node.children) { PopulateSpecFromTree(stage, spec, current_mj_body, ¤t_node, - *child_node, xform_cache, body_to_prims); + *child_node, caches, body_to_prims); } } } // namespace @@ -1368,8 +1440,8 @@ mjSpec* mj_parseUSDStage(const pxr::UsdStageRefPtr stage) { std::vector physics_scenes; - // Xform cache to use for all queries when parsing. - pxr::UsdGeomXformCache xform_cache; + // Set of caches to use for all queries when parsing. + UsdCaches caches; // Search for UsdPhysicsScene type prim, use the first one that has // the MjcPhysicsSceneAPI applied or the first UsdPhysicsScene otherwise. @@ -1417,7 +1489,7 @@ mjSpec* mj_parseUSDStage(const pxr::UsdStageRefPtr stage) { pxr::UsdPrim prim = *it; bool is_body = prim.HasAPI(); - bool resets = xform_cache.GetResetXformStack(prim); + bool resets = caches.xform_cache.GetResetXformStack(prim); // Only update (push/pop) the owner stack for bodies (becomes new owner) and // resetXformStack (reset owner to world). bool is_pushed_to_stack = is_body || resets; @@ -1472,7 +1544,7 @@ mjSpec* mj_parseUSDStage(const pxr::UsdStageRefPtr stage) { if (kinematic_tree) { PopulateSpecFromTree(stage, spec, /*parent_mj_body=*/nullptr, - /*parent_node=*/nullptr, *kinematic_tree, xform_cache, + /*parent_node=*/nullptr, *kinematic_tree, caches, body_to_prims); } 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 6b9e2bfb..0107301b 100644 --- a/test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc +++ b/test/experimental/usd/plugins/mjcf/mjcf_file_format_test.cc @@ -139,6 +139,41 @@ TEST_F(MjcfSdfFileFormatPluginTest, TestBasicMeshSources) { EXPECT_PRIM_VALID(stage, "/mesh_test/test_body/tetrahedron/Mesh"); } +TEST_F(MjcfSdfFileFormatPluginTest, TestPhysicsMaterials) { + static constexpr char kXml[] = R"( + + + + + + + + )"; + auto stage = OpenStageWithPhysics(kXml); + EXPECT_PRIM_VALID( + stage, "/physics_materials_test/PhysicsMaterials/geom_with_friction"); + EXPECT_REL_HAS_TARGET( + stage, + "/physics_materials_test/test_body/geom_with_friction.material:binding", + "/physics_materials_test/PhysicsMaterials/geom_with_friction"); + ExpectAttributeEqual(stage, + "/physics_materials_test/PhysicsMaterials/" + "geom_with_friction.physics:staticFriction", + 4.0f); + ExpectAttributeEqual(stage, + "/physics_materials_test/PhysicsMaterials/" + "geom_with_friction.physics:dynamicFriction", + 4.0f); + ExpectAttributeEqual(stage, + "/physics_materials_test/PhysicsMaterials/" + "geom_with_friction.mjc:torsionalfriction", + 5.0); + ExpectAttributeEqual(stage, + "/physics_materials_test/PhysicsMaterials/" + "geom_with_friction.mjc:rollingfriction", + 6.0); +} + TEST_F(MjcfSdfFileFormatPluginTest, TestMaterials) { const std::string xml_path = GetTestDataFilePath(kMaterialsPath);