Skip to content

Commit 13f300b

Browse files
authored
Merge pull request #4348 from Autodesk/barbalt/dev/EMSUSD-2662-slow-blendshapes
EMSUSD-2662 BlendShapes Import performance is very slow compared to other DCCs
2 parents 9f25269 + 5a174d6 commit 13f300b

File tree

2 files changed

+175
-113
lines changed

2 files changed

+175
-113
lines changed

lib/mayaUsd/fileio/translators/translatorBlendShape.cpp

Lines changed: 175 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,29 @@
1616
//
1717
#include "translatorBlendShape.h"
1818

19+
#include <mayaUsd/utils/util.h>
20+
1921
#include <pxr/base/tf/diagnostic.h>
22+
#include <pxr/base/tf/stringUtils.h>
2023
#include <pxr/base/vt/types.h>
2124
#include <pxr/usd/usd/common.h>
25+
#include <pxr/usd/usdGeom/metrics.h>
2226
#include <pxr/usd/usdSkel/animQuery.h>
2327
#include <pxr/usd/usdSkel/animation.h>
2428
#include <pxr/usd/usdSkel/binding.h>
2529
#include <pxr/usd/usdSkel/bindingAPI.h>
2630
#include <pxr/usd/usdSkel/blendShape.h>
2731
#include <pxr/usd/usdSkel/root.h>
2832

33+
#include <maya/MDistance.h>
2934
#include <maya/MFloatVectorArray.h>
3035
#include <maya/MFnAnimCurve.h>
3136
#include <maya/MFnBlendShapeDeformer.h>
37+
#include <maya/MFnComponentListData.h>
3238
#include <maya/MFnGeometryFilter.h>
39+
#include <maya/MFnPointArrayData.h>
40+
#include <maya/MFnSingleIndexedComponent.h>
41+
#include <maya/MGlobal.h>
3342
#include <maya/MItDependencyGraph.h>
3443
#include <maya/MPointArray.h>
3544
#include <maya/MStatus.h>
@@ -39,64 +48,121 @@
3948
PXR_NAMESPACE_USING_DIRECTIVE
4049

