a5dc57c0c3
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
274 lines
8.9 KiB
C++
274 lines
8.9 KiB
C++
// 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 "kinematic_tree.h"
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <mujoco/experimental/usd/mjcPhysics/actuator.h>
|
|
#include <mujoco/experimental/usd/mjcPhysics/keyframe.h>
|
|
#include <mujoco/experimental/usd/mjcPhysics/siteAPI.h>
|
|
#include <mujoco/experimental/usd/mjcPhysics/tendon.h>
|
|
#include <mujoco/mujoco.h>
|
|
#include <pxr/usd/sdf/path.h>
|
|
#include <pxr/usd/usd/common.h>
|
|
#include <pxr/usd/usd/primRange.h>
|
|
#include <pxr/usd/usdGeom/gprim.h>
|
|
#include <pxr/usd/usdGeom/xformCache.h>
|
|
#include <pxr/usd/usdPhysics/collisionAPI.h>
|
|
#include <pxr/usd/usdPhysics/joint.h>
|
|
#include <pxr/usd/usdPhysics/rigidBodyAPI.h>
|
|
#include <pxr/usd/usdPhysics/scene.h>
|
|
|
|
bool GetJointBodies(const pxr::UsdPhysicsJoint& joint, pxr::SdfPath* from,
|
|
pxr::SdfPath* to) {
|
|
// Grab the default prim path
|
|
pxr::SdfPath default_prim_path;
|
|
auto stage = joint.GetPrim().GetStage();
|
|
if (stage->GetDefaultPrim().IsValid()) {
|
|
default_prim_path = stage->GetDefaultPrim().GetPath();
|
|
}
|
|
|
|
pxr::SdfPathVector body1_paths;
|
|
joint.GetBody1Rel().GetTargets(&body1_paths);
|
|
if (body1_paths.empty()) {
|
|
mju_warning("Joint %s does not have body1 rel. Skipping.",
|
|
joint.GetPath().GetAsString().c_str());
|
|
return false;
|
|
} else if (body1_paths.size() > 1) {
|
|
mju_warning("Joint %s has multiple body1 rels. Skipping.",
|
|
joint.GetPath().GetAsString().c_str());
|
|
return false;
|
|
}
|
|
*to = body1_paths[0];
|
|
|
|
pxr::SdfPathVector body0_paths;
|
|
joint.GetBody0Rel().GetTargets(&body0_paths);
|
|
if (body0_paths.size() > 1) {
|
|
mju_warning("Joint %s has multiple body0 rels. Skipping.",
|
|
joint.GetPath().GetAsString().c_str());
|
|
return false;
|
|
}
|
|
// Empty body0, or body0 pointing to the default prim means we'll attach
|
|
// to the worldbody.
|
|
if (body0_paths.empty() || body0_paths[0] == default_prim_path) {
|
|
*from = pxr::SdfPath();
|
|
} else {
|
|
*from = body0_paths[0];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct ExtractedPrims {
|
|
std::vector<std::unique_ptr<Node>> nodes;
|
|
std::vector<pxr::UsdPhysicsJoint> joints;
|
|
};
|
|
|
|
ExtractedPrims ExtractPrims(pxr::UsdStageRefPtr stage) {
|
|
pxr::UsdPhysicsScene physics_scene;
|
|
std::vector<std::unique_ptr<Node>> nodes;
|
|
std::vector<pxr::UsdPhysicsJoint> joints;
|
|
nodes.push_back(std::make_unique<Node>());
|
|
Node* root = nodes.back().get();
|
|
|
|
// =========================================================================
|
|
// PASS 1: Collect Bodies, Joints, and Geoms/Sites/etc.
|
|
// =========================================================================
|
|
// A single DFS pass to find all bodies, joints, and determine
|
|
// which body owns each geom/site/etc. prim.
|
|
std::vector<Node*> owner_stack;
|
|
owner_stack.push_back(root); // Start with the world as owner.
|
|
|
|
const auto range = pxr::UsdPrimRange::PreAndPostVisit(
|
|
stage->GetPseudoRoot(), pxr::UsdTraverseInstanceProxies());
|
|
|
|
pxr::UsdGeomXformCache xform_cache;
|
|
for (auto it = range.begin(); it != range.end(); ++it) {
|
|
pxr::UsdPrim prim = *it;
|
|
|
|
bool is_body = prim.HasAPI<pxr::UsdPhysicsRigidBodyAPI>();
|
|
bool resets = 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;
|
|
|
|
if (it.IsPostVisit()) {
|
|
if (is_pushed_to_stack) {
|
|
owner_stack.pop_back();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
pxr::SdfPath prim_path = prim.GetPath();
|
|
Node* current_node = owner_stack.back();
|
|
|
|
if (is_body) {
|
|
auto new_node = std::make_unique<Node>();
|
|
new_node->body_path = prim_path;
|
|
nodes.push_back(std::move(new_node));
|
|
current_node = nodes.back().get();
|
|
} else if (resets) {
|
|
current_node = root; // Reset owner to world.
|
|
}
|
|
|
|
if (is_pushed_to_stack) {
|
|
owner_stack.push_back(current_node);
|
|
}
|
|
|
|
if (prim.IsA<pxr::UsdPhysicsScene>() && root->physics_scene.IsEmpty()) {
|
|
root->physics_scene = prim_path;
|
|
}
|
|
|
|
if (prim.IsA<pxr::UsdGeomGprim>()) {
|
|
bool has_collision_api = prim.HasAPI<pxr::UsdPhysicsCollisionAPI>();
|
|
if (has_collision_api) {
|
|
bool collision_enabled = false;
|
|
pxr::UsdPhysicsCollisionAPI(prim).GetCollisionEnabledAttr().Get(
|
|
&collision_enabled);
|
|
if (collision_enabled) {
|
|
current_node->colliders.push_back(prim_path);
|
|
} else {
|
|
current_node->visual_gprims.push_back(prim_path);
|
|
}
|
|
} else {
|
|
current_node->visual_gprims.push_back(prim_path);
|
|
}
|
|
}
|
|
|
|
if (prim.HasAPI<pxr::MjcPhysicsSiteAPI>()) {
|
|
current_node->sites.push_back(prim_path);
|
|
// Sites should not have children.
|
|
it.PruneChildren();
|
|
}
|
|
|
|
if (prim.IsA<pxr::UsdPhysicsJoint>()) {
|
|
// We may not know which body this belongs to yet so we'll add it to a
|
|
// list and the caller can assign the joints when building the tree.
|
|
joints.push_back(pxr::UsdPhysicsJoint(prim));
|
|
// Joints should not have children.
|
|
it.PruneChildren();
|
|
}
|
|
|
|
if (prim.IsA<pxr::MjcPhysicsActuator>()) {
|
|
root->actuators.push_back(prim_path);
|
|
// Joints should not have children.
|
|
it.PruneChildren();
|
|
}
|
|
|
|
if (prim.IsA<pxr::MjcPhysicsTendon>()) {
|
|
root->tendons.push_back(prim_path);
|
|
// Tendons should not have children of interest.
|
|
it.PruneChildren();
|
|
}
|
|
|
|
if (prim.IsA<pxr::MjcPhysicsKeyframe>()) {
|
|
root->keyframes.push_back(prim_path);
|
|
// Keyframes should not have children.
|
|
it.PruneChildren();
|
|
}
|
|
}
|
|
return {std::move(nodes), std::move(joints)};
|
|
}
|
|
|
|
std::unique_ptr<Node> BuildKinematicTree(const pxr::UsdStageRefPtr stage) {
|
|
ExtractedPrims extraction = ExtractPrims(stage);
|
|
|
|
std::map<pxr::SdfPath, int> body_index;
|
|
body_index[pxr::SdfPath()] = 0;
|
|
for (int i = 0; i < extraction.nodes.size(); ++i) {
|
|
body_index[extraction.nodes[i]->body_path] = i;
|
|
}
|
|
|
|
// List of direct children for each body.
|
|
std::vector<std::vector<bool>> children(
|
|
extraction.nodes.size(), std::vector<bool>(extraction.nodes.size()));
|
|
// List of joint prim paths associated with a child for each body.
|
|
std::vector<std::vector<pxr::SdfPath>> parent_joints(extraction.nodes.size());
|
|
for (const pxr::UsdPhysicsJoint& joint : extraction.joints) {
|
|
pxr::SdfPath from, to;
|
|
if (!GetJointBodies(joint, &from, &to)) {
|
|
continue;
|
|
}
|
|
|
|
int from_idx = body_index[from];
|
|
int to_idx = body_index[to];
|
|
if (from_idx == to_idx) {
|
|
mju_error("Cycle detected: self referencing joint at node %s",
|
|
to.GetString().c_str());
|
|
return nullptr;
|
|
}
|
|
|
|
children[from_idx][to_idx] = true;
|
|
parent_joints[to_idx].push_back(joint.GetPath());
|
|
// Now that we know all the bodies, we can assign joints to respective
|
|
// nodes.
|
|
extraction.nodes[to_idx]->joints.push_back(joint.GetPath());
|
|
}
|
|
|
|
// The world body is represented by an empty SdfPath.
|
|
auto world_root = std::move(extraction.nodes[0]);
|
|
|
|
std::vector<std::pair<int, Node*>> stack;
|
|
stack.emplace_back(0, world_root.get());
|
|
|
|
// A node without any joints from a parent has a free joint.
|
|
// Add all free joints as children of the world body.
|
|
for (int i = 1; i < extraction.nodes.size(); ++i) {
|
|
if (parent_joints[i].empty()) {
|
|
children[0][i] = true;
|
|
}
|
|
}
|
|
|
|
std::vector<bool> visited(extraction.nodes.size());
|
|
while (!stack.empty()) {
|
|
auto [current_body, parent] = stack.back();
|
|
stack.pop_back();
|
|
visited[current_body] = true;
|
|
|
|
Node* current_node = nullptr;
|
|
if (current_body > 0) {
|
|
parent->children.push_back(std::move(extraction.nodes[current_body]));
|
|
current_node = parent->children.back().get();
|
|
} else {
|
|
current_node = world_root.get();
|
|
}
|
|
|
|
// Process children in reverse to maintain DFS.
|
|
for (int i = extraction.nodes.size() - 1; i > 0; --i) {
|
|
if (!children[current_body][i]) {
|
|
continue;
|
|
}
|
|
if (visited[i]) {
|
|
mju_error("Cycle detected in the kinematic tree at node %s",
|
|
current_node->body_path.GetString().c_str());
|
|
return nullptr;
|
|
}
|
|
stack.emplace_back(
|
|
i, current_body > 0 ? parent->children.back().get() : parent);
|
|
}
|
|
}
|
|
|
|
for (int i = 1; i < visited.size(); ++i) {
|
|
if (!visited[i]) {
|
|
mju_error("Cycle detected: Node %s is not reachable from the world.",
|
|
extraction.nodes[i]->body_path.GetString().c_str());
|
|
return nullptr;
|
|
}
|
|
}
|
|
return world_root;
|
|
}
|