Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support duplicate blendshape target names #1179

Merged
2 changes: 2 additions & 0 deletions lib/mayaUsd/fileio/jobs/writeJob.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,8 @@ bool UsdMaya_WriteJob::_BeginWriting(const std::string& fileName, bool append)
}
}

this->mJobCtx.mBlendShapesAnimWeightPlugs.clear();

// Now do a depth-first traversal of the Maya DAG from the world root.
// We keep a reference to arg dagPaths as we encounter them.
MDagPath curLeafDagPath;
Expand Down
7 changes: 7 additions & 0 deletions lib/mayaUsd/fileio/writeJobContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <maya/MDagPath.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MObjectHandle.h>
#include <maya/MPlugArray.h>

#include <memory>
#include <vector>
Expand Down Expand Up @@ -134,6 +135,12 @@ class UsdMayaWriteJobContext
const VtVec3fArray& bbox,
const UsdTimeCode& timeSample);

/// Used to cache the animated blend shape weight plugs that need to be sampled per-frame.
/// This is cached at the writeJob-level because the state needs to persist across instances of
/// the meshWriter->Write() function.
MAYAUSD_CORE_PUBLIC
MPlugArray mBlendShapesAnimWeightPlugs;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's rather surprising to see translator-specific data in this generic context. What do others think? Should we make it easier to attach any blind data to the context instead of hardcoding something specific each time a translator needs it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes; I foresee a lot of future needs for this where a translator will need to retain state between invocations or similar, especially for certain types of Maya nodes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kxl-adsk Based on our discussion, I've gone ahead and made the change to move this into the meshWriter translator instead, along with updating the unit test as also discussed. Please let me know if there are other questions/concerns.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good. I re-requested the review from @williamkrick and dismissed the stale review since it was no longer applicable based on what we discussed. I also started a new PF.


protected:
/// Opens the stage with the given \p filename for writing.
/// If \p append is \c true, the file must already exist.
Expand Down
2 changes: 1 addition & 1 deletion lib/mayaUsd/utils/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ std::string UsdMayaUtil::GetMayaNodeName(const MObject& mayaNode)
return nodeName.asChar();
}

