Move OpenUSD parsing to usd_decoder

Add usd_decoder to mujoco/plugin.
  - Previously was src/experimental/usd/usd_to_mjspec.cc, now that same spec is returned by the plugin.

PiperOrigin-RevId: 854350962
Change-Id: Ia980190a557c50ce8197980922a2594b613859b4
This commit is contained in:
Sam Haves
2026-01-09 14:47:37 -08:00
committed by Copybara-Service
parent 64a2345c07
commit a5dc57c0c3
17 changed files with 270 additions and 317 deletions
+1
View File
@@ -243,6 +243,7 @@ if(MUJOCO_WITH_USD)
endif()
add_subdirectory(src/experimental/usd)
add_subdirectory(plugin/usd_decoder)
endif()
if(BUILD_TESTING AND MUJOCO_BUILD_TESTS)
+32 -35
View File
@@ -13,42 +13,39 @@
# limitations under the License.
cmake_minimum_required(VERSION "3.16")
option(BUILD_USD OFF "Build OpenUSD")
project(openusd-dependency)
if (BUILD_USD)
include(ExternalProject)
include(ExternalProject)
set(DEPS_DIR ${CMAKE_BINARY_DIR}/../../../../build/_deps)
set(DEPS_DIR ${CMAKE_BINARY_DIR}/../../../../build/_deps)
ExternalProject_Add(openusd
GIT_REPOSITORY https://github.com/PixarAnimationStudios/USD.git
GIT_TAG v25.11
PREFIX openusd
BUILD_IN_SOURCE 0
UPDATE_COMMAND ""
CONFIGURE_COMMAND ""
INSTALL_COMMAND ""
SOURCE_DIR ${DEPS_DIR}/openusd-src
BINARY_DIR ${DEPS_DIR}/openusd-build
INSTALL_DIR ${DEPS_DIR}/openusd-build
BUILD_COMMAND python3 ${DEPS_DIR}/openusd-src/build_scripts/build_usd.py
--build-shared
--no-examples
--no-tools
--no-ptex
--no-prman
--no-openimageio
--no-opencolorio
--no-alembic
--no-draco
--no-materialx
--no-tutorials
--no-tests
--no-docs
--no-imaging
--no-python
--no-usdValidation
<INSTALL_DIR>
)
endif()
ExternalProject_Add(openusd
GIT_REPOSITORY https://github.com/PixarAnimationStudios/USD.git
GIT_TAG v25.11
PREFIX openusd
BUILD_IN_SOURCE 0
UPDATE_COMMAND ""
CONFIGURE_COMMAND ""
INSTALL_COMMAND ""
SOURCE_DIR ${DEPS_DIR}/openusd-src
BINARY_DIR ${DEPS_DIR}/openusd-build
INSTALL_DIR ${DEPS_DIR}/openusd-build
BUILD_COMMAND python3 ${DEPS_DIR}/openusd-src/build_scripts/build_usd.py
--build-shared
--no-examples
--no-tools
--no-ptex
--no-prman
--no-openimageio
--no-opencolorio
--no-alembic
--no-draco
--no-materialx
--no-tutorials
--no-tests
--no-docs
--no-imaging
--no-python
--no-usdValidation
<INSTALL_DIR>
)
+35 -29
View File
@@ -3,55 +3,61 @@ Building
.. WARNING:: OpenUSD support is currently experimental and subject to frequent change.
Advanced users can start testing out USD support by building against their own USD libraries or USD built from source.
MuJoCo must be built against a pre-built USD library, we provide a utility to do so but you may also bring your own USD
libraries.
This assumes that you have built MuJoCo in ``~/mujoco`` and have a build directory at ``~/mujoco/build``
The following instructions assume that you have cloned MuJoCo into ``~/mujoco`` and have a build directory at
``~/mujoco/build``.
.. _usdBuildingUSD:
Building USD
------------
USD has a pretty streamlined installation via their ``build_usd.py`` script. It's recommended to use a separate
installation directory that exists outside of the cloned repository directory.
If you have a pre-built USD library, you can skip this section.
MuJoCo provides a CMake project that simplifies the process of building USD. It will download and build USD with only
the necessary features enabled.
.. code-block:: bash
cd ~/mujoco
cmake -Bcmake/third_party_deps/openusd/build cmake/third_party_deps/openusd
cmake --build cmake/third_party_deps/openusd/build
If you want to customize the build process, you can use USD's ``build_usd.py`` script. It's recommended to use a
separate installation directory that exists outside of the cloned repository directory.
.. code-block:: bash
git clone https://github.com/PixarAnimationStudios/OpenUSD
python OpenUSD/build_scripts/build_usd.py /path/to/my_usd_install_dir
.. _usdEnablingUSD:
Enabling USD
------------
USD is comprised of many plugins. When USD enabled application starts up it looks for an environment variable called
``PXR_PLUGINPATH_NAME``. Below is an example where we build MuJoCo with USD enabled and set this variable.
If USD was built with the third_party_deps/openusd CMake project, you can enable USD support with the MUJOCO_WITH_USD
flag.
.. code-block:: bash
cd ~/mujoco/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DUSD_DIR=/path/to/my_usd_install_dir
cmake --build . -j 30; sudo cmake --install .
export PXR_PLUGINPATH_NAME=/usr/local/lib/mujocoUsd/resources/*/plugInfo.json
cd ~/mujoco
cmake -Bbuild -S. -DMUJOCO_WITH_USD=True
cmake --build build -j 64
Otherwise, if you have a pre-built USD library, you must also pass the pxr_DIR flag.
.. code-block:: bash
cd ~/mujoco
cmake -Bbuild -S. -DMUJOCO_WITH_USD=True -Dpxr_DIR=/path/to/my_usd_install_dir
cmake --build build -j 64
If we now run :ref:`simulate.cc <saSimulate>`, we will be able to drag and drop USD files.
.. code-block:: bash
simulate
Enabling plugins in Houdini
---------------------------
Houdini is a procedural content authoring tool with extensive support for USD workflows via their Solaris context. It's
highly popular in the VFX industry, and it's easy to imagine procedural generation tools for simulation ready assets and
scenes.
To allow support for loading MJCF files in Solaris, and usage of the mjcPhysics schemas you can build against Houdini's
USD libraries. To do so, simply run `source ./houdini_setup` as descrived in the `SideFX documentation
<https://www.sidefx.com/faq/question/how-do-i-set-up-the-houdini-environment-for-command-line-tools>`__.
.. code-block:: bash
cd ~/mujoco/build
cmake .. -DCMAKE_BUILD_TYPE=Release -DHOUDINI_HFS_DIR=$HFS
cmake --build . -j 30; sudo cmake --install .
export PXR_PLUGINPATH_NAME=/usr/local/lib/mujocoUsd/resources/*/plugInfo.json
houdini
+7
View File
@@ -39,6 +39,13 @@ General
a silent implicit cast, now negativity will trigger an error.
- Added a :ref:`depth<mjtRndFlag>` rendering flag.
- :doc:`OpenUSD <OpenUSD/index>`:
- Parsing has been moved out of experimental into a mjpDecoder plugin. (documentation pending)
- OpenUSD can now be built with the `third_party_deps/openusd` <https://github.com/google-deepmind/mujoco/tree/main/cmake/third_party_deps/openusd> CMake utility project.
- ``USD_DIR`` is no longer used by the MuJoCo CMake project, instead use ``pxr_DIR`` if you have a pre-built USD library.
- Users no longer have to set ``PXR_PLUGINPATH_NAME`` environment variable, MuJoCo should load USD plugins automatically.
MJX
^^^
- Added ``actuator_length``, ``cdof`` and ``cdof_dof`` fields to ``mjx.Data``.
-44
View File
@@ -1,44 +0,0 @@
// 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_EXPERIMENTAL_SRC_USD_USD_TO_MJSPEC_H_
#define MUJOCO_EXPERIMENTAL_SRC_USD_USD_TO_MJSPEC_H_
#include <mujoco/mujoco.h>
#include <pxr/usd/usd/common.h>
// Given a USD file parse to a spec, then compile and return the low-level
// model.
//
// Particular care is taken for physics data to be lossless but visual
// data such as materials may be lossy.
MJAPI mjModel* mj_loadUSD(const char* filename, const mjVFS* vfs, char* error,
int error_sz);
// Given a USD layer identifier, this function will do a best effort conversion
// from the composed stage to mjSpec.
//
// Particular care is taken for physics data to be lossless but visual
// data such as materials may be lossy.
MJAPI mjSpec* mj_parseUSD(const char* identifier, const mjVFS* vfs, char* error,
int error_sz);
// Given a USD stage, this function will do a best effort conversion to
// mjSpec.
//
// Particular care is taken for physics data to be lossless but visual
// data such as materials may be lossy.
MJAPI mjSpec* mj_parseUSDStage(pxr::UsdStageRefPtr stage);
#endif // MUJOCO_EXPERIMENTAL_SRC_USD_USD_TO_MJSPEC_H_
+53
View File
@@ -0,0 +1,53 @@
# Copyright 2026 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
#
# https://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.
find_package(pxr REQUIRED)
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
set(USD_DECODER_SRCS
usd_decoder.cc
kinematic_tree.cc
kinematic_tree.h
material_parsing.cc
material_parsing.h
utils.h
)
add_library(usd_decoder_plugin SHARED ${USD_DECODER_SRCS})
target_include_directories(usd_decoder_plugin PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../..
)
target_link_libraries(usd_decoder_plugin PRIVATE
mujoco
mjcPhysics
)
target_compile_options(usd_decoder_plugin PRIVATE
${AVX_COMPILE_OPTIONS}
${MUJOCO_MACOS_COMPILE_OPTIONS}
${EXTRA_COMPILE_OPTIONS}
${MUJOCO_CXX_FLAGS}
-Wno-deprecated # pxr Tf lib uses deprecated header
)
target_link_options(usd_decoder_plugin PRIVATE
${MUJOCO_MACOS_LINK_OPTIONS}
${EXTRA_LINK_OPTIONS}
)
# Install to mujoco_plugin directory in bin location so that it is picked up by simulate
# on startup.
install(
TARGETS usd_decoder_plugin
LIBRARY DESTINATION "${CMAKE_INSTALL_BINDIR}/mujoco_plugin"
)
target_link_libraries(usd_decoder_plugin PRIVATE usd usdGeom usdPhysics usdShade gf tf ar vt kind)
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "experimental/usd/kinematic_tree.h"
#include "kinematic_tree.h"
#include <map>
#include <memory>
@@ -34,9 +34,6 @@
#include <pxr/usd/usdPhysics/rigidBodyAPI.h>
#include <pxr/usd/usdPhysics/scene.h>
namespace mujoco {
namespace usd {
bool GetJointBodies(const pxr::UsdPhysicsJoint& joint, pxr::SdfPath* from,
pxr::SdfPath* to) {
// Grab the default prim path
@@ -274,6 +271,3 @@ std::unique_ptr<Node> BuildKinematicTree(const pxr::UsdStageRefPtr stage) {
}
return world_root;
}
} // namespace usd
} // namespace mujoco
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_KINEMATIC_TREE_H_
#define MUJOCO_SRC_EXPERIMENTAL_USD_KINEMATIC_TREE_H_
#ifndef MUJOCO_PLUGIN_USD_DECODER_KINEMATIC_TREE_H_
#define MUJOCO_PLUGIN_USD_DECODER_KINEMATIC_TREE_H_
#include <memory>
#include <vector>
@@ -21,9 +21,6 @@
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usdPhysics/joint.h>
namespace mujoco {
namespace usd {
// A struct to represent a node in the kinematic tree.
// Using a struct with a vector of children preserves the order of bodies,
// which is important for things like keyframes and policy compatibility.
@@ -51,7 +48,4 @@ using JointVec = std::vector<pxr::UsdPhysicsJoint>;
// Returns the root of the kinematic tree, or `nullptr` for invalid structures.
std::unique_ptr<Node> BuildKinematicTree(const pxr::UsdStageRefPtr stage);
} // namespace usd
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_KINEMATIC_TREE_H_
#endif // MUJOCO_PLUGIN_USD_DECODER_KINEMATIC_TREE_H_
@@ -12,17 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include "experimental/usd/material_parsing.h"
#include "material_parsing.h"
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <optional>
#include <string>
#include <vector>
#include "lodepng.h"
#include <mujoco/mujoco.h>
#include <pxr/base/gf/vec3f.h>
#include <pxr/base/gf/vec4f.h>
@@ -39,9 +34,6 @@
#include <pxr/usd/usdShade/types.h>
#include <pxr/usd/usdShade/udimUtils.h>
namespace mujoco {
namespace usd {
// Using to satisfy TF_DEFINE_PRIVATE_TOKENS macro below and avoid operating in
// PXR_NS.
using pxr::TfToken;
@@ -146,53 +138,16 @@ ResolvedShaderInput<T> ReadUsdUVTexture(mjSpec* spec,
}
auto extension = resolver.GetExtension(resolved_texture_path);
if (extension != "png") {
mju_error("MuJoCo USD Parsing only supports PNG textures: %s",
resolved_texture_path.c_str());
return out;
}
FILE* fp = fopen(resolved_texture_path.c_str(), "r");
if (fp) {
mjs_setString(texture->content_type, "image/png");
mjs_setString(texture->file, resolved_texture_path.c_str());
out.sampler = texture;
if (fp == nullptr) {
mju_error(
"USD decoder only supports assets that are available on the file "
"system");
return out;
}
std::shared_ptr<pxr::ArAsset> texture_asset =
resolver.OpenAsset(pxr::ArResolvedPath(resolved_texture_path));
size_t texture_size = texture_asset->GetSize();
uint32_t width, height;
std::vector<unsigned char> image;
lodepng::State state;
if (nchannels == 3) {
state.info_raw.colortype = LCT_RGB;
} else if (nchannels == 1) {
state.info_raw.colortype = LCT_GREY;
} else {
mju_error("MuJoCo USD Parsing only supports 1 or 3 channel textures.");
return out;
}
unsigned error = lodepng::decode(
image, width, height, state,
reinterpret_cast<const unsigned char*>(texture_asset->GetBuffer().get()),
texture_size);
// check for errors
if (error) {
mju_error("LodePNG error %u: %s", error, lodepng_error_text(error));
return out;
}
texture->width = width;
texture->height = height;
texture->nchannel = nchannels;
mjs_setBuffer(texture->data, image.data(), image.size());
mjs_setString(texture->file, resolved_texture_path.c_str());
out.sampler = texture;
return out;
}
@@ -311,5 +266,3 @@ mjsMaterial* ParseMaterial(mjSpec* spec,
return mj_mat;
}
} // namespace usd
} // namespace mujoco
@@ -12,16 +12,12 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef MUJOCO_SRC_EXPERIMENTAL_USD_MATERIAL_PARSING_H_
#define MUJOCO_SRC_EXPERIMENTAL_USD_MATERIAL_PARSING_H_
#ifndef MUJOCO_PLUGIN_USD_DECODER_MATERIAL_PARSING_H_
#define MUJOCO_PLUGIN_USD_DECODER_MATERIAL_PARSING_H_
#include <pxr/usd/usdShade/material.h>
#include <mujoco/mujoco.h>
namespace mujoco {
namespace usd {
mjsMaterial* ParseMaterial(mjSpec* spec, const pxr::UsdShadeMaterial &material);
} // namespace usd
} // namespace mujoco
#endif // MUJOCO_SRC_EXPERIMENTAL_USD_MATERIAL_PARSING_H_
#endif // MUJOCO_PLUGIN_USD_DECODER_MATERIAL_PARSING_H_
@@ -20,6 +20,7 @@
#include <numbers>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <mujoco/experimental/usd/mjcPhysics/actuator.h>
@@ -33,11 +34,9 @@
#include <mujoco/experimental/usd/mjcPhysics/siteAPI.h>
#include <mujoco/experimental/usd/mjcPhysics/tendon.h>
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
#include <mujoco/experimental/usd/usd.h>
#include <mujoco/experimental/usd/utils.h>
#include <mujoco/mujoco.h>
#include "experimental/usd/kinematic_tree.h"
#include "experimental/usd/material_parsing.h"
#include "kinematic_tree.h"
#include "material_parsing.h"
#include <pxr/base/gf/declare.h>
#include <pxr/base/gf/matrix4d.h>
#include <pxr/base/gf/rotation.h>
@@ -74,9 +73,6 @@
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
namespace mujoco {
namespace usd {
using pxr::MjcPhysicsTokens;
using pxr::TfToken;
@@ -87,6 +83,26 @@ struct UsdCaches {
std::map<pxr::SdfPath, mjsMaterial*> parsed_materials;
};
constexpr const char* kUsdPrimPathKey = "usd_primpath";
void SetUsdPrimPathUserValue(mjsElement* element,
const pxr::SdfPath& prim_path) {
// The value is a pointer to a newly allocated SdfPath, which will be deleted
// when the mjsElement is deleted.
const pxr::SdfPath* usd_primpath = new pxr::SdfPath(prim_path);
mjs_setUserValueWithCleanup(
element, kUsdPrimPathKey, usd_primpath,
[](const void* data) { delete static_cast<const pxr::SdfPath*>(data); });
}
pxr::SdfPath GetUsdPrimPathUserValue(mjsElement* element) {
const void* user_data = mjs_getUserValue(element, kUsdPrimPathKey);
if (user_data) {
return *static_cast<const pxr::SdfPath*>(user_data);
}
return pxr::SdfPath();
}
void SetDoubleArrFromGfVec3d(double* to, const pxr::GfVec3d& from) {
to[0] = from[0];
to[1] = from[1];
@@ -279,7 +295,7 @@ mjsMesh* ParseUsdMesh(mjSpec* spec, const pxr::UsdPrim& prim, mjsGeom* geom,
}
mjsMesh* mesh = mjs_addMesh(spec, nullptr);
mujoco::usd::SetUsdPrimPathUserValue(mesh->element, prim.GetPath());
SetUsdPrimPathUserValue(mesh->element, prim.GetPath());
geom->type = mjGEOM_MESH;
pxr::UsdGeomMesh usd_mesh(prim);
@@ -924,14 +940,13 @@ void ParseMjcPhysicsMeshCollisionAPI(
}
}
void ParseMjcPhysicsTendon(mjSpec* spec,
const pxr::MjcPhysicsTendon& tendon) {
void ParseMjcPhysicsTendon(mjSpec* spec, const pxr::MjcPhysicsTendon& tendon) {
pxr::UsdPrim prim = tendon.GetPrim();
pxr::UsdStageRefPtr stage = prim.GetStage();
mjsTendon* mj_tendon = mjs_addTendon(spec, nullptr);
mjs_setName(mj_tendon->element, prim.GetPath().GetAsString().c_str());
mujoco::usd::SetUsdPrimPathUserValue(mj_tendon->element, prim.GetPath());
SetUsdPrimPathUserValue(mj_tendon->element, prim.GetPath());
pxr::TfToken type;
tendon.GetTypeAttr().Get(&type);
@@ -963,25 +978,31 @@ void ParseMjcPhysicsTendon(mjSpec* spec,
wrap_targets.size());
return;
}
// Check that if we have >1 segments that the user has specified how much each segment
// contributes to the total segment length.
// Check that if we have >1 segments that the user has specified how much
// each segment contributes to the total segment length.
if (!segments.empty() && divisors.empty()) {
mju_warning(
"Spatial tendon %s has >1 segments (%d) but does not specify divisors, skipping.",
prim.GetPath().GetAsString().c_str(), *std::max_element(segments.begin(), segments.end()) + 1);
"Spatial tendon %s has >1 segments (%d) but does not specify "
"divisors, skipping.",
prim.GetPath().GetAsString().c_str(),
*std::max_element(segments.begin(), segments.end()) + 1);
return;
}
// Check that if we side site indices that we have N of them.
if (!side_site_indices.empty() && side_site_indices.size() != wrap_targets.size()) {
if (!side_site_indices.empty() &&
side_site_indices.size() != wrap_targets.size()) {
mju_warning(
"Spatial tendon %s has %lu sideSite indices but %lu wrap targets, skipping.",
prim.GetPath().GetAsString().c_str(), side_site_indices.size(), wrap_targets.size());
"Spatial tendon %s has %lu sideSite indices but %lu wrap targets, "
"skipping.",
prim.GetPath().GetAsString().c_str(), side_site_indices.size(),
wrap_targets.size());
return;
}
if (!side_site_indices.empty() && side_site_paths.empty()) {
mju_warning(
"Spatial tendon %s has %lu sideSite indices but no side sites, skipping.",
"Spatial tendon %s has %lu sideSite indices but no side sites, "
"skipping.",
prim.GetPath().GetAsString().c_str(), side_site_indices.size());
return;
}
@@ -990,7 +1011,8 @@ void ParseMjcPhysicsTendon(mjSpec* spec,
if (!coefs.empty() && coefs.size() != wrap_targets.size()) {
mju_warning(
"Spatial tendon %s has %lu coefs but %lu wrap targets, skipping.",
prim.GetPath().GetAsString().c_str(), coefs.size(), wrap_targets.size());
prim.GetPath().GetAsString().c_str(), coefs.size(),
wrap_targets.size());
}
}
@@ -998,21 +1020,25 @@ void ParseMjcPhysicsTendon(mjSpec* spec,
for (int i = 0; i < wrap_targets.size(); ++i) {
auto wrap_target = wrap_targets[i];
auto wrap_prim = stage->GetPrimAtPath(wrap_target);
// Important to check site before Imageable here because some Imageable prims are sites.
// Important to check site before Imageable here because some Imageable
// prims are sites.
if (!segments.empty()) {
int segment = segments[i];
if (segment >= divisors.size()) {
mju_warning("Tendon %s has at least %d segments but only %lu divisors, skipping.",
prim.GetPath().GetAsString().c_str(), segment + 1, divisors.size());
mju_warning(
"Tendon %s has at least %d segments but only %lu divisors, "
"skipping.",
prim.GetPath().GetAsString().c_str(), segment + 1, divisors.size());
return;
}
if (segment > last_segment) {
mjsWrap* pulley_wrap = mjs_wrapPulley(mj_tendon, divisors[segment]);
mjs_setString(pulley_wrap->info, ("Pulley between segments: " +
std::to_string(last_segment) + " and " +
std::to_string(segment)).c_str());
std::to_string(last_segment) +
" and " + std::to_string(segment))
.c_str());
}
last_segment = segment;
}
@@ -1031,13 +1057,17 @@ void ParseMjcPhysicsTendon(mjSpec* spec,
if (!side_site_indices.empty()) {
int side_site_index = side_site_indices[i];
if (side_site_index >= side_site_paths.size()) {
mju_warning("Tendon %s has side site index %d but only %lu side sites, skipping.",
prim.GetPath().GetAsString().c_str(), side_site_index, side_site_paths.size());
mju_warning(
"Tendon %s has side site index %d but only %lu side sites, "
"skipping.",
prim.GetPath().GetAsString().c_str(), side_site_index,
side_site_paths.size());
return;
}
side_site_name = side_site_paths[side_site_index].GetAsString();
}
wrap = mjs_wrapGeom(mj_tendon, wrap_target.GetAsString().c_str(), side_site_name.c_str());
wrap = mjs_wrapGeom(mj_tendon, wrap_target.GetAsString().c_str(),
side_site_name.c_str());
} else {
mju_warning("Tendon %s has an invalid wrap target type, skipping.",
prim.GetPath().GetAsString().c_str());
@@ -1226,7 +1256,7 @@ void ParseMjcPhysicsActuator(mjSpec* spec,
mjsActuator* mj_act = mjs_addActuator(spec, nullptr);
mjs_setName(mj_act->element, prim.GetPath().GetAsString().c_str());
mujoco::usd::SetUsdPrimPathUserValue(mj_act->element, prim.GetPath());
SetUsdPrimPathUserValue(mj_act->element, prim.GetPath());
auto group_attr = tran.GetGroupAttr();
if (group_attr.HasAuthoredValue()) {
@@ -1671,7 +1701,7 @@ void ParseUsdGeomGprim(mjSpec* spec, const pxr::UsdPrim& gprim,
geom->contype = 0;
geom->conaffinity = 0;
mujoco::usd::SetUsdPrimPathUserValue(geom->element, gprim.GetPath());
SetUsdPrimPathUserValue(geom->element, gprim.GetPath());
ParseDisplayColorAndOpacity(gprim, geom);
SetLocalPoseFromPrim(gprim, body_prim, geom, caches.xform_cache);
@@ -1728,7 +1758,7 @@ void ParseUsdPhysicsCollider(mjSpec* spec,
geom->contype = 1;
geom->conaffinity = 1;
mujoco::usd::SetUsdPrimPathUserValue(geom->element, prim.GetPath());
SetUsdPrimPathUserValue(geom->element, prim.GetPath());
if (prim.HasAPI<pxr::MjcPhysicsCollisionAPI>()) {
ParseMjcPhysicsCollisionAPI(geom, pxr::MjcPhysicsCollisionAPI(prim));
@@ -1810,7 +1840,7 @@ void ParseUsdPhysicsJoint(mjSpec* spec, const pxr::UsdPrim& prim, mjsBody* body,
mj_joint->type = type;
mjs_setName(mj_joint->element, prim.GetPath().GetAsString().c_str());
mujoco::usd::SetUsdPrimPathUserValue(mj_joint->element, prim.GetPath());
SetUsdPrimPathUserValue(mj_joint->element, prim.GetPath());
if (prim.IsA<pxr::UsdPhysicsRevoluteJoint>()) {
pxr::UsdPhysicsRevoluteJoint revolute(prim);
@@ -1901,7 +1931,7 @@ void ParseMjcPhysicsSite(mjSpec* spec, const pxr::MjcPhysicsSiteAPI& site_api,
site_api.GetPrim().GetPath().GetAsString().c_str());
SetLocalPoseFromPrim(site_api.GetPrim(), parent_prim, site, xform_cache);
mujoco::usd::SetUsdPrimPathUserValue(site->element, prim.GetPath());
SetUsdPrimPathUserValue(site->element, prim.GetPath());
auto group_attr = site_api.GetGroupAttr();
if (group_attr.HasAuthoredValue()) {
@@ -1949,7 +1979,7 @@ void ParseMjcPhysicsKeyframe(mjSpec* spec,
// If no time samples, we create a single keyframe.
mjsKey* key = mjs_addKey(spec);
mujoco::usd::SetUsdPrimPathUserValue(key->element, prim.GetPath());
SetUsdPrimPathUserValue(key->element, prim.GetPath());
mjs_setName(key->element, prim.GetName().GetString().c_str());
setKeyframeData(key, qpos_attr, &key->qpos);
@@ -1966,7 +1996,7 @@ void ParseMjcPhysicsKeyframe(mjSpec* spec,
for (double time : times) {
mjsKey* key = mjs_addKey(spec);
mujoco::usd::SetUsdPrimPathUserValue(key->element, prim.GetPath());
SetUsdPrimPathUserValue(key->element, prim.GetPath());
std::string key_name =
prim.GetName().GetString() + "_" + std::to_string(keyframe_id++);
@@ -1995,7 +2025,12 @@ mjsBody* ParseUsdPhysicsRigidbody(
ParseUsdPhysicsMassAPIForBody(body, pxr::UsdPhysicsMassAPI(prim));
}
mujoco::usd::SetUsdPrimPathUserValue(body->element, prim.GetPath());
// The value is a pointer to a newly allocated SdfPath, which will be deleted
// when the mjsElement is deleted.
const pxr::SdfPath* usd_primpath = new pxr::SdfPath(prim.GetPath());
mjs_setUserValueWithCleanup(
body->element, kUsdPrimPathKey, usd_primpath,
[](const void* data) { delete static_cast<const pxr::SdfPath*>(data); });
return body;
}
@@ -2080,49 +2115,45 @@ void PopulateSpecFromTree(pxr::UsdStageRefPtr stage, mjSpec* spec,
child_node.get(), caches);
}
}
} // namespace usd
} // namespace mujoco
mjSpec* mj_parseUSDStage(const pxr::UsdStageRefPtr stage) {
mjSpec* ParseStage(const pxr::UsdStageRefPtr stage) {
mjSpec* spec = mj_makeSpec();
std::unique_ptr<mujoco::usd::Node> root =
mujoco::usd::BuildKinematicTree(stage);
std::unique_ptr<Node> root = BuildKinematicTree(stage);
// First parse the physics scene and other root elements such as keyframes
// and actuators.
if (!root->physics_scene.IsEmpty()) {
mujoco::usd::ParseUsdPhysicsScene(
spec, pxr::UsdPhysicsScene::Get(stage, root->physics_scene));
ParseUsdPhysicsScene(spec,
pxr::UsdPhysicsScene::Get(stage, root->physics_scene));
} else {
// If there is no physics scene we still need to infer the gravity vector
// from the stage up axis and units per meter metadata.
mujoco::usd::SetGravityAttributes(spec, stage);
SetGravityAttributes(spec, stage);
}
if (!root->keyframes.empty()) {
for (const auto& keyframe : root->keyframes) {
mujoco::usd::ParseMjcPhysicsKeyframe(
spec, pxr::MjcPhysicsKeyframe::Get(stage, keyframe));
ParseMjcPhysicsKeyframe(spec,
pxr::MjcPhysicsKeyframe::Get(stage, keyframe));
}
}
if (!root->actuators.empty()) {
for (const auto& actuator : root->actuators) {
mujoco::usd::ParseMjcPhysicsActuator(
spec, pxr::MjcPhysicsActuator::Get(stage, actuator));
ParseMjcPhysicsActuator(spec,
pxr::MjcPhysicsActuator::Get(stage, actuator));
}
}
if (!root->tendons.empty()) {
for (const auto& tendon : root->tendons) {
mujoco::usd::ParseMjcPhysicsTendon(
spec, pxr::MjcPhysicsTendon::Get(stage, tendon));
ParseMjcPhysicsTendon(spec, pxr::MjcPhysicsTendon::Get(stage, tendon));
}
}
// Set of caches to use for all queries when parsing.
mujoco::usd::UsdCaches caches;
UsdCaches caches;
// Then populate the kinematic tree.
PopulateSpecFromTree(stage, spec, /*parent_mj_body=*/nullptr,
/*parent_node=*/nullptr, root.get(), caches);
@@ -2130,8 +2161,27 @@ mjSpec* mj_parseUSDStage(const pxr::UsdStageRefPtr stage) {
return spec;
}
mjSpec* mj_parseUSD(const char* identifier, const mjVFS* vfs, char* error,
int error_sz) {
auto stage = pxr::UsdStage::Open(identifier);
return mj_parseUSDStage(stage);
namespace {
// load 2D
mjSpec* Decode(mjResource* resource, const mjVFS* vfs) {
auto stage = pxr::UsdStage::Open(resource->name);
return ParseStage(stage);
}
int CanDecode(const mjResource* resource) {
std::string_view name(resource->name);
return name.ends_with(".usd") || name.ends_with(".usda") ||
name.ends_with(".usdc") || name.ends_with(".usdz");
}
} // namespace
// clang-format off
mjPLUGIN_LIB_INIT {
mjpDecoder decoder;
mjp_defaultDecoder(&decoder);
decoder.content_type = "model/usd";
decoder.extension = ".usd|.usda|.usdc|.usdz";
decoder.decode = Decode;
decoder.can_decode = CanDecode;
mjp_registerDecoder(&decoder);
}
+1
View File
@@ -196,6 +196,7 @@ if(SIMULATE_BUILD_EXECUTABLE)
target_link_libraries(
simulate
mujoco::usd::mjcf
usd_decoder_plugin
)
endif()
+17 -21
View File
@@ -25,9 +25,6 @@
#include <string>
#include <thread>
#if defined(mjUSEUSD)
#include <mujoco/experimental/usd/usd.h>
#endif
#include <mujoco/mujoco.h>
#include "glfw_adapter.h"
#include "simulate.h"
@@ -241,22 +238,26 @@ mjModel* LoadModel(const char* file, mj::Simulate& sim) {
if (!mnew) {
mju::strcpy_arr(loadError, "could not load binary model");
}
#if defined(mjUSEUSD)
} else if (extension == ".usda" || extension == ".usd" ||
extension == ".usdc" || extension == ".usdz" ) {
mnew = mj_loadUSD(filename, nullptr, loadError, kErrorLength);
#endif
} else {
} else if (extension == ".xml") {
mnew = mj_loadXML(filename, nullptr, loadError, kErrorLength);
// remove trailing newline character from loadError
if (loadError[0]) {
int error_length = mju::strlen_arr(loadError);
if (loadError[error_length-1] == '\n') {
loadError[error_length-1] = '\0';
}
} else {
mjSpec* spec = mj_parse(filename, nullptr, nullptr, loadError, kErrorLength);
if (!spec) {
mju::strcpy_arr(loadError, "could not parse model");
} else {
mnew = mj_compile(spec, nullptr);
mj_deleteSpec(spec);
}
}
// remove trailing newline character from loadError
if (loadError[0]) {
int error_length = mju::strlen_arr(loadError);
if (loadError[error_length-1] == '\n') {
loadError[error_length-1] = '\0';
}
}
auto load_interval = mj::Simulate::Clock::now() - load_start;
double load_seconds = Seconds(load_interval).count();
@@ -516,11 +517,6 @@ int main(int argc, char** argv) {
// scan for libraries in the plugin directory to load additional plugins
scanPluginLibraries();
#if defined(mjUSEUSD)
// If USD is used, print the version.
std::printf("OpenUSD version v%d.%02d\n", PXR_MINOR_VERSION, PXR_PATCH_VERSION);
#endif
mjvCamera cam;
mjv_defaultCamera(&cam);
+1 -5
View File
@@ -13,6 +13,7 @@
# limitations under the License.
# --- Global Configuration ---
# Plugin target names (used for library and plugInfo.json)
set(MJCF_PLUGIN_TARGET_NAME usdMjcf)
set(MJC_PHYSICS_PLUGIN_TARGET_NAME mjcPhysics)
@@ -147,12 +148,7 @@ target_include_directories(${MJC_PHYSICS_PLUGIN_TARGET_NAME} PUBLIC
## ----- mujoco USD utils -----
target_sources(mujoco PRIVATE
kinematic_tree.cc
kinematic_tree.h
layer_sink.cc
material_parsing.cc
material_parsing.h
usd_to_mjspec.cc
utils.cc
writer.cc
)
-36
View File
@@ -31,12 +31,6 @@
#include "xml/xml_global.h"
#include "xml/xml_native_reader.h"
#include "xml/xml_util.h"
#if defined(mjUSEUSD)
#include <mujoco/experimental/usd/usd.h>
#include <pxr/usd/usd/stage.h>
#endif
//---------------------------------- Functions -----------------------------------------------------
@@ -75,36 +69,6 @@ mjModel* mj_loadXML(const char* filename, const mjVFS* vfs,
return m;
}
#if defined(mjUSEUSD)
// parse USD file, compile it, and return low-level model.
// if vfs is not NULL, look up files in vfs before reading from disk
// error can be NULL; otherwise assumed to have size error_sz
mjModel* mj_loadUSD(const char* filename, const mjVFS* vfs, char* error, int error_sz) {
auto stage = pxr::UsdStage::Open(filename);
if (stage == nullptr) {
mjCopyError(error, "Failed to load USD stage from file.", error_sz);
return nullptr;
}
// Parse USD into mjSpec.
std::unique_ptr<mjSpec, std::function<void(mjSpec*)> > spec(
mj_parseUSDStage(stage),
[](mjSpec* s) {
mj_deleteSpec(s);
});
// Compile new model.
mjModel* m = mj_compile(spec.get(), vfs);
if (!m) {
mjCopyError(error, mjs_getError(spec.get()), error_sz);
return nullptr;
}
SetGlobalXmlSpec(spec.release());
return m;
}
#endif
// update XML data structures with info from low-level model, save as MJCF
// returns 1 if successful, 0 otherwise
+1 -14
View File
@@ -45,9 +45,6 @@
#include "xml/xml_base.h"
#include "xml/xml_util.h"
#include "tinyxml2.h"
#ifdef mjUSEUSD
#include <mujoco/experimental/usd/usd.h>
#endif // mjUSEUSD
namespace {
using std::string;
@@ -3446,21 +3443,11 @@ void mjXReader::Asset(XMLElement* section, const mjVFS* vfs) {
ReadAttrTxt(elem, "content_type", content_type);
// parse the child
mjSpec* child = nullptr;
std::array<char, 1024> error;
auto filename = modelfiledir_ + ReadAttrFile(elem, "file", vfs).value();
#ifdef mjUSEUSD
if (content_type == "text/usd") {
child = mj_parseUSD(filename.c_str(), vfs, error.data(), error.size());
} else {
#endif // mjUSEUSD
child = mj_parse(filename.c_str(), content_type.c_str(), vfs,
mjSpec* child = mj_parse(filename.c_str(), content_type.c_str(), vfs,
error.data(), error.size());
#ifdef mjUSEUSD
}
#endif // mjUSEUSD
if (!child) {
throw mjXError(elem, "could not parse model file with error: %s", error.data());
}
@@ -12,10 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#include <array>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mujoco/experimental/usd/mjcPhysics/siteAPI.h>
#include <mujoco/experimental/usd/usd.h>
#include <mujoco/mujoco.h>
#include "test/fixture.h"
#include <pxr/base/tf/token.h>
@@ -79,7 +79,9 @@ TEST_F(MjcSiteApiTest, TestApply) {
auto box = pxr::UsdGeomCube::Define(stage, test_box_site_path);
MjcPhysicsSiteAPI::Apply(box.GetPrim());
mjSpec* spec = mj_parseUSDStage(stage);
std::array<char, 1024> error;
mjSpec* spec = mj_parse(stage->GetRootLayer()->GetIdentifier().c_str(),
"model/usd", nullptr, error.data(), error.size());
mjModel* default_model = mj_compile(spec, nullptr);
EXPECT_THAT(default_model, NotNull()) << mjs_getError(spec);