mj_usd plugin: add joints export.
Note that ball joint is missing, will come later. PiperOrigin-RevId: 776057523 Change-Id: I8d13c3ea9e7f3d894af73a4640eb93202f8a2a03
This commit is contained in:
committed by
Copybara-Service
parent
48205fb570
commit
3441e0aa03
@@ -21,9 +21,9 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
|
||||
#include <mujoco/mjspec.h>
|
||||
#include <mujoco/mujoco.h>
|
||||
#include <mujoco/experimental/usd/mjcPhysics/tokens.h>
|
||||
#include "mjcf/utils.h"
|
||||
#include <pxr/base/arch/attributes.h>
|
||||
#include <pxr/base/gf/matrix4d.h>
|
||||
@@ -55,6 +55,11 @@
|
||||
#include <pxr/usd/usdGeom/metrics.h>
|
||||
#include <pxr/usd/usdGeom/tokens.h>
|
||||
#include <pxr/usd/usdLux/tokens.h>
|
||||
#include <pxr/usd/usdPhysics/fixedJoint.h>
|
||||
#include <pxr/usd/usdPhysics/joint.h>
|
||||
#include <pxr/usd/usdPhysics/prismaticJoint.h>
|
||||
#include <pxr/usd/usdPhysics/revoluteJoint.h>
|
||||
#include <pxr/usd/usdPhysics/sphericalJoint.h>
|
||||
#include <pxr/usd/usdPhysics/tokens.h>
|
||||
#include <pxr/usd/usdShade/tokens.h>
|
||||
#include <pxr/usdImaging/usdImaging/tokens.h>
|
||||
@@ -102,6 +107,7 @@ TF_DEFINE_PRIVATE_TOKENS(kTokens,
|
||||
(repeat)
|
||||
((sourceMesh, pxr::UsdGeomTokens->Mesh))
|
||||
((inputsNormal, "inputs:normal"))
|
||||
((joint, "Joint"))
|
||||
);
|
||||
|
||||
// Using to satisfy TF_REGISTRY_FUNCTION macro below and avoid operating in PXR_NS.
|
||||
@@ -169,6 +175,9 @@ class ModelWriter {
|
||||
~ModelWriter() { mj_deleteModel(model_); }
|
||||
|
||||
void Write(bool write_physics) {
|
||||
// Set working parameters.
|
||||
write_physics_ = write_physics;
|
||||
|
||||
// Create top level class holder.
|
||||
class_path_ = CreateClassSpec(data_, pxr::SdfPath::AbsoluteRootPath(),
|
||||
pxr::TfToken("__class__"));
|
||||
@@ -176,9 +185,6 @@ class ModelWriter {
|
||||
// Create the world body.
|
||||
body_paths_[kWorldIndex] = WriteWorldBody(kWorldIndex);
|
||||
|
||||
// Set working parameters.
|
||||
write_physics_ = write_physics;
|
||||
|
||||
SetLayerMetadata(data_, pxr::SdfFieldKeys->Documentation,
|
||||
"Generated by mujoco model writer.");
|
||||
// Mujoco is Z up by default.
|
||||
@@ -1494,6 +1500,219 @@ class ModelWriter {
|
||||
}
|
||||
}
|
||||
|
||||
void WriteJoints(mjsBody *body) {
|
||||
if (!write_physics_) return;
|
||||
|
||||
int body_id = mjs_getId(body->element);
|
||||
if (body_id == kWorldIndex) return;
|
||||
|
||||
mjsJoint *joint = mjs_asJoint(mjs_firstChild(body, mjOBJ_JOINT, false));
|
||||
|
||||
if (!joint) {
|
||||
// If no joint is found, then we pass nullptr to create a FixedJoint.
|
||||
// WriteJoint properly handles the case where the parent is the worldbody.
|
||||
WriteJoint(nullptr, body);
|
||||
} else {
|
||||
WriteJoint(joint, body);
|
||||
if (mjs_asJoint(mjs_nextChild(body, joint->element, false))) {
|
||||
TF_WARN(
|
||||
"Multiple joints found for body %d. Only writing the first one.",
|
||||
body_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write the joint. If null, then a FixedJoint is created.
|
||||
void WriteJoint(mjsJoint *joint, const mjsBody *parent_mj_body) {
|
||||
// Default to fixed joint if joint is null.
|
||||
pxr::TfToken joint_prim_type = pxr::UsdPhysicsTokens->PhysicsFixedJoint;
|
||||
|
||||
int joint_id = -1;
|
||||
if (joint) {
|
||||
joint_id = mjs_getId(joint->element);
|
||||
mjtJoint type = (mjtJoint)model_->jnt_type[joint_id];
|
||||
switch (type) {
|
||||
case mjJNT_FREE:
|
||||
// Free joints are guaranteed to only ever be on the top-level body so
|
||||
// we just write no joint. As a top-level body with no joint it will
|
||||
// be considered a floating-base body.
|
||||
return;
|
||||
case mjJNT_HINGE:
|
||||
joint_prim_type = pxr::UsdPhysicsTokens->PhysicsRevoluteJoint;
|
||||
break;
|
||||
case mjJNT_SLIDE:
|
||||
joint_prim_type = pxr::UsdPhysicsTokens->PhysicsPrismaticJoint;
|
||||
break;
|
||||
default:
|
||||
TF_WARN("Unsupported joint type '%d' for joint '%s'. Skipping.",
|
||||
(int)type, joint->name->c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int body_id = mjs_getId(parent_mj_body->element);
|
||||
|
||||
// the joint connects the current body as body1, to its parent body as
|
||||
// body0.
|
||||
int body1_id_usd = body_id;
|
||||
int body0_id_usd = model_->body_parentid[body_id];
|
||||
|
||||
const pxr::SdfPath &body1_path_usd = body_paths_[body1_id_usd];
|
||||
auto joint_name = joint ? *joint->name : "FixedJoint";
|
||||
pxr::TfToken joint_name_token =
|
||||
GetAvailablePrimName(joint_name, kTokens->joint, body1_path_usd);
|
||||
pxr::SdfPath joint_path = CreatePrimSpec(data_, body1_path_usd,
|
||||
joint_name_token, joint_prim_type);
|
||||
|
||||
// Set body0 and body1 relationships
|
||||
// For the initial joints that connect to the world, we signal this by
|
||||
// keeping the body0 relationship empty.
|
||||
if (body0_id_usd != kWorldIndex) {
|
||||
const pxr::SdfPath &body0_path_usd = body_paths_[body0_id_usd];
|
||||
CreateRelationshipSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsBody0,
|
||||
body0_path_usd, pxr::SdfVariabilityUniform);
|
||||
}
|
||||
CreateRelationshipSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsBody1, body1_path_usd,
|
||||
pxr::SdfVariabilityUniform);
|
||||
|
||||
// Joint frame in MuJoCo is defined by jnt_pos and jnt_axis in body1's frame
|
||||
// For FixedJoint, these are both unity.
|
||||
pxr::GfVec3d mj_jnt_pos = pxr::GfVec3d(0.0);
|
||||
pxr::GfVec3d mj_jnt_axis = pxr::GfVec3d(0.0, 0.0, 1.0);
|
||||
if (joint) {
|
||||
mj_jnt_pos = pxr::GfVec3d(&model_->jnt_pos[joint_id * 3]);
|
||||
mj_jnt_axis = pxr::GfVec3d(&model_->jnt_axis[joint_id * 3]);
|
||||
}
|
||||
|
||||
// Local joint frame for body1
|
||||
pxr::GfVec3f local_pos1(mj_jnt_pos);
|
||||
pxr::GfRotation().SetRotateInto(pxr::GfVec3f::ZAxis(), mj_jnt_axis);
|
||||
pxr::GfQuatf local_rot1(
|
||||
pxr::GfRotation()
|
||||
.SetRotateInto(pxr::GfVec3f::ZAxis(), mj_jnt_axis)
|
||||
.GetQuat());
|
||||
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLocalPos1,
|
||||
pxr::SdfValueTypeNames->Float3),
|
||||
local_pos1);
|
||||
if (joint_prim_type == pxr::UsdPhysicsTokens->PhysicsRevoluteJoint ||
|
||||
joint_prim_type == pxr::UsdPhysicsTokens->PhysicsPrismaticJoint) {
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLocalRot1,
|
||||
pxr::SdfValueTypeNames->Quatf),
|
||||
local_rot1);
|
||||
} else {
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLocalRot1,
|
||||
pxr::SdfValueTypeNames->Quatf),
|
||||
pxr::GfQuatf::GetIdentity());
|
||||
}
|
||||
|
||||
// Calculate local joint frame for body0
|
||||
pxr::GfMatrix4d body1_transform_local =
|
||||
MujocoPosQuatToTransform(&model_->body_pos[body1_id_usd * 3],
|
||||
&model_->body_quat[body1_id_usd * 4]);
|
||||
pxr::GfVec3d jnt_pos_parent_local =
|
||||
body1_transform_local.Transform(mj_jnt_pos);
|
||||
pxr::GfVec3d jnt_axis_parent_local =
|
||||
body1_transform_local.TransformDir(mj_jnt_axis);
|
||||
|
||||
pxr::GfVec3f local_pos0(jnt_pos_parent_local);
|
||||
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLocalPos0,
|
||||
pxr::SdfValueTypeNames->Float3),
|
||||
local_pos0);
|
||||
|
||||
if (joint_prim_type == pxr::UsdPhysicsTokens->PhysicsRevoluteJoint ||
|
||||
joint_prim_type == pxr::UsdPhysicsTokens->PhysicsPrismaticJoint) {
|
||||
pxr::GfQuatf other_rot0(
|
||||
pxr::GfRotation()
|
||||
.SetRotateInto(pxr::GfVec3f::ZAxis(), jnt_axis_parent_local)
|
||||
.GetQuat());
|
||||
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLocalRot0,
|
||||
pxr::SdfValueTypeNames->Quatf),
|
||||
other_rot0);
|
||||
} else {
|
||||
// Fixed joints have no frame and no axis per se. We simply need the
|
||||
// rotation quaternion of the body its on.
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLocalRot0,
|
||||
pxr::SdfValueTypeNames->Quatf),
|
||||
body1_transform_local.ExtractRotationQuat());
|
||||
}
|
||||
|
||||
if (joint) {
|
||||
mjtJoint type = (mjtJoint)model_->jnt_type[joint_id];
|
||||
|
||||
// Joint-specific attributes
|
||||
if (type == mjJNT_HINGE || type == mjJNT_SLIDE) {
|
||||
// The joint motion occurs around/along the Z-axis of the joint frame
|
||||
// established by localRot0/1.
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsAxis,
|
||||
pxr::SdfValueTypeNames->Token),
|
||||
pxr::UsdPhysicsTokens->z); // "Z" axis
|
||||
}
|
||||
|
||||
if (model_->jnt_limited[joint_id]) {
|
||||
float lower_limit = model_->jnt_range[joint_id * 2];
|
||||
float upper_limit = model_->jnt_range[joint_id * 2 + 1];
|
||||
|
||||
if (type == mjJNT_HINGE) {
|
||||
// Convert radians to degrees for USD
|
||||
// As per the XML Reference, "mjModel always uses radians"
|
||||
lower_limit *= (180.0 / mjPI);
|
||||
upper_limit *= (180.0 / mjPI);
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLowerLimit,
|
||||
pxr::SdfValueTypeNames->Float),
|
||||
lower_limit);
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsUpperLimit,
|
||||
pxr::SdfValueTypeNames->Float),
|
||||
upper_limit);
|
||||
} else if (type == mjJNT_SLIDE) {
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsLowerLimit,
|
||||
pxr::SdfValueTypeNames->Float),
|
||||
lower_limit);
|
||||
SetAttributeDefault(
|
||||
data_,
|
||||
CreateAttributeSpec(data_, joint_path,
|
||||
pxr::UsdPhysicsTokens->physicsUpperLimit,
|
||||
pxr::SdfValueTypeNames->Float),
|
||||
upper_limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteCamera(mjsCamera *spec_cam, const mjsBody *body) {
|
||||
const auto &body_path = body_paths_[mjs_getId(body->element)];
|
||||
auto name = GetAvailablePrimName(*spec_cam->name,
|
||||
@@ -1635,17 +1854,6 @@ class ModelWriter {
|
||||
|
||||
ApplyApiSchema(data_, body_path,
|
||||
pxr::UsdPhysicsTokens->PhysicsRigidBodyAPI);
|
||||
|
||||
// If the parent is not the world body, but is child of the world body
|
||||
// then we need to apply the articulation root API.
|
||||
if (parent_id != kWorldIndex) {
|
||||
int parent_parent_id =
|
||||
mjs_getId(mjs_getParent(parent->element)->element);
|
||||
if (parent_parent_id == kWorldIndex) {
|
||||
ApplyApiSchema(data_, parent_path,
|
||||
pxr::UsdPhysicsTokens->PhysicsArticulationRootAPI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create classes if necessary
|
||||
@@ -1692,6 +1900,7 @@ class ModelWriter {
|
||||
}
|
||||
WriteSites(body);
|
||||
WriteGeoms(body);
|
||||
WriteJoints(body);
|
||||
WriteCameras(body);
|
||||
WriteLights(body);
|
||||
body = mjs_asBody(mjs_nextElement(spec_, body->element));
|
||||
@@ -1707,6 +1916,14 @@ class ModelWriter {
|
||||
CreatePrimSpec(data_, pxr::SdfPath::AbsoluteRootPath(), name,
|
||||
pxr::UsdGeomTokens->Xform);
|
||||
SetPrimKind(data_, world_group_path, pxr::KindTokens->group);
|
||||
|
||||
if (write_physics_) {
|
||||
// Apply the PhysicsArticulationRootAPI to the world body so that
|
||||
// everything under it is automatically considered an articulation.
|
||||
ApplyApiSchema(data_, world_group_path,
|
||||
pxr::UsdPhysicsTokens->PhysicsArticulationRootAPI);
|
||||
}
|
||||
|
||||
return world_group_path;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user