MString UsdMayaUtil::GetUniqueNameOfDAGNode(const MObject& node)
MString UsdMayaUtil::GetUniqueNameOfDagNode(const MObject& node)
{
if (!TF_VERIFY(!node.isNull() && node.hasFn(MFn::kDagNode))) {
return MString();
Expand Down
2 changes: 1 addition & 1 deletion lib/mayaUsd/utils/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ std::string GetMayaNodeName(const MObject& mayaNode);
* @return The name as a Maya string.
*/
MAYAUSD_CORE_PUBLIC
MString GetUniqueNameOfDAGNode(const MObject& node);
MString GetUniqueNameOfDagNode(const MObject& node);

/// Gets the Maya MObject for the node named \p nodeName.
MAYAUSD_CORE_PUBLIC
Expand Down
4 changes: 0 additions & 4 deletions lib/usd/translators/meshWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
#include <maya/MBoundingBox.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnMesh.h>
#include <maya/MPlugArray.h>
#include <maya/MString.h>

#include <set>
Expand Down Expand Up @@ -78,9 +77,6 @@ class PxrUsdTranslators_MeshWriter : public UsdMayaPrimWriter
/// Input mesh before any skeletal deformations, cached between iterations.
MObject _skelInputMesh;

/// The animated plugs of any blendshape nodes involved in mesh deformation.
MPlugArray _animBlendShapeWeightPlugs;

/// The previous sample for the mesh extents. Cached between iterations.
VtVec3fArray _prevMeshExtentsSample;

Expand Down
103 changes: 84 additions & 19 deletions lib/usd/translators/meshWriter_BlendShapes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,18 @@ void findUnionAndProcessArrays(
return;
}

MStatus mayaPrefixBlendShapeTargetNameForUSD(MString& targetName, const MObject& blendShapeNode)
{
MStatus stat;
MFnDependencyNode fnNode(blendShapeNode, &stat);
CHECK_MSTATUS_AND_RETURN_IT(stat);
MString blendShapeName = fnNode.absoluteName();
// NOTE: (yliangsiew) We format the name to be more like an SdfPath for the sake of semantics.
stat = blendShapeName.substitute(":", "/");
targetName = blendShapeName + "/" + targetName;
return stat;
}

// NOTE: (yliangsiew) This gets called once for each shape being exported
// under a single transform.
MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchema)
Expand Down Expand Up @@ -629,8 +641,14 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem

bool exportAnim = !(exportArgs.timeSamples.empty());

// NOTE: (yliangsiew) Because in UsdSkelBlendShape, the SkelAnimation stores the names of _all_
// blendshapes across the entire USD in a single array, we need a way to avoid collisions, while
// preserving the "nice" names of the individual target shapes. So the BlendShape prim itself
// will have the "nice" short name, while we use a unique long name to refer to the blendshape
// target internally for namespacing purposes.
SdfPathVector usdBlendShapePaths;
VtTokenArray usdBlendShapeNames;
VtTokenArray usdBlendShapeShortNames;
VtTokenArray usdBlendShapeLongNames;
const SdfPath& primSchemaPath = primSchema.GetPrim().GetPath();
for (size_t i = 0; i < numOfBlendShapeDeformers; ++i) {
MayaBlendShapeDatum blendShapeInfo = blendShapeDeformerInfos[i];
Expand All @@ -651,6 +669,7 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
MayaBlendShapeTargetDatum targetDatum = weightInfo.targets[k];
MObject targetMesh = targetDatum.targetMesh;
MString curTargetNameMStr;
MString curTargetLongNameMStr;
if (!targetMesh.isNull()) {
// NOTE: (yliangsiew) Because UsdSkelBlendShape does not
// support animated targets (the `normalOffsets` and
Expand All @@ -664,7 +683,11 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
"connections first before attempting to export.");
return MObject::kNullObj;
}
curTargetNameMStr = UsdMayaUtil::GetUniqueNameOfDAGNode(targetMesh);
curTargetNameMStr = UsdMayaUtil::GetUniqueNameOfDagNode(targetMesh);
curTargetLongNameMStr = MString(curTargetNameMStr);
stat = mayaPrefixBlendShapeTargetNameForUSD(
curTargetLongNameMStr, blendShapeInfo.blendShapeDeformer);
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
} else {
MFnDependencyNode fnNode(blendShapeInfo.blendShapeDeformer, &stat);
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
Expand All @@ -682,19 +705,29 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
// we'll use that instead of calling our target "weight_".
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
if (k == 0) {
curTargetNameMStr
= MString(TfStringPrintf("%s", plgBlendShapeName.asChar()).c_str());
curTargetNameMStr = MString(plgBlendShapeName);
} else {
// NOTE: (yliangsiew) Because a single weight can drive multiple
// targets, we have to put a numeric suffix in the target name.
curTargetNameMStr = MString(
TfStringPrintf("%s%zu", plgBlendShapeName.asChar(), k).c_str());
}
// NOTE: (yliangsiew) Need to format a scene-globally-unique
// blendshape target name here to avoid name collisions where
// different meshes could have the same exact target names (Since
// UsdSkelBlendShape stores _all_ the target names across the
// entire file in a single array for the animation samples.)
curTargetLongNameMStr = MString(curTargetNameMStr);
stat = mayaPrefixBlendShapeTargetNameForUSD(
curTargetLongNameMStr, blendShapeInfo.blendShapeDeformer);
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
}

TF_VERIFY(curTargetNameMStr.length() != 0);
std::string curTargetName
= TfMakeValidIdentifier(std::string(curTargetNameMStr.asChar()));
std::string curTargetLongName
= TfMakeValidIdentifier(std::string(curTargetLongNameMStr.asChar()));
SdfPath usdBlendShapePath = primSchemaPath.AppendChild(TfToken(curTargetName));
UsdSkelBlendShape usdBlendShape
= UsdSkelBlendShape::Define(this->GetUsdStage(), usdBlendShapePath);
Expand All @@ -710,7 +743,8 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
usdBlendShape.CreateNormalOffsetsAttr(VtValue(targetDatum.normalOffsets));