4150
namespace MAYAUSD_NS_DEF {
51+
52+
struct _BlendShapeAttributesNames
53+
{
54+
const char* it { "inputTarget" };
55+
const char* itg { "inputTargetGroup" };
56+
const char* iti { "inputTargetItem" };
57+
const char* ipt { "inputPointsTarget" };
58+
const char* ict { "inputComponentsTarget" };
59+
const char* sn { "supportNegativeWeights" };
60+
const char* w { "weight" };
61+
const char* ibig { "inbetweenInfoGroup" };
62+
const char* ibi { "inbetweenInfo" };
63+
const char* ibtn { "inbetweenTargetName" };
64+
};
65+
66+
TfStaticData<_BlendShapeAttributesNames> _BlendShapeAttributes;
67+
4268
void _AddBlendShape(
43-
MFnBlendShapeDeformer& blendShapeFn,
44-
const MObject& originalShape,
45-
const MObject& parentTransform,
46-
const MPointArray& originalPoints,
47-
const GfVec3f* originalNormals,
48-
const VtIntArray& pointIndices,
49-
const std::string name,
50-
const VtVec3fArray offsetArray,
51-
const VtVec3fArray& normalsOffsetArray,
52-
int blendShapeTargetIndex,
53-
float weight)
69+
const MFnBlendShapeDeformer& fnBlendShape,
70+
const std::string& blendShapeName,
71+
unsigned int targetIdx,
72+
float weight,
73+
const MPointArray& deltas,
74+
MIntArray& indices,
75+
bool isInBetween = false)
5476
{
55-
if (offsetArray.size() != pointIndices.size()) {
56-
TF_RUNTIME_ERROR(
57-
"BlendShape <%s> doesn't match the number of offset points.", name.c_str());
58-
return;
59-
}
77+
const auto blendShapeObj = fnBlendShape.object();
6078

61-
MFnMesh blendShapeMeshFn;
62-
MStatus status;
63-
auto newShape = blendShapeMeshFn.copy(originalShape, parentTransform, &status);
64-
65-
MPointArray deltaPoints(originalPoints);
66-
for (size_t pidx = 0; pidx < pointIndices.size(); ++pidx) {
67-
// USD BlendShapes doesn't require to all points to have a delta.
68-
// Thus, given the indicesArray, find the actual index (index) in the original shape that
69-
// will be affected that the offset on that particular position.
70-
int index = pointIndices[pidx];
71-
MPoint original = originalPoints[index];
72-
GfVec3f offset = offsetArray[pidx];
73-
74-
deltaPoints.set(
75-
MPoint(original.x + offset[0], original.y + offset[1], original.z + offset[2]), index);
79+
auto inputTargetAttr = fnBlendShape.attribute(_BlendShapeAttributes->it);
80+
auto inputTargetGroupAttr = fnBlendShape.attribute(_BlendShapeAttributes->itg);
81+
auto inputTargetItemAttr = fnBlendShape.attribute(_BlendShapeAttributes->iti);
82+
auto inputPointsTargetAttr = fnBlendShape.attribute(_BlendShapeAttributes->ipt);
83+
auto inputComponentTargetAttr = fnBlendShape.attribute(_BlendShapeAttributes->ict);
84+
auto inputWeightAttr = fnBlendShape.attribute(_BlendShapeAttributes->w);
85+
86+
if (weight < 0.f) {
87+
auto supportNegativeWeightAttr = fnBlendShape.attribute(_BlendShapeAttributes->sn);
88+
89+
MPlug plgSupportNegativeWeight(blendShapeObj, supportNegativeWeightAttr);
90+
plgSupportNegativeWeight.setBool(true);
7691
}
77-
blendShapeMeshFn.setPoints(deltaPoints);
78-
79-
// Applying blendShape normals
80-
if (!normalsOffsetArray.empty()) {
81-
MFloatVectorArray deltaNormals(normalsOffsetArray.size());
82-
for (size_t i = 0; i < normalsOffsetArray.size(); ++i) {
83-
MFloatVector deltaNormal(
84-
originalNormals[i][0], originalNormals[i][1], originalNormals[i][2]);
85-
86-
deltaNormal.x += normalsOffsetArray[i][0];
87-
deltaNormal.y += normalsOffsetArray[i][1];
88-
deltaNormal.z += normalsOffsetArray[i][2];
89-
deltaNormals.set(deltaNormal, i);
90-
}
91-
blendShapeMeshFn.setNormals(deltaNormals);
92+
93+
// The weight index is an integer that maps weight values to Maya's internal representation
94+
// Maya supports negative weights, so we need to handle the full range
95+
// Positive weights: 5000-6000 (weight 0.001 to 1.0)
96+
// Negative weights: 0-4999 (weight -5.0 to -0.001)
97+
// Zero weight: 5000
98+
static const auto convertWeightToIndex = [](float weight) -> int {
99+
int result = 5000 + static_cast<int>(weight * 1000.f);
100+
return result >= 0 ? result : 0;
101+
};
102+
103+
// Maya's blendShape api requires a copy of the original shape or a copy of the target mesh
104+
// for it to work.
105+
// To avoid that, use plugs to manually create the blendShape targets using only the deltas
106+
// from USD.
107+
108+
// Create the plug for the desired attribute: .it[-1].itg[-1].iti[-1].ipt
109+
MPlug plgInPoints(blendShapeObj, inputPointsTargetAttr);
110+
// Set the first element position in the plug: .it[0].itg[-1].iti[-1].ipt
111+
plgInPoints.selectAncestorLogicalIndex(0, inputTargetAttr);
112+
// Set the second plug element: .it[0].itg[targetIdx].iti[-1].ipt
113+
plgInPoints.selectAncestorLogicalIndex(targetIdx, inputTargetGroupAttr);
114+
// Third element: .it[0].itg[targetIdx].iti[convertWeightToIndex(weight)].ipt
115+
plgInPoints.selectAncestorLogicalIndex(convertWeightToIndex(weight), inputTargetItemAttr);
116+
117+
{
118+
MFnPointArrayData data;
119+
MObject dataObj = data.create();
120+
data.set(deltas);
121+
plgInPoints.setValue(dataObj);
92122
}
93123

94-
MFnDependencyNode ibDepNodeFn;
95-
ibDepNodeFn.setObject(newShape);
96-
ibDepNodeFn.setName(MString(name.c_str()));
124+
// Now, similarly, set the inputComponentsTarget attribute (which are the indices being changed)
125+
MPlug plgInCompTarget(blendShapeObj, inputComponentTargetAttr);
126+
plgInCompTarget.selectAncestorLogicalIndex(0, inputTargetAttr);
127+
plgInCompTarget.selectAncestorLogicalIndex(targetIdx, inputTargetGroupAttr);
128+
plgInCompTarget.selectAncestorLogicalIndex(convertWeightToIndex(weight), inputTargetItemAttr);
129+
{
130+
MFnSingleIndexedComponent indexList(blendShapeObj);
131+
auto indexListObj = indexList.create(MFn::kMeshVertComponent);
132+
indexList.addElements(indices);
133+
MFnComponentListData fnComp;
134+
MObject compObj = fnComp.create();
135+
fnComp.add(indexListObj);
136+
plgInCompTarget.setValue(compObj);
137+
}
97138

98-
blendShapeFn.addTarget(originalShape, blendShapeTargetIndex, newShape, weight);
99-
blendShapeMeshFn.setIntermediateObject(true);
139+
// If weight is not 1.0, that means we are creating an inbetween shape
140+
if (isInBetween) {
141+
auto inBetweenTargetNameAttr = fnBlendShape.attribute(_BlendShapeAttributes->ibtn);
142+
auto inBetweenInfoGroupAttr = fnBlendShape.attribute(_BlendShapeAttributes->ibig);
143+
auto inBetweenInfoAttr = fnBlendShape.attribute(_BlendShapeAttributes->ibi);
144+
MPlug plgInBetweenTargetName(blendShapeObj, inBetweenTargetNameAttr);
145+
plgInBetweenTargetName.selectAncestorLogicalIndex(targetIdx, inBetweenInfoGroupAttr);
146+
plgInBetweenTargetName.selectAncestorLogicalIndex(
147+
convertWeightToIndex(weight), inBetweenInfoAttr);
148+
149+
// USD inbetweens are named: "inbetween:<name>"
150+
// Remove the inbetween prefix
151+
auto inBetweenName = blendShapeName.substr(blendShapeName.find_first_of(":") + 1);
152+
153+
plgInBetweenTargetName.setString(MString(inBetweenName.c_str()));
154+
} else {
155+
MPlug plgWeight(blendShapeObj, inputWeightAttr);
156+
plgWeight.selectAncestorLogicalIndex(targetIdx, inputWeightAttr);
157+
158+
MString cmd;
159+
cmd.format(
160+
"import maya.cmds as cmds; cmds.aliasAttr(\"^1s\",\"^2s\");",
161+
blendShapeName.c_str(),
162+
plgWeight.name().asChar());
163+
164+
MGlobal::executePythonCommand(cmd);
165+
}
100166
}
101167

102168
bool UsdMayaTranslatorBlendShape::Read(const UsdPrim& meshPrim, UsdMayaPrimReaderContext* context)
@@ -137,13 +203,6 @@ bool UsdMayaTranslatorBlendShape::Read(const UsdPrim& meshPrim, UsdMayaPrimReade
137203
MFnMesh originalMeshFn(originalShape);
138204
MPointArray originalPoints;
139205
originalMeshFn.getPoints(originalPoints);
140-
const size_t originalNumVertices = originalPoints.length();
141-
142-
// There's no easy way to access the mesh's original normals.
143-
// This is how it's done in other places when those are needed.
144-
const float* normals = originalMeshFn.getRawNormals(&status);
145-
CHECK_MSTATUS_AND_RETURN(status, false)
146-
const GfVec3f* originalNormals = reinterpret_cast<const GfVec3f*>(normals);
147206

148207
MFnBlendShapeDeformer blendFn;
149208
const auto blendShapeObj
@@ -154,14 +213,12 @@ bool UsdMayaTranslatorBlendShape::Read(const UsdPrim& meshPrim, UsdMayaPrimReade
154213
blendShapeDepNodeFn.setName(MString(
155214
TfStringPrintf("%s_Deformer", meshPrim.GetPath().GetElementString().c_str()).c_str()));
156215

157-
MObject deformedMeshObject;
158-
VtVec3fArray deltaPoints, deltaNormals;
159-
VtIntArray pointIndices;
160216
for (size_t targetIdx = 0; targetIdx < blendShapeTargets.size(); ++targetIdx) {
161-
const SdfPath blendShapePath = blendShapeTargets[targetIdx];
162-
UsdSkelBlendShape blendShape(stage->GetPrimAtPath(blendShapePath));
163-
std::string blendShapeName = blendShape.GetPath().GetElementString();
217+
const SdfPath& blendShapePath = blendShapeTargets[targetIdx];
218+
const UsdSkelBlendShape blendShape(stage->GetPrimAtPath(blendShapePath));
219+
const std::string blendShapeName = blendShape.GetPath().GetElementString();
164220

221+
VtVec3fArray deltaPoints;
165222
blendShape.GetOffsetsAttr().Get(&deltaPoints);
166223
if (deltaPoints.empty()) {
167224
TF_RUNTIME_ERROR(
@@ -171,46 +228,55 @@ bool UsdMayaTranslatorBlendShape::Read(const UsdPrim& meshPrim, UsdMayaPrimReade
171228
continue;
172229
}
173230

174-
blendShape.GetNormalOffsetsAttr().Get(&deltaNormals);
231+
VtIntArray pointIndices;
175232
blendShape.GetPointIndicesAttr().Get(&pointIndices);
176233

177234
// Indices aren't authored. Use mesh default pointIndices
178235
if (pointIndices.empty()) {
179-
pointIndices.resize(originalNumVertices);
180-
for (size_t i = 0; i < originalNumVertices; ++i) {
181-
pointIndices[i] = i;
236+
pointIndices.reserve(deltaPoints.size());
237+
for (size_t i = 0; i < deltaPoints.size(); ++i) {
238+
pointIndices.emplace_back(i);
182239
}
183240
}
184241

242+
if (deltaPoints.size() != pointIndices.size()) {
243+
TF_RUNTIME_ERROR(
244+
"BlendShape <%s> for mesh <%s> has mismatched number of delta points and indices.",
245+
blendShapePath.GetText(),
246+
meshPrim.GetPath().GetText());
247+
continue;
248+
}
249+
250+
// Convert Usd Arrays to Maya Arrays
251+
252+
MIntArray indicesIntArray(static_cast<unsigned int>(pointIndices.size()));
253+
for (size_t i = 0; i < pointIndices.size(); ++i) {
254+
indicesIntArray.set(pointIndices[i], static_cast<unsigned int>(i));
255+
}
256+
257+
MPointArray deltasPointsArray(deltaPoints.size());
258+
for (size_t pidx = 0; pidx < pointIndices.size(); ++pidx) {
259+
const GfVec3f& offset = deltaPoints[pidx];
260+
deltasPointsArray.set(MPoint(offset[0], offset[1], offset[2]), pidx);
261+
}
262+
185263
_AddBlendShape(
186264
blendFn,
187-
originalShape,
188-
parentTransform,
189-
originalPoints,
190-
originalNormals,
191-
pointIndices,
192265
blendShapeName,
193-
deltaPoints,
194-
deltaNormals,
195-
targetIdx,
196-
1.0f); // The original blendShape always has full weight of 1.0f
197-
198-
// After adding the main blendShape - now add all the in-betweens
266+
static_cast<unsigned int>(targetIdx),
267+
1.f,
268+
deltasPointsArray,
269+
indicesIntArray);
199270

271+
MIntArray inBetweenIndicesIntArray(indicesIntArray);
200272
const std::vector<UsdSkelInbetweenShape> inBetweens = blendShape.GetInbetweens();
201-
// no in-betweens, can continue to the next blendShape
202-
if (inBetweens.empty()) {
203-
continue;
204-
}
205-
206-
for (size_t ib = 0; ib < inBetweens.size(); ++ib) {
207-
const auto& currentInBetween = inBetweens[ib];
208-
const std::string inBetweenName = currentInBetween.GetAttr().GetName().GetString();
273+
for (const auto& inBetween : inBetweens) {
274+
const std::string inBetweenName = inBetween.GetAttr().GetName().GetString();
209275
float ibWeight = 0.f;
210-
currentInBetween.GetWeight(&ibWeight);
276+
inBetween.GetWeight(&ibWeight);
211277

212-
VtVec3fArray inBetweenDeltaPoints, inBetweenNormals;
213-
currentInBetween.GetOffsets(&inBetweenDeltaPoints);
278+
VtVec3fArray inBetweenDeltaPoints;
279+
inBetween.GetOffsets(&inBetweenDeltaPoints);
214280
if (inBetweenDeltaPoints.empty()) {
215281
TF_RUNTIME_ERROR(
216282
"InBetween BlendShape <%s> for mesh <%s> has no delta points.",
@@ -219,21 +285,29 @@ bool UsdMayaTranslatorBlendShape::Read(const UsdPrim& meshPrim, UsdMayaPrimReade
219285
continue;
220286
}
221287

222-
currentInBetween.GetNormalOffsets(&inBetweenNormals);
288+
if (inBetweenDeltaPoints.size() != pointIndices.size()) {
289+
TF_RUNTIME_ERROR(
290+
"InBetween BlendShape <%s> for mesh <%s> has a different number of delta "
291+
"points and indices.",
292+
inBetweenName.c_str(),
293+
meshPrim.GetPath().GetText());
294+
continue;
295+
}
296+
297+
MPointArray inBetweenDeltasPointsArray(inBetweenDeltaPoints.size());
298+
for (size_t pidx = 0; pidx < pointIndices.size(); ++pidx) {
299+
const GfVec3f& offset = inBetweenDeltaPoints[pidx];
300+
inBetweenDeltasPointsArray.set(MPoint(offset[0], offset[1], offset[2]), pidx);
301+
}
223302

224-
// InBetween are new blendShapes added to the same target index
225303
_AddBlendShape(
226304
blendFn,
227-
originalShape,
228-
parentTransform,
229-
originalPoints,
230-
originalNormals,
231-
pointIndices,
232305
inBetweenName,
233-
inBetweenDeltaPoints,
234-
inBetweenNormals,
235-
targetIdx,
236-
ibWeight);
306+
static_cast<unsigned int>(targetIdx),
307+
ibWeight,
308+
inBetweenDeltasPointsArray,
309+
inBetweenIndicesIntArray,
310+
true);
237311
}
238312
}
239313
return true;

test/lib/usd/translators/testUsdImportBlendShapes.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,6 @@ def test_BlendShapesImport(self):
6868

6969
self.assertEqual(cmds.nodeType("b1_Deformer"), "blendShape")
7070

71-
regularShape = sorted(
72-
cmds.listConnections(
73-
"b1_Deformer.inputTarget[0].inputTargetGroup[0].inputTargetItem[6000].inputGeomTarget",
74-
destination=False, source=True, plugs=True))
75-
self.assertEqual(regularShape, ['Box0002.worldMesh'])
76-
77-
inBetween = sorted(
78-
cmds.listConnections(
79-
"b1_Deformer.inputTarget[0].inputTargetGroup[0].inputTargetItem[4000].inputGeomTarget",
80-
destination=False, source=True, plugs=True))
81-
self.assertEqual(inBetween, ['IBT_1.worldMesh'])
82-
8371
cmds.currentTime(0)
8472
self.assertEqual(sorted(cmds.getAttr("b1_Deformer.weight")[0]), [-1.0])
8573
cmds.currentTime(3)

0 commit comments

Comments
 (0)