usdBlendShapePaths.push_back(usdBlendShapePath);
usdBlendShapeNames.push_back(TfToken(curTargetName));
usdBlendShapeShortNames.push_back(TfToken(curTargetName));
usdBlendShapeLongNames.push_back(TfToken(curTargetLongName));

// NOTE: (yliangsiew) Because animation export is deferred until subsequent
// calls in meshWriter.cpp, we just store the plugs to retrieve the samples from
Expand All @@ -725,7 +759,8 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
TF_VERIFY(weightsPlug.isArray());
MPlug weightPlug = weightsPlug.elementByLogicalIndex(weightIndex);
this->_animBlendShapeWeightPlugs.append(weightPlug);
// this->_animBlendShapeWeightPlugs.append(weightPlug);
this->_writeJobCtx.mBlendShapesAnimWeightPlugs.append(weightPlug);
}
}
break;
Expand Down Expand Up @@ -826,6 +861,7 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
MObject targetMesh = targetDatum.targetMesh;
// NOTE: (yliangsiew) If mesh is already baked in, format name differently.
MString curTargetNameMStr;
MString curTargetLongNameMStr;
if (!targetMesh.isNull()) {
// NOTE: (yliangsiew) Because UsdSkelBlendShape does not
// support animated targets (the `normalOffsets` and
Expand All @@ -839,7 +875,16 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
"connections first before attempting to export.");
return MObject::kNullObj;
}
curTargetNameMStr = UsdMayaUtil::GetUniqueNameOfDAGNode(targetMesh);
// NOTE: (yliangsiew) Need to format a scene-globally-unique
// blendshape target name here to avoid name collisions where
// different meshes could have the same exact target names (Since
// UsdSkelBlendShape stores _all_ the target names across the
// entire file in a single array for the animation samples.)
curTargetNameMStr = UsdMayaUtil::GetUniqueNameOfDagNode(targetMesh);
curTargetLongNameMStr = MString(curTargetNameMStr);
stat = mayaPrefixBlendShapeTargetNameForUSD(
curTargetLongNameMStr, blendShapeInfo.blendShapeDeformer);
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
} else {
MFnDependencyNode fnNode(blendShapeInfo.blendShapeDeformer, &stat);
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
Expand All @@ -857,19 +902,29 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
// we'll use that instead of calling our target "weight_".
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
if (k == 0) {
curTargetNameMStr
= MString(TfStringPrintf("%s", plgBlendShapeName.asChar()).c_str());
// NOTE: (yliangsiew) Need to format a scene-globally-unique
// blendshape target name here to avoid name collisions where
// different meshes could have the same exact target names (Since
// UsdSkelBlendShape stores _all_ the target names across the
// entire file in a single array for the animation samples.)
curTargetNameMStr = MString(plgBlendShapeName);
parentTargetNameMStr = MString(curTargetNameMStr);
} else {
// NOTE: (yliangsiew) Because a single weight can drive multiple
// targets, we have to put a numeric suffix in the target name.
curTargetNameMStr = MString(
TfStringPrintf("%s%zu", plgBlendShapeName.asChar(), k).c_str());
}
curTargetLongNameMStr = MString(curTargetNameMStr);
stat = mayaPrefixBlendShapeTargetNameForUSD(
curTargetLongNameMStr, blendShapeInfo.blendShapeDeformer);
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
}
TF_VERIFY(curTargetNameMStr.length() != 0);
std::string curTargetName
= TfMakeValidIdentifier(std::string(curTargetNameMStr.asChar()));
std::string curTargetLongName
= TfMakeValidIdentifier(std::string(curTargetLongNameMStr.asChar()));
std::string parentTargetName
= TfMakeValidIdentifier(std::string(parentTargetNameMStr.asChar()));
unsigned int targetWeightIndex = weightInfo.targetItemIndices[k];
Expand All @@ -879,8 +934,10 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
usdBlendShape.CreateOffsetsAttr(VtValue(processedOffsetsArrays[k]));
usdBlendShape.CreateNormalOffsetsAttr(
VtValue(processedNormalsOffsetsArrays[k]));

usdBlendShapePaths.push_back(usdBlendShapePath);
usdBlendShapeNames.push_back(TfToken(parentTargetName));
usdBlendShapeShortNames.push_back(TfToken(parentTargetName));
usdBlendShapeLongNames.push_back(TfToken(curTargetLongName));

// NOTE: (yliangsiew) Because animation export is deferred until subsequent
// calls in meshWriter.cpp, we just store the plugs to retrieve the samples
Expand All @@ -895,7 +952,8 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
CHECK_MSTATUS_AND_RETURN(stat, MObject::kNullObj);
TF_VERIFY(weightsPlug.isArray());
MPlug weightPlug = weightsPlug.elementByLogicalIndex(weightIndex);
this->_animBlendShapeWeightPlugs.append(weightPlug);
// this->_animBlendShapeWeightPlugs.append(weightPlug);
this->_writeJobCtx.mBlendShapesAnimWeightPlugs.append(weightPlug);
}
} else {
float weightValue
Expand Down Expand Up @@ -923,7 +981,7 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem

const UsdSkelBindingAPI bindingAPI = UsdSkelBindingAPI::Apply(primSchema.GetPrim());
UsdAttribute blendShapesAttr = bindingAPI.CreateBlendShapesAttr();
blendShapesAttr.Set(VtValue(usdBlendShapeNames));
blendShapesAttr.Set(VtValue(usdBlendShapeLongNames));

UsdRelationship targetsRel = bindingAPI.CreateBlendShapeTargetsRel();
targetsRel.SetTargets(usdBlendShapePaths);
Expand Down Expand Up @@ -990,14 +1048,20 @@ MObject PxrUsdTranslators_MeshWriter::writeBlendShapeData(UsdGeomMesh& primSchem
}
}

// NOTE: (yliangsiew) Because this function is run for each prim, we do this so as to format
// the union of blendshape target names.
VtTokenArray existingBlendShapeNames;
UsdAttribute skelAnimBlendShapesAttr = _skelAnim.GetBlendShapesAttr();
if (skelAnimBlendShapesAttr.HasAuthoredValue()) {
skelAnimBlendShapesAttr.Get(&existingBlendShapeNames);
for (size_t i = 0; i < usdBlendShapeLongNames.size(); ++i) {
existingBlendShapeNames.push_back(usdBlendShapeLongNames[i]);
}
skelAnimBlendShapesAttr.Set(existingBlendShapeNames);
} else {
skelAnimBlendShapesAttr = _skelAnim.CreateBlendShapesAttr();
skelAnimBlendShapesAttr.Set(usdBlendShapeLongNames);
}
skelAnimBlendShapesAttr.Set(usdBlendShapeNames);

return deformedMesh;
}
Expand All @@ -1020,18 +1084,19 @@ bool PxrUsdTranslators_MeshWriter::writeBlendShapeAnimation(const UsdTimeCode& u
blendShapeWeightsAttr = this->_skelAnim.CreateBlendShapeWeightsAttr();
}

unsigned int numWeightPlugs = this->_animBlendShapeWeightPlugs.length();
// NOTE: (yliangsiew) This should be the combined array of _all_ animated blendshape weight
// plugs that line up with the array and indices of the blendshape names above.
unsigned int numWeightPlugs = this->_writeJobCtx.mBlendShapesAnimWeightPlugs.length();
if (numExistingBlendShapes != numWeightPlugs) {
TF_RUNTIME_ERROR("There was a mismatch in the blendshapes determined and their "
"corresponding weight plugs.");
return false;
}

for (size_t i = 0; i < usdWeights.size(); ++i) {
MPlug weightPlug = this->_animBlendShapeWeightPlugs[i];
for (unsigned int i = 0; i < numWeightPlugs; ++i) {
MPlug weightPlug = this->_writeJobCtx.mBlendShapesAnimWeightPlugs[i];
usdWeights[i] = weightPlug.asFloat();
}

bool result = blendShapeWeightsAttr.Set(VtValue(usdWeights), usdTime);

return result;
}

Expand Down
Loading