diff --git a/lib/fileio/CMakeLists.txt b/lib/fileio/CMakeLists.txt index c6c30e0f02..ecc81679ad 100644 --- a/lib/fileio/CMakeLists.txt +++ b/lib/fileio/CMakeLists.txt @@ -43,8 +43,6 @@ target_sources(${PROJECT_NAME} translators/translatorMaterial.cpp translators/translatorMayaReference.cpp translators/translatorMesh.cpp - translators/translatorMesh_PrimVars.cpp - translators/translatorMesh_SubDiv.cpp translators/translatorNurbsPatch.cpp translators/translatorPrim.cpp translators/translatorRfMLight.cpp diff --git a/lib/fileio/translators/translatorMesh.cpp b/lib/fileio/translators/translatorMesh.cpp index 58d1d33079..597d05e437 100644 --- a/lib/fileio/translators/translatorMesh.cpp +++ b/lib/fileio/translators/translatorMesh.cpp @@ -13,39 +13,22 @@ // See the License for the specific language governing permissions and // limitations under the License. // +// Modifications copyright (C) 2020 Autodesk +// + #include "translatorMesh.h" -#include "../utils/meshUtil.h" #include "../../nodes/pointBasedDeformerNode.h" -#include "../primReaderArgs.h" -#include "../primReaderContext.h" -#include "../utils/readUtil.h" -#include "../utils/roundTripUtil.h" #include "../../nodes/stageNode.h" -#include "translatorGprim.h" -#include "translatorMaterial.h" -#include "translatorUtil.h" #include "../../utils/util.h" +#include "../utils/meshUtil.h" +#include "../utils/readUtil.h" -#include "pxr/base/tf/diagnostic.h" -#include "pxr/base/tf/stringUtils.h" -#include "pxr/base/tf/token.h" -#include "pxr/base/vt/array.h" -#include "pxr/base/vt/types.h" - -#include "pxr/usd/sdf/path.h" -#include "pxr/usd/sdf/tokens.h" -#include "pxr/usd/sdf/types.h" -#include "pxr/usd/sdf/valueTypeName.h" -#include "pxr/usd/usd/attribute.h" -#include "pxr/usd/usd/prim.h" -#include "pxr/usd/usd/timeCode.h" -#include "pxr/usd/usdGeom/mesh.h" -#include "pxr/usd/usdGeom/primvar.h" -#include "pxr/usd/usdGeom/tokens.h" - +#include +#include #include #include +#include #include #include #include @@ -55,191 +38,32 @@ #include #include #include +#include #include #include #include -#include #include -#include -#include -#include -#include -#include #include #include - -PXR_NAMESPACE_OPEN_SCOPE - - -static -bool -_SetupPointBasedDeformerForMayaNode( - MObject& mayaObj, - const UsdPrim& prim, - UsdMayaPrimReaderContext* context) -{ - // We try to get the USD stage node from the context's registry, so if we - // don't have a reader context, we can't continue. - if (!context) { - return false; - } - - MObject stageNode = - context->GetMayaNode( - SdfPath(UsdMayaStageNodeTokens->MayaTypeName.GetString()), - false); - if (stageNode.isNull()) { - return false; - } - - // Get the output time plug and node for Maya's global time object. - MPlug timePlug = UsdMayaUtil::GetMayaTimePlug(); - if (timePlug.isNull()) { - return false; - } - - MStatus status; - MObject timeNode = timePlug.node(&status); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Clear the selection list so that the deformer command doesn't try to add - // anything to the new deformer's set. We'll do that manually afterwards. - status = MGlobal::clearSelectionList(); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Create the point based deformer node for this prim. - const std::string pointBasedDeformerNodeName = - TfStringPrintf("usdPointBasedDeformerNode%s", - TfStringReplace(prim.GetPath().GetString(), - SdfPathTokens->childDelimiter.GetString(), - "_").c_str()); - - const std::string deformerCmd = TfStringPrintf( - "from maya import cmds; cmds.deformer(name=\'%s\', type=\'%s\')[0]", - pointBasedDeformerNodeName.c_str(), - UsdMayaPointBasedDeformerNodeTokens->MayaTypeName.GetText()); - MString newPointBasedDeformerName; - status = MGlobal::executePythonCommand(deformerCmd.c_str(), - newPointBasedDeformerName); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Get the newly created point based deformer node. - MObject pointBasedDeformerNode; - status = UsdMayaUtil::GetMObjectByName(newPointBasedDeformerName.asChar(), - pointBasedDeformerNode); - CHECK_MSTATUS_AND_RETURN(status, false); - - context->RegisterNewMayaNode(newPointBasedDeformerName.asChar(), - pointBasedDeformerNode); - - MFnDependencyNode depNodeFn(pointBasedDeformerNode, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - MDGModifier dgMod; - - // Set the prim path on the deformer node. - MPlug primPathPlug = - depNodeFn.findPlug(UsdMayaPointBasedDeformerNode::primPathAttr, - true, - &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - status = dgMod.newPlugValueString(primPathPlug, prim.GetPath().GetText()); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Connect the stage node's stage output to the deformer node. - status = dgMod.connect(stageNode, - UsdMayaStageNode::outUsdStageAttr, - pointBasedDeformerNode, - UsdMayaPointBasedDeformerNode::inUsdStageAttr); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Connect the global Maya time to the deformer node. - status = dgMod.connect(timeNode, - timePlug.attribute(), - pointBasedDeformerNode, - UsdMayaPointBasedDeformerNode::timeAttr); - CHECK_MSTATUS_AND_RETURN(status, false); - - status = dgMod.doIt(); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Add the Maya object to the point based deformer node's set. - const MFnGeometryFilter geomFilterFn(pointBasedDeformerNode, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - MObject deformerSet = geomFilterFn.deformerSet(&status); - CHECK_MSTATUS_AND_RETURN(status, false); - - MFnSet setFn(deformerSet, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - status = setFn.addMember(mayaObj); - CHECK_MSTATUS_AND_RETURN(status, false); - - // When we created the point based deformer, Maya will have automatically - // created a tweak deformer and put it *before* the point based deformer in - // the deformer chain. We don't want that, since any component edits made - // interactively in Maya will appear to have no effect since they'll be - // overridden by the point based deformer. Instead, we want the tweak to go - // *after* the point based deformer. To do this, we need to dig for the - // name of the tweak deformer node that Maya created to be able to pass it - // to the reorderDeformers command. - const MFnDagNode dagNodeFn(mayaObj, &status); - CHECK_MSTATUS_AND_RETURN(status, false); - - // XXX: This seems to be the "most sane" way of finding the tweak deformer - // node's name... - const std::string findTweakCmd = TfStringPrintf( - "from maya import cmds; [x for x in cmds.listHistory(\'%s\') if cmds.nodeType(x) == \'tweak\'][0]", - dagNodeFn.fullPathName().asChar()); - - MString tweakDeformerNodeName; - status = MGlobal::executePythonCommand(findTweakCmd.c_str(), - tweakDeformerNodeName); - CHECK_MSTATUS_AND_RETURN(status, false); - - // Do the reordering. - const std::string reorderDeformersCmd = TfStringPrintf( - "from maya import cmds; cmds.reorderDeformers(\'%s\', \'%s\', \'%s\')", - tweakDeformerNodeName.asChar(), - newPointBasedDeformerName.asChar(), - dagNodeFn.fullPathName().asChar()); - status = MGlobal::executePythonCommand(reorderDeformersCmd.c_str()); - CHECK_MSTATUS_AND_RETURN(status, false); - - return true; -} - -/* static */ -bool -UsdMayaTranslatorMesh::Create( - const UsdGeomMesh& mesh, - MObject parentNode, - const UsdMayaPrimReaderArgs& args, - UsdMayaPrimReaderContext* context) +MAYAUSD_NS_DEF { + +TranslatorMeshRead::TranslatorMeshRead(const UsdGeomMesh& mesh, + const UsdPrim& prim, + const MObject& transformObj, + const MObject& stageNode, + const GfInterval& frameRange, + bool wantCacheAnimation, + MStatus * status) + : m_wantCacheAnimation(wantCacheAnimation) + , m_pointsNumTimeSamples(0u) { - if (!mesh) { - return false; - } - - const UsdPrim& prim = mesh.GetPrim(); - - MStatus status; - - // Create node (transform) - MObject mayaNodeTransformObj; - if (!UsdMayaTranslatorUtil::CreateTransformNode(prim, - parentNode, - args, - context, - &status, - &mayaNodeTransformObj)) { - return false; - } + MStatus stat{MS::kSuccess}; + // ============================================== + // construct a Maya mesh + // ============================================== VtIntArray faceVertexCounts; VtIntArray faceVertexIndices; @@ -252,7 +76,6 @@ UsdMayaTranslatorMesh::Create( "faceVertexCounts), which isn't currently supported. " "Skipping...", prim.GetPath().GetText()); - return false; } else { fvc.Get(&faceVertexCounts, UsdTimeCode::EarliestTime()); } @@ -266,7 +89,6 @@ UsdMayaTranslatorMesh::Create( "faceVertexIndices), which isn't currently supported. " "Skipping...", prim.GetPath().GetText()); - return false; } else { fvi.Get(&faceVertexIndices, UsdTimeCode::EarliestTime()); } @@ -278,7 +100,6 @@ UsdMayaTranslatorMesh::Create( "[count: %zu, indices:%zu] on Mesh <%s>. Skipping...", faceVertexCounts.size(), faceVertexIndices.size(), prim.GetPath().GetText()); - return false; // invalid mesh, so exit } // Gather points and normals @@ -289,17 +110,17 @@ UsdMayaTranslatorMesh::Create( UsdTimeCode pointsTimeSample = UsdTimeCode::EarliestTime(); UsdTimeCode normalsTimeSample = UsdTimeCode::EarliestTime(); std::vector pointsTimeSamples; - size_t pointsNumTimeSamples = 0u; - if (!args.GetTimeInterval().IsEmpty()) { - mesh.GetPointsAttr().GetTimeSamplesInInterval(args.GetTimeInterval(), + + if (!frameRange.IsEmpty()) { + mesh.GetPointsAttr().GetTimeSamplesInInterval(frameRange, &pointsTimeSamples); if (!pointsTimeSamples.empty()) { - pointsNumTimeSamples = pointsTimeSamples.size(); + m_pointsNumTimeSamples = pointsTimeSamples.size(); pointsTimeSample = pointsTimeSamples.front(); } std::vector normalsTimeSamples; - mesh.GetNormalsAttr().GetTimeSamplesInInterval(args.GetTimeInterval(), + mesh.GetNormalsAttr().GetTimeSamplesInInterval(frameRange, &normalsTimeSamples); if (!normalsTimeSamples.empty()) { normalsTimeSample = normalsTimeSamples.front(); @@ -312,7 +133,6 @@ UsdMayaTranslatorMesh::Create( if (points.empty()) { TF_RUNTIME_ERROR("points array is empty on Mesh <%s>. Skipping...", prim.GetPath().GetText()); - return false; } std::string reason; @@ -322,13 +142,13 @@ UsdMayaTranslatorMesh::Create( &reason)) { TF_RUNTIME_ERROR("Skipping Mesh <%s> with invalid topology: %s", prim.GetPath().GetText(), reason.c_str()); - return false; + *status = MS::kFailure; + return; } - - // == Convert data + // == Convert data to Maya ( vertices, faces, indices ) const size_t mayaNumVertices = points.size(); - MPointArray mayaPoints(mayaNumVertices); + MPointArray mayaPoints(mayaNumVertices); for (size_t i = 0u; i < mayaNumVertices; ++i) { mayaPoints.set(i, points[i][0], points[i][1], points[i][2]); } @@ -338,40 +158,31 @@ UsdMayaTranslatorMesh::Create( // == Create Mesh Shape Node MFnMesh meshFn; - MObject meshObj = meshFn.create(mayaPoints.length(), - polygonCounts.length(), - mayaPoints, - polygonCounts, - polygonConnects, - mayaNodeTransformObj, - &status); - if (status != MS::kSuccess) { - return false; + m_meshObj = meshFn.create( mayaPoints.length(), + polygonCounts.length(), + mayaPoints, + polygonCounts, + polygonConnects, + transformObj, + &stat); + + if (!stat) { + *status = stat; + return; } - // Since we are "decollapsing", we will create a xform and a shape node for each USD prim - const std::string usdPrimName = prim.GetName().GetString(); - const std::string shapeName = TfStringPrintf("%sShape", - usdPrimName.c_str()); - - // Set mesh name and register - meshFn.setName(MString(shapeName.c_str()), false, &status); - if (context) { - const SdfPath shapePath = - prim.GetPath().AppendChild(TfToken(shapeName)); - context->RegisterNewMayaNode(shapePath.GetString(), meshObj); // used for undo/redo - } + // set mesh name + const auto& primName = prim.GetName().GetString(); + const auto shapeName = TfStringPrintf("%sShape", primName.c_str()); + meshFn.setName(MString(shapeName.c_str()), false, &stat); - // If a material is bound, create (or reuse if already present) and assign it - // If no binding is present, assign the mesh to the default shader - const TfToken& shadingMode = args.GetShadingMode(); - UsdMayaTranslatorMaterial::AssignMaterial(shadingMode, - mesh, - meshObj, - context); + if (!stat) { + *status = stat; + return; + } - // Mesh is a shape, so read Gprim properties - UsdMayaTranslatorGprim::Read(mesh, meshObj, context); + // store the path + m_shapePath = prim.GetPath().AppendChild(TfToken(shapeName)); // Set normals if supplied MIntArray normalsFaceIds; @@ -387,8 +198,7 @@ UsdMayaTranslatorMesh::Create( for (size_t i = 0u; i < normals.size(); ++i) { mayaNormals.set(MVector(normals[i][0u], normals[i][1u], - normals[i][2u]), - i); + normals[i][2u]),i); } meshFn.setFaceVertexNormals(mayaNormals, @@ -397,6 +207,20 @@ UsdMayaTranslatorMesh::Create( } } + // If we are dealing with polys, check if there are normals and set the + // internal emit-normals tag so that the normals will round-trip. + // If we are dealing with a subdiv, read additional subdiv tags. + TfToken subdScheme; + if (mesh.GetSubdivisionSchemeAttr().Get(&subdScheme) && subdScheme == UsdGeomTokens->none) { + if (normals.size() == static_cast(meshFn.numFaceVertices()) && + mesh.GetNormalsInterpolation() == UsdGeomTokens->faceVarying) { + UsdMayaMeshUtil::SetEmitNormalsTag(meshFn, true); + } + } + else { + stat = UsdMayaMeshUtil::assignSubDivTagsToMesh(mesh, m_meshObj, meshFn); + } + // Copy UsdGeomMesh schema attrs into Maya if they're authored. UsdMayaReadUtil::ReadSchemaAttributesFromPrim( prim, @@ -407,152 +231,28 @@ UsdMayaTranslatorMesh::Create( UsdGeomTokens->faceVaryingLinearInterpolation }); - // If we are dealing with polys, check if there are normals and set the - // internal emit-normals tag so that the normals will round-trip. - // If we are dealing with a subdiv, read additional subdiv tags. - TfToken subdScheme; - if (mesh.GetSubdivisionSchemeAttr().Get(&subdScheme) && - subdScheme == UsdGeomTokens->none) { - if (normals.size() == static_cast(meshFn.numFaceVertices()) && - mesh.GetNormalsInterpolation() == UsdGeomTokens->faceVarying) { - UsdMayaMeshUtil::SetEmitNormalsTag(meshFn, true); - } - } else { - _AssignSubDivTagsToMesh(mesh, meshObj, meshFn); - } - - // Set Holes - VtIntArray holeIndices; - mesh.GetHoleIndicesAttr().Get(&holeIndices); // not animatable - if (!holeIndices.empty()) { - MUintArray mayaHoleIndices; - mayaHoleIndices.setLength(holeIndices.size()); - for (size_t i = 0u; i < holeIndices.size(); ++i) { - mayaHoleIndices[i] = holeIndices[i]; - } - - if (meshFn.setInvisibleFaces(mayaHoleIndices) != MS::kSuccess) { - TF_RUNTIME_ERROR("Unable to set Invisible Faces on <%s>", - meshFn.fullPathName().asChar()); - } - } - - // GETTING PRIMVARS - const std::vector primvars = mesh.GetPrimvars(); - TF_FOR_ALL(iter, primvars) { - const UsdGeomPrimvar& primvar = *iter; - const TfToken name = primvar.GetBaseName(); - const TfToken fullName = primvar.GetPrimvarName(); - const SdfValueTypeName typeName = primvar.GetTypeName(); - const TfToken& interpolation = primvar.GetInterpolation(); - - // Exclude primvars using the full primvar name without "primvars:". - // This applies to all primvars; we don't care if it's a color set, a - // UV set, etc. - if (args.GetExcludePrimvarNames().count(fullName) != 0) { - continue; - } - - // If the primvar is called either displayColor or displayOpacity check - // if it was really authored from the user. It may not have been - // authored by the user, for example if it was generated by shader - // values and not an authored colorset/entity. - // If it was not really authored, we skip the primvar. - if (name == UsdMayaMeshColorSetTokens->DisplayColorColorSetName || - name == UsdMayaMeshColorSetTokens->DisplayOpacityColorSetName) { - if (!UsdMayaRoundTripUtil::IsAttributeUserAuthored(primvar)) { - continue; - } - } - - // XXX: Maya stores UVs in MFloatArrays and color set data in MColors - // which store floats, so we currently only import primvars holding - // float-typed arrays. Should we still consider other precisions - // (double, half, ...) and/or numeric types (int)? - if (typeName == SdfValueTypeNames->TexCoord2fArray || - (UsdMayaReadUtil::ReadFloat2AsUV() && - typeName == SdfValueTypeNames->Float2Array)) { - // Looks for TexCoord2fArray types for UV sets first - // Otherwise, if env variable for reading Float2 - // as uv sets is turned on, we assume that Float2Array primvars - // are UV sets. - if (!_AssignUVSetPrimvarToMesh(primvar, meshFn)) { - TF_WARN("Unable to retrieve and assign data for UV set <%s> on " - "mesh <%s>", - name.GetText(), - mesh.GetPrim().GetPath().GetText()); - } - } else if (typeName == SdfValueTypeNames->FloatArray || - typeName == SdfValueTypeNames->Float3Array || - typeName == SdfValueTypeNames->Color3fArray || - typeName == SdfValueTypeNames->Float4Array || - typeName == SdfValueTypeNames->Color4fArray) { - if (!_AssignColorSetPrimvarToMesh(mesh, primvar, meshFn)) { - TF_WARN("Unable to retrieve and assign data for color set <%s> " - "on mesh <%s>", - name.GetText(), - mesh.GetPrim().GetPath().GetText()); - } - } else if (interpolation == UsdGeomTokens->constant){ - // Constant primvars get added as attributes on the mesh. - if (!_AssignConstantPrimvarToMesh(primvar, meshFn)) { - TF_WARN("Unable to assign constant primvar <%s> as attribute " - "on mesh <%s>", - name.GetText(), - mesh.GetPrim().GetPath().GetText()); - } - } - } - - // We only vizualize the colorset by default if it is "displayColor". - MStringArray colorSetNames; - if (meshFn.getColorSetNames(colorSetNames) == MS::kSuccess) { - for (unsigned int i = 0u; i < colorSetNames.length(); ++i) { - const MString colorSetName = colorSetNames[i]; - if (std::string(colorSetName.asChar()) - == UsdMayaMeshColorSetTokens->DisplayColorColorSetName.GetString()) { - const MFnMesh::MColorRepresentation csRep = - meshFn.getColorRepresentation(colorSetName); - if (csRep == MFnMesh::kRGB || csRep == MFnMesh::kRGBA) { - // both of these are needed to show the colorset. - MPlug plg = meshFn.findPlug("displayColors"); - if (!plg.isNull()) { - plg.setBool(true); - } - meshFn.setCurrentColorSetName(colorSetName); - } - break; - } - } - } - + // ================================================== + // construct blendshape object, PointBasedDeformer + // ================================================== // Code below this point is for handling deforming meshes, so if we don't // have time samples to deal with, we're done. - if (pointsNumTimeSamples == 0u) { - return true; + if (m_pointsNumTimeSamples == 0u) { + return; } - // If we're using the imported USD as an animation cache, try to setup the - // point based deformer for this prim. If that fails, we'll fallback on - // creating a blend shape deformer. - if (args.GetUseAsAnimationCache() && - _SetupPointBasedDeformerForMayaNode(meshObj, prim, context)) { - return true; + if (m_wantCacheAnimation) { + *status = setPointBasedDeformerForMayaNode(m_meshObj, stageNode, prim); + return; } - // Use blendShapeDeformer so that all the points for a frame are contained - // in a single node. - // + // Use blendShapeDeformer so that all the points for a frame are contained in a single node. MPointArray mayaAnimPoints(mayaNumVertices); MObject meshAnimObj; MFnBlendShapeDeformer blendFn; - MObject blendObj = blendFn.create(meshObj); - if (context) { - context->RegisterNewMayaNode(blendFn.name().asChar(), blendObj); // used for undo/redo - } + m_meshBlendObj = blendFn.create(m_meshObj); - for (unsigned int ti = 0u; ti < pointsNumTimeSamples; ++ti) { + for (unsigned int ti = 0u; ti < m_pointsNumTimeSamples; ++ti) { mesh.GetPointsAttr().Get(&points, pointsTimeSamples[ti]); for (unsigned int i = 0u; i < mayaNumVertices; ++i) { @@ -567,15 +267,16 @@ UsdMayaTranslatorMesh::Create( mayaAnimPoints, polygonCounts, polygonConnects, - mayaNodeTransformObj, - &status); - if (status != MS::kSuccess) { + transformObj, + &stat); + + if (!stat) { continue; } } else { // Reuse the already created mesh by copying it and then setting the points - meshAnimObj = meshFn.copy(meshAnimObj, mayaNodeTransformObj, &status); + meshAnimObj = meshFn.copy(meshAnimObj, transformObj, &stat); meshFn.setPoints(mayaAnimPoints); } @@ -585,13 +286,12 @@ UsdMayaTranslatorMesh::Create( // mesh.GetNormalsAttr().Get(&normals, pointsTimeSamples[ti]); if (normals.size() == static_cast(meshFn.numFaceVertices()) && - normalsFaceIds.length() == static_cast(meshFn.numFaceVertices())) { + normalsFaceIds.length() == static_cast(meshFn.numFaceVertices())) { MVectorArray mayaNormals(normals.size()); for (size_t i = 0; i < normals.size(); ++i) { mayaNormals.set(MVector(normals[i][0u], normals[i][1u], - normals[i][2u]), - i); + normals[i][2u]),i); } meshFn.setFaceVertexNormals(mayaNormals, @@ -602,7 +302,7 @@ UsdMayaTranslatorMesh::Create( // Add as target and set as an intermediate object. We do *not* // register the mesh object for undo/redo, since it will be handled // automatically by deleting the blend shape deformer object. - blendFn.addTarget(meshObj, ti, meshAnimObj, 1.0); + blendFn.addTarget(m_meshObj, ti, meshAnimObj, 1.0); meshFn.setIntermediateObject(true); } @@ -611,19 +311,19 @@ UsdMayaTranslatorMesh::Create( // Construct the time array to be used for all the keys MTimeArray timeArray; - timeArray.setLength(pointsNumTimeSamples); - for (unsigned int ti = 0u; ti < pointsNumTimeSamples; ++ti) { + timeArray.setLength(m_pointsNumTimeSamples); + for (unsigned int ti = 0u; ti < m_pointsNumTimeSamples; ++ti) { timeArray.set(MTime(pointsTimeSamples[ti]), ti); } // Key/Animate the weights MPlug plgAry = blendFn.findPlug("weight"); if (!plgAry.isNull() && plgAry.isArray()) { - for (unsigned int ti = 0u; ti < pointsNumTimeSamples; ++ti) { - MPlug plg = plgAry.elementByLogicalIndex(ti, &status); - MDoubleArray valueArray(pointsNumTimeSamples, 0.0); + for (unsigned int ti = 0u; ti < m_pointsNumTimeSamples; ++ti) { + MPlug plg = plgAry.elementByLogicalIndex(ti, &stat); + MDoubleArray valueArray(m_pointsNumTimeSamples, 0.0); valueArray[ti] = 1.0; // Set the time value where this mesh's weight should be 1.0 - MObject animObj = animFn.create(plg, nullptr, &status); + MObject animObj = animFn.create(plg, nullptr, &stat); animFn.addKeys(&timeArray, &valueArray); // We do *not* register the anim curve object for undo/redo, // since it will be handled automatically by deleting the blend @@ -631,8 +331,163 @@ UsdMayaTranslatorMesh::Create( } } - return true; + *status = stat; +} + +MStatus +TranslatorMeshRead::setPointBasedDeformerForMayaNode(const MObject& mayaObj, const MObject& stageNode, const UsdPrim& prim) +{ + MStatus status{MS::kSuccess}; + + // Get the output time plug and node for Maya's global time object. + MPlug timePlug = UsdMayaUtil::GetMayaTimePlug(); + if (timePlug.isNull()) { + status = MS::kFailure; + } + + MObject timeNode = timePlug.node(&status); + CHECK_MSTATUS(status); + + // Clear the selection list so that the deformer command doesn't try to add + // anything to the new deformer's set. We'll do that manually afterwards. + status = MGlobal::clearSelectionList(); + CHECK_MSTATUS(status); + + // Create the point based deformer node for this prim. + const std::string pointBasedDeformerNodeName = + TfStringPrintf("usdPointBasedDeformerNode%s", + TfStringReplace(prim.GetPath().GetString(), + SdfPathTokens->childDelimiter.GetString(), + "_").c_str()); + + const std::string deformerCmd = TfStringPrintf( + "from maya import cmds; cmds.deformer(name=\'%s\', type=\'%s\')[0]", + pointBasedDeformerNodeName.c_str(), + UsdMayaPointBasedDeformerNodeTokens->MayaTypeName.GetText()); + status = MGlobal::executePythonCommand(deformerCmd.c_str(), + m_newPointBasedDeformerName); + CHECK_MSTATUS(status); + + // Get the newly created point based deformer node. + status = UsdMayaUtil::GetMObjectByName(m_newPointBasedDeformerName.asChar(), + m_pointBasedDeformerNode); + CHECK_MSTATUS(status); + + MFnDependencyNode depNodeFn(m_pointBasedDeformerNode, &status); + CHECK_MSTATUS(status); + + MDGModifier dgMod; + + // Set the prim path on the deformer node. + MPlug primPathPlug = + depNodeFn.findPlug(UsdMayaPointBasedDeformerNode::primPathAttr, + true, + &status); + CHECK_MSTATUS(status); + + status = dgMod.newPlugValueString(primPathPlug, prim.GetPath().GetText()); + CHECK_MSTATUS(status); + + // Connect the stage node's stage output to the deformer node. + status = dgMod.connect(stageNode, + UsdMayaStageNode::outUsdStageAttr, + m_pointBasedDeformerNode, + UsdMayaPointBasedDeformerNode::inUsdStageAttr); + CHECK_MSTATUS(status); + + // Connect the global Maya time to the deformer node. + status = dgMod.connect(timeNode, + timePlug.attribute(), + m_pointBasedDeformerNode, + UsdMayaPointBasedDeformerNode::timeAttr); + CHECK_MSTATUS(status); + + status = dgMod.doIt(); + CHECK_MSTATUS(status); + + // Add the Maya object to the point based deformer node's set. + const MFnGeometryFilter geomFilterFn(m_pointBasedDeformerNode, &status); + CHECK_MSTATUS(status); + + MObject deformerSet = geomFilterFn.deformerSet(&status); + CHECK_MSTATUS(status); + + MFnSet setFn(deformerSet, &status); + CHECK_MSTATUS(status); + + status = setFn.addMember(mayaObj); + CHECK_MSTATUS(status); + + // When we created the point based deformer, Maya will have automatically + // created a tweak deformer and put it *before* the point based deformer in + // the deformer chain. We don't want that, since any component edits made + // interactively in Maya will appear to have no effect since they'll be + // overridden by the point based deformer. Instead, we want the tweak to go + // *after* the point based deformer. To do this, we need to dig for the + // name of the tweak deformer node that Maya created to be able to pass it + // to the reorderDeformers command. + const MFnDagNode dagNodeFn(mayaObj, &status); + CHECK_MSTATUS(status); + + // XXX: This seems to be the "most sane" way of finding the tweak deformer + // node's name... + const std::string findTweakCmd = TfStringPrintf( + "from maya import cmds; [x for x in cmds.listHistory(\'%s\') if cmds.nodeType(x) == \'tweak\'][0]", + dagNodeFn.fullPathName().asChar()); + + MString tweakDeformerNodeName; + status = MGlobal::executePythonCommand(findTweakCmd.c_str(), + tweakDeformerNodeName); + CHECK_MSTATUS(status); + + // Do the reordering. + const std::string reorderDeformersCmd = TfStringPrintf( + "from maya import cmds; cmds.reorderDeformers(\'%s\', \'%s\', \'%s\')", + tweakDeformerNodeName.asChar(), + m_newPointBasedDeformerName.asChar(), + dagNodeFn.fullPathName().asChar()); + status = MGlobal::executePythonCommand(reorderDeformersCmd.c_str()); + CHECK_MSTATUS(status); + + return status; +} + +MObject +TranslatorMeshRead::meshObject() const +{ + return m_meshObj; +} + +MObject +TranslatorMeshRead::blendObject() const +{ + return m_meshBlendObj; +} + +MObject +TranslatorMeshRead::pointBasedDeformerNode() const +{ + return m_pointBasedDeformerNode; +} + +MString +TranslatorMeshRead::pointBasedDeformerName() const +{ + return m_newPointBasedDeformerName; +} + +size_t +TranslatorMeshRead::pointsNumTimeSamples() const +{ + return m_pointsNumTimeSamples; } +SdfPath +TranslatorMeshRead::shapePath() const +{ + return m_shapePath; +} + + +} // namespace MayaUsd -PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/fileio/translators/translatorMesh.h b/lib/fileio/translators/translatorMesh.h index f98c057e57..f9fa2c320e 100644 --- a/lib/fileio/translators/translatorMesh.h +++ b/lib/fileio/translators/translatorMesh.h @@ -13,64 +13,69 @@ // See the License for the specific language governing permissions and // limitations under the License. // +// Modifications copyright (C) 2020 Autodesk +// -/// \file usdMaya/translatorMesh.h - -#ifndef PXRUSDMAYA_TRANSLATOR_MESH_H -#define PXRUSDMAYA_TRANSLATOR_MESH_H +#pragma once #include "../../base/api.h" -#include "../primReaderArgs.h" -#include "../primReaderContext.h" - #include "pxr/pxr.h" - #include "pxr/usd/usdGeom/mesh.h" -#include "pxr/usd/usdGeom/primvar.h" -#include #include +#include +PXR_NAMESPACE_USING_DIRECTIVE -PXR_NAMESPACE_OPEN_SCOPE +MAYAUSD_NS_DEF { - -/// Provides helper functions for translating UsdGeomMesh prims into Maya -/// meshes. -class UsdMayaTranslatorMesh +// +// \class MayaUsd::TranslatorMeshRead +// +// This class is used to translate a UsdGeomMesh prim +// using schema mesh utilities into a Maya mesh. +// +class MAYAUSD_CORE_PUBLIC TranslatorMeshRead { - public: - /// Creates an MFnMesh under \p parentNode from \p mesh. - MAYAUSD_CORE_PUBLIC - static bool Create( - const UsdGeomMesh& mesh, - MObject parentNode, - const UsdMayaPrimReaderArgs& args, - UsdMayaPrimReaderContext* context); - - private: - static bool _AssignSubDivTagsToMesh( - const UsdGeomMesh& primSchema, - MObject& meshObj, - MFnMesh& meshFn); - - static bool _AssignUVSetPrimvarToMesh( - const UsdGeomPrimvar& primvar, - MFnMesh& meshFn); - - static bool _AssignColorSetPrimvarToMesh( - const UsdGeomMesh& primSchema, - const UsdGeomPrimvar& primvar, - MFnMesh& meshFn); - - static bool _AssignConstantPrimvarToMesh( - const UsdGeomPrimvar& primvar, - MFnMesh& meshFn); +public: + TranslatorMeshRead(const UsdGeomMesh& mesh, + const UsdPrim& prim, + const MObject& transformObj, + const MObject& stageNode, + const GfInterval& frameRange, + bool wantCacheAnimation, + MStatus * status = nullptr); + + ~TranslatorMeshRead() = default; + + TranslatorMeshRead(const TranslatorMeshRead&) = delete; + TranslatorMeshRead& operator=(const TranslatorMeshRead&) = delete; + TranslatorMeshRead(TranslatorMeshRead&&) = delete; + TranslatorMeshRead& operator=(TranslatorMeshRead&&) = delete; + + MObject meshObject() const; + + MObject blendObject() const; + MObject pointBasedDeformerNode() const; + MString pointBasedDeformerName() const; + size_t pointsNumTimeSamples() const; + + SdfPath shapePath() const; + +private: + MStatus setPointBasedDeformerForMayaNode(const MObject&, + const MObject&, + const UsdPrim&); +private: + MObject m_meshObj; + MObject m_meshBlendObj; + MObject m_pointBasedDeformerNode; + MString m_newPointBasedDeformerName; + bool m_wantCacheAnimation; + size_t m_pointsNumTimeSamples; + + SdfPath m_shapePath; }; - -PXR_NAMESPACE_CLOSE_SCOPE - - -#endif +} // namespace MayaUsd diff --git a/lib/fileio/translators/translatorMesh_PrimVars.cpp b/lib/fileio/translators/translatorMesh_PrimVars.cpp deleted file mode 100644 index 50d7513784..0000000000 --- a/lib/fileio/translators/translatorMesh_PrimVars.cpp +++ /dev/null @@ -1,461 +0,0 @@ -// -// Copyright 2016 Pixar -// -// 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 "translatorMesh.h" - -#include "../../utils/colorSpace.h" -#include "../utils/meshUtil.h" -#include "../utils/readUtil.h" -#include "../utils/roundTripUtil.h" - -#include "pxr/base/gf/vec2f.h" -#include "pxr/base/gf/vec4f.h" -#include "pxr/base/tf/diagnostic.h" -#include "pxr/base/tf/token.h" -#include "pxr/base/vt/array.h" -#include "pxr/base/vt/types.h" -#include "pxr/base/vt/value.h" -#include "pxr/usd/sdf/types.h" -#include "pxr/usd/sdf/valueTypeName.h" -#include "pxr/usd/usdGeom/mesh.h" -#include "pxr/usd/usdGeom/primvar.h" -#include "pxr/usd/usdUtils/pipeline.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - - -PXR_NAMESPACE_OPEN_SCOPE - - -/// "Flattens out" the given \p interpolation onto face-vertexes of the given -/// \p meshFn, returning a mapping of the face-vertex indices to data indices. -/// Takes into account data authored sparsely if \p assignmentIndices and -/// \p unauthoredValuesIndex are specified. -static -MIntArray -_GetMayaFaceVertexAssignmentIds( - const MFnMesh& meshFn, - const TfToken& interpolation, - const VtIntArray& assignmentIndices, - const int unauthoredValuesIndex) -{ - MIntArray valueIds(meshFn.numFaceVertices(), -1); - - MItMeshFaceVertex itFV(meshFn.object()); - unsigned int fvi = 0; - for (itFV.reset(); !itFV.isDone(); itFV.next(), ++fvi) { - int valueId = 0; - if (interpolation == UsdGeomTokens->constant) { - valueId = 0; - } else if (interpolation == UsdGeomTokens->uniform) { - valueId = itFV.faceId(); - } else if (interpolation == UsdGeomTokens->vertex) { - valueId = itFV.vertId(); - } else if (interpolation == UsdGeomTokens->faceVarying) { - valueId = fvi; - } - - if (static_cast(valueId) < assignmentIndices.size()) { - // The data is indexed, so consult the indices array for the - // correct index into the data. - valueId = assignmentIndices[valueId]; - - if (valueId == unauthoredValuesIndex) { - // This component had no authored value, so leave it unassigned. - continue; - } - } - - valueIds[fvi] = valueId; - } - - return valueIds; -} - -/* static */ -bool -UsdMayaTranslatorMesh::_AssignUVSetPrimvarToMesh( - const UsdGeomPrimvar& primvar, - MFnMesh& meshFn) -{ - const TfToken& primvarName = primvar.GetPrimvarName(); - - // Get the raw data before applying any indexing. - VtVec2fArray uvValues; - if (!primvar.Get(&uvValues) || uvValues.empty()) { - TF_WARN("Could not read UV values from primvar '%s' on mesh: %s", - primvarName.GetText(), - primvar.GetAttr().GetPrimPath().GetText()); - return false; - } - - // This is the number of UV values assuming the primvar is NOT indexed. - VtIntArray assignmentIndices; - if (primvar.GetIndices(&assignmentIndices)) { - // The primvar IS indexed, so the indices array is what determines the - // number of UV values. - int unauthoredValuesIndex = primvar.GetUnauthoredValuesIndex(); - - // Replace any index equal to unauthoredValuesIndex with -1. - if (unauthoredValuesIndex != -1) { - for (int& index : assignmentIndices) { - if (index == unauthoredValuesIndex) { - index = -1; - } - } - } - - // Furthermore, if unauthoredValuesIndex is valid for uvValues, then - // remove it from uvValues and shift the indices (we don't want to - // import the unauthored value into Maya, where it has no meaning). - if (unauthoredValuesIndex >= 0 && - static_cast(unauthoredValuesIndex) < uvValues.size()) { - // This moves [unauthoredValuesIndex + 1, end) to - // [unauthoredValuesIndex, end - 1), erasing the - // unauthoredValuesIndex. - std::move( - uvValues.begin() + unauthoredValuesIndex + 1, - uvValues.end(), - uvValues.begin() + unauthoredValuesIndex); - uvValues.pop_back(); - - for (int& index : assignmentIndices) { - if (index > unauthoredValuesIndex) { - index = index - 1; - } - } - } - } - - // Go through the UV data and add the U and V values to separate - // MFloatArrays. - MFloatArray uCoords; - MFloatArray vCoords; - for (const GfVec2f& v : uvValues) { - uCoords.append(v[0]); - vCoords.append(v[1]); - } - - MStatus status; - MString uvSetName(primvarName.GetText()); - if (primvarName == UsdUtilsGetPrimaryUVSetName()) { - // We assume that the primary USD UV set maps to Maya's default 'map1' - // set which always exists, so we shouldn't try to create it. - uvSetName = "map1"; - } else { - status = meshFn.createUVSet(uvSetName); - if (status != MS::kSuccess) { - TF_WARN("Unable to create UV set '%s' for mesh: %s", - uvSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - } - - // The following two lines should have no effect on user-visible state but - // prevent a Maya crash in MFnMesh.setUVs after creating a crease set. - // XXX this workaround is needed pending a fix by Autodesk. - MString currentSet = meshFn.currentUVSetName(); - meshFn.setCurrentUVSetName(currentSet); - - // Create UVs on the mesh from the values we collected out of the primvar. - // We'll assign mesh components to these values below. - status = meshFn.setUVs(uCoords, vCoords, &uvSetName); - if (status != MS::kSuccess) { - TF_WARN("Unable to set UV data on UV set '%s' for mesh: %s", - uvSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - - const TfToken& interpolation = primvar.GetInterpolation(); - - // Build an array of value assignments for each face vertex in the mesh. - // Any assignments left as -1 will not be assigned a value. - MIntArray uvIds = _GetMayaFaceVertexAssignmentIds(meshFn, - interpolation, - assignmentIndices, - -1); - - MIntArray vertexCounts; - MIntArray vertexList; - status = meshFn.getVertices(vertexCounts, vertexList); - if (status != MS::kSuccess) { - TF_WARN("Could not get vertex counts for UV set '%s' on mesh: %s", - uvSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - - status = meshFn.assignUVs(vertexCounts, uvIds, &uvSetName); - if (status != MS::kSuccess) { - TF_WARN("Could not assign UV values to UV set '%s' on mesh: %s", - uvSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - - return true; -} - -/* static */ -bool -UsdMayaTranslatorMesh::_AssignColorSetPrimvarToMesh( - const UsdGeomMesh& primSchema, - const UsdGeomPrimvar& primvar, - MFnMesh& meshFn) -{ - const TfToken& primvarName = primvar.GetPrimvarName(); - const SdfValueTypeName& typeName = primvar.GetTypeName(); - - MString colorSetName(primvarName.GetText()); - - // If the primvar is displayOpacity and it is a FloatArray, check if - // displayColor is authored. If not, we'll import this 'displayOpacity' - // primvar as a 'displayColor' color set. This supports cases where the - // user created a single channel value for displayColor. - // Note that if BOTH displayColor and displayOpacity are authored, they will - // be imported as separate color sets. We do not attempt to combine them - // into a single color set. - if (primvarName == UsdMayaMeshColorSetTokens->DisplayOpacityColorSetName && - typeName == SdfValueTypeNames->FloatArray) { - if (!UsdMayaRoundTripUtil::IsAttributeUserAuthored(primSchema.GetDisplayColorPrimvar())) { - colorSetName = UsdMayaMeshColorSetTokens->DisplayColorColorSetName.GetText(); - } - } - - // We'll need to convert colors from linear to display if this color set is - // for display colors. - const bool isDisplayColor = - (colorSetName == UsdMayaMeshColorSetTokens->DisplayColorColorSetName.GetText()); - - // Get the raw data before applying any indexing. We'll only populate one - // of these arrays based on the primvar's typeName, and we'll also set the - // color representation so we know which array to use later. - VtFloatArray alphaArray; - VtVec3fArray rgbArray; - VtVec4fArray rgbaArray; - MFnMesh::MColorRepresentation colorRep; - size_t numValues = 0; - - MStatus status = MS::kSuccess; - - if (typeName == SdfValueTypeNames->FloatArray) { - colorRep = MFnMesh::kAlpha; - if (!primvar.Get(&alphaArray) || alphaArray.empty()) { - status = MS::kFailure; - } else { - numValues = alphaArray.size(); - } - } else if (typeName == SdfValueTypeNames->Float3Array || - typeName == SdfValueTypeNames->Color3fArray) { - colorRep = MFnMesh::kRGB; - if (!primvar.Get(&rgbArray) || rgbArray.empty()) { - status = MS::kFailure; - } else { - numValues = rgbArray.size(); - } - } else if (typeName == SdfValueTypeNames->Float4Array || - typeName == SdfValueTypeNames->Color4fArray) { - colorRep = MFnMesh::kRGBA; - if (!primvar.Get(&rgbaArray) || rgbaArray.empty()) { - status = MS::kFailure; - } else { - numValues = rgbaArray.size(); - } - } else { - TF_WARN("Unsupported color set primvar type '%s' for primvar '%s' on " - "mesh: %s", - typeName.GetAsToken().GetText(), - primvarName.GetText(), - primvar.GetAttr().GetPrimPath().GetText()); - return false; - } - - if (status != MS::kSuccess || numValues == 0) { - TF_WARN("Could not read color set values from primvar '%s' on mesh: %s", - primvarName.GetText(), - primvar.GetAttr().GetPrimPath().GetText()); - return false; - } - - VtIntArray assignmentIndices; - int unauthoredValuesIndex = -1; - if (primvar.GetIndices(&assignmentIndices)) { - // The primvar IS indexed, so the indices array is what determines the - // number of color values. - numValues = assignmentIndices.size(); - unauthoredValuesIndex = primvar.GetUnauthoredValuesIndex(); - } - - // Go through the color data and translate the values into MColors in the - // colorArray, taking into consideration that indexed data may have been - // authored sparsely. If the assignmentIndices array is empty then the data - // is NOT indexed. - // Note that with indexed data, the data is added to the arrays in ascending - // component ID order according to the primvar's interpolation (ascending - // face ID for uniform interpolation, ascending vertex ID for vertex - // interpolation, etc.). This ordering may be different from the way the - // values are ordered in the primvar. Because of this, we recycle the - // assignmentIndices array as we go to store the new mapping from component - // index to color index. - MColorArray colorArray; - for (size_t i = 0; i < numValues; ++i) { - int valueIndex = i; - - if (i < assignmentIndices.size()) { - // The data is indexed, so consult the indices array for the - // correct index into the data. - valueIndex = assignmentIndices[i]; - - if (valueIndex == unauthoredValuesIndex) { - // This component is unauthored, so just update the - // mapping in assignmentIndices and then skip the value. - // We don't actually use the value at the unassigned index. - assignmentIndices[i] = -1; - continue; - } - - // We'll be appending a new value, so the current length of the - // array gives us the new value's index. - assignmentIndices[i] = colorArray.length(); - } - - GfVec4f colorValue(1.0); - - switch(colorRep) { - case MFnMesh::kAlpha: - colorValue[3] = alphaArray[valueIndex]; - break; - case MFnMesh::kRGB: - colorValue[0] = rgbArray[valueIndex][0]; - colorValue[1] = rgbArray[valueIndex][1]; - colorValue[2] = rgbArray[valueIndex][2]; - break; - case MFnMesh::kRGBA: - colorValue[0] = rgbaArray[valueIndex][0]; - colorValue[1] = rgbaArray[valueIndex][1]; - colorValue[2] = rgbaArray[valueIndex][2]; - colorValue[3] = rgbaArray[valueIndex][3]; - break; - default: - break; - } - - if (isDisplayColor) { - colorValue = UsdMayaColorSpace::ConvertLinearToMaya(colorValue); - } - - MColor mColor(colorValue[0], colorValue[1], colorValue[2], colorValue[3]); - colorArray.append(mColor); - } - - // colorArray now stores all of the values and any unassigned components - // have had their indices set to -1, so update the unauthored values index. - unauthoredValuesIndex = -1; - - const bool clamped = UsdMayaRoundTripUtil::IsPrimvarClamped(primvar); - - status = meshFn.createColorSet(colorSetName, nullptr, clamped, colorRep); - if (status != MS::kSuccess) { - TF_WARN("Unable to create color set '%s' for mesh: %s", - colorSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - - // Create colors on the mesh from the values we collected out of the - // primvar. We'll assign mesh components to these values below. - status = meshFn.setColors(colorArray, &colorSetName, colorRep); - if (status != MS::kSuccess) { - TF_WARN("Unable to set color data on color set '%s' for mesh: %s", - colorSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - - const TfToken& interpolation = primvar.GetInterpolation(); - - // Build an array of value assignments for each face vertex in the mesh. - // Any assignments left as -1 will not be assigned a value. - MIntArray colorIds = _GetMayaFaceVertexAssignmentIds(meshFn, - interpolation, - assignmentIndices, - unauthoredValuesIndex); - - status = meshFn.assignColors(colorIds, &colorSetName); - if (status != MS::kSuccess) { - TF_WARN("Could not assign color values to color set '%s' on mesh: %s", - colorSetName.asChar(), - meshFn.fullPathName().asChar()); - return false; - } - - return true; -} - -/* static */ -bool -UsdMayaTranslatorMesh::_AssignConstantPrimvarToMesh( - const UsdGeomPrimvar& primvar, - MFnMesh& meshFn) -{ - const TfToken& interpolation = primvar.GetInterpolation(); - if (interpolation != UsdGeomTokens->constant) { - return false; - } - - const TfToken& name = primvar.GetBaseName(); - const SdfValueTypeName& typeName = primvar.GetTypeName(); - const SdfVariability& variability = SdfVariabilityUniform; - - MObject attrObj = - UsdMayaReadUtil::FindOrCreateMayaAttr( - typeName, - variability, - meshFn, - name.GetText()); - if (attrObj.isNull()) { - return false; - } - - VtValue primvarData; - primvar.Get(&primvarData); - - MStatus status; - MPlug plug = meshFn.findPlug( - name.GetText(), - /* wantNetworkedPlug = */ true, - &status); - if (status != MS::kSuccess || plug.isNull()) { - return false; - } - - return UsdMayaReadUtil::SetMayaAttr(plug, primvarData); -} - - -PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/fileio/translators/translatorMesh_SubDiv.cpp b/lib/fileio/translators/translatorMesh_SubDiv.cpp deleted file mode 100644 index 2a1646cb0d..0000000000 --- a/lib/fileio/translators/translatorMesh_SubDiv.cpp +++ /dev/null @@ -1,292 +0,0 @@ -// -// Copyright 2016 Pixar -// -// 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 "translatorMesh.h" - -#include "../../utils/util.h" - -#include "pxr/usd/usdGeom/mesh.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -PXR_NAMESPACE_OPEN_SCOPE - - - -bool -_AddCreaseSet( - const std::string &rootName, - double creaseLevel, - MSelectionList &componentList, - MStatus *statusOK ) -{ - // Crease Set functionality is native to Maya, but undocumented and not - // directly supported in the API. The below implementation is derived from - // the editor code in the maya distro at: - // - // .../lib/python2.7/site-packages/maya/app/general/creaseSetEditor.py - - MObject creasePartitionObj; - *statusOK = UsdMayaUtil::GetMObjectByName(":creasePartition", - creasePartitionObj); - - if (creasePartitionObj.isNull()) { - statusOK->clear(); - - // There is no documented way to create a shared node through the C++ API - const std::string partitionName = MGlobal::executeCommandStringResult( - "createNode \"partition\" -shared -name \":creasePartition\"").asChar(); - - *statusOK = UsdMayaUtil::GetMObjectByName(partitionName, - creasePartitionObj); - if (!*statusOK) { - return false; - } - } - - MFnPartition creasePartition( creasePartitionObj, statusOK ); - if (!*statusOK) return false; - - std::string creaseSetname = - TfStringPrintf("%s_creaseSet#",rootName.c_str()); - - MFnDependencyNode creaseSetFn; - MObject creaseSetObj = - creaseSetFn.create("creaseSet", creaseSetname.c_str(), statusOK ); - if (!*statusOK) return false; - - MPlug levelPlug = creaseSetFn.findPlug("creaseLevel",false, statusOK); - if (!*statusOK) return false; - - *statusOK = levelPlug.setValue(creaseLevel); - if (!*statusOK) return false; - - *statusOK = creasePartition.addMember(creaseSetObj); - if (!*statusOK) return false; - - MFnSet creaseSet( creaseSetObj, statusOK ); - if (!*statusOK) return false; - - *statusOK = creaseSet.addMembers( componentList ); - if (!*statusOK) return false; - - return true; -} - -/* static */ -bool -UsdMayaTranslatorMesh::_AssignSubDivTagsToMesh( const UsdGeomMesh &primSchema, MObject &meshObj, MFnMesh &meshFn) -{ - // We may want to provide the option in the future, but for now, we - // default to using crease sets when setting crease data. - // - static const bool USE_CREASE_SETS = true; - - MStatus statusOK; - - MDagPath meshPath; - statusOK = MDagPath::getAPathTo(meshObj,meshPath); - if (!statusOK) return false; - - // USD does not support grouped verts and edges, so combine all components - // with the same weight into one set to reduce the overall crease set - // count. The user can always split the sets up later if desired. - // - // This structure is unused if crease sets aren't being created. - std::unordered_map elemsPerWeight; - - // Vert Creasing - VtArray subdCornerIndices; - VtArray subdCornerSharpnesses; - primSchema.GetCornerIndicesAttr().Get(&subdCornerIndices); // not animatable - primSchema.GetCornerSharpnessesAttr().Get(&subdCornerSharpnesses); // not animatable - if (!subdCornerIndices.empty()) { - if (subdCornerIndices.size() == subdCornerSharpnesses.size() ) { - statusOK.clear(); - - if (USE_CREASE_SETS) { - MItMeshVertex vertIt(meshObj); - for (unsigned int i=0; i < subdCornerIndices.size(); i++) { - - // Ignore zero-sharpness corners - if (subdCornerSharpnesses[i]==0) - continue; - - MSelectionList &elemList = - elemsPerWeight[ subdCornerSharpnesses[i] ]; - - int prevIndexDummy; // dummy param - statusOK = vertIt.setIndex(subdCornerIndices[i], prevIndexDummy); - if (!statusOK) - break; - statusOK = elemList.add(meshPath,vertIt.currentItem()); - if (!statusOK) - break; - } - - } else { - MUintArray mayaCreaseVertIds; - MDoubleArray mayaCreaseVertValues; - mayaCreaseVertIds.setLength( subdCornerIndices.size() ); - mayaCreaseVertValues.setLength( subdCornerIndices.size() ); - for (unsigned int i=0; i < subdCornerIndices.size(); i++) { - - // Ignore zero-sharpness corners - if (subdCornerSharpnesses[i]==0) - continue; - - mayaCreaseVertIds[i] = subdCornerIndices[i]; - mayaCreaseVertValues[i] = subdCornerSharpnesses[i]; - } - statusOK = meshFn.setCreaseVertices(mayaCreaseVertIds, mayaCreaseVertValues); - } - - if (!statusOK) { - TF_RUNTIME_ERROR("Unable to set Crease Vertices on <%s>: %s", - meshFn.fullPathName().asChar(), - statusOK.errorString().asChar()); - return false; - } - - } else { - TF_RUNTIME_ERROR( - "Mismatch between Corner Indices & Sharpness on <%s>", - primSchema.GetPrim().GetPath().GetText()); - return false; - } - } - - // Edge Creasing - VtArray subdCreaseLengths; - VtArray subdCreaseIndices; - VtArray subdCreaseSharpnesses; - primSchema.GetCreaseLengthsAttr().Get(&subdCreaseLengths); - primSchema.GetCreaseIndicesAttr().Get(&subdCreaseIndices); - primSchema.GetCreaseSharpnessesAttr().Get(&subdCreaseSharpnesses); - if (!subdCreaseLengths.empty()) { - if (subdCreaseLengths.size() == subdCreaseSharpnesses.size() ) { - MUintArray mayaCreaseEdgeIds; - MDoubleArray mayaCreaseEdgeValues; - MIntArray connectedEdges; - unsigned int creaseIndexBase = 0; - - statusOK.clear(); - - for (unsigned int creaseGroup=0; - statusOK && creaseGroup: %s", - meshFn.fullPathName().asChar(), - statusOK.errorString().asChar()); - return false; - } - - } else { - TF_RUNTIME_ERROR( - "Mismatch between Crease Lengths & Sharpness on <%s>", - primSchema.GetPrim().GetPath().GetText()); - return false; - } - } - - if (USE_CREASE_SETS) { - TF_FOR_ALL(weightList, elemsPerWeight) { - double creaseLevel = weightList->first; - MSelectionList &elemList = weightList->second; - - if (!_AddCreaseSet( meshFn.name().asChar(), - creaseLevel, elemList, &statusOK )){ - TF_RUNTIME_ERROR("Unable to set crease sets on <%s>: %s", - meshFn.fullPathName().asChar(), - statusOK.errorString().asChar()); - return false; - } - } - } - - return true; -} - -PXR_NAMESPACE_CLOSE_SCOPE - diff --git a/lib/fileio/utils/meshUtil.cpp b/lib/fileio/utils/meshUtil.cpp index 2becf96424..3e59b6d9bc 100644 --- a/lib/fileio/utils/meshUtil.cpp +++ b/lib/fileio/utils/meshUtil.cpp @@ -13,23 +13,42 @@ // See the License for the specific language governing permissions and // limitations under the License. // +// Modifications copyright (C) 2020 Autodesk +// #include "meshUtil.h" - +#include "roundTripUtil.h" +#include "readUtil.h" #include "adaptor.h" +#include "../../base/debugCodes.h" +#include "../../utils/util.h" +#include "../../utils/colorSpace.h" + #include "pxr/base/gf/vec3f.h" +#include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/staticTokens.h" #include "pxr/base/tf/token.h" #include "pxr/base/vt/array.h" - #include "pxr/usd/usdGeom/mesh.h" +#include "pxr/usd/usdGeom/tokens.h" +#include "pxr/usd/usdUtils/pipeline.h" #include #include +#include #include +#include +#include +#include +#include +#include #include +#include #include +#include +#include #include +#include PXR_NAMESPACE_OPEN_SCOPE @@ -64,6 +83,489 @@ PXRUSDMAYA_REGISTER_ADAPTOR_ATTRIBUTE_ALIAS( UsdGeomTokens->faceVaryingLinearInterpolation, "USD_faceVaryingLinearInterpolation"); +namespace +{ + bool addCreaseSet( const std::string &rootName, + double creaseLevel, + MSelectionList &componentList, + MStatus *statusOK ) + { + // Crease Set functionality is native to Maya, but undocumented and not + // directly supported in the API. The below implementation is derived from + // the editor code in the maya distro at: + // + // .../lib/python2.7/site-packages/maya/app/general/creaseSetEditor.py + + MObject creasePartitionObj; + *statusOK = UsdMayaUtil::GetMObjectByName(":creasePartition", + creasePartitionObj); + + if (creasePartitionObj.isNull()) { + statusOK->clear(); + + // There is no documented way to create a shared node through the C++ API + const std::string partitionName = MGlobal::executeCommandStringResult( + "createNode \"partition\" -shared -name \":creasePartition\"").asChar(); + + *statusOK = UsdMayaUtil::GetMObjectByName(partitionName, + creasePartitionObj); + if (!*statusOK) { + return false; + } + } + + MFnPartition creasePartition( creasePartitionObj, statusOK ); + if (!*statusOK) return false; + + std::string creaseSetname = + TfStringPrintf("%s_creaseSet#",rootName.c_str()); + + MFnDependencyNode creaseSetFn; + MObject creaseSetObj = + creaseSetFn.create("creaseSet", creaseSetname.c_str(), statusOK ); + if (!*statusOK) return false; + + MPlug levelPlug = creaseSetFn.findPlug("creaseLevel",false, statusOK); + if (!*statusOK) return false; + + *statusOK = levelPlug.setValue(creaseLevel); + if (!*statusOK) return false; + + *statusOK = creasePartition.addMember(creaseSetObj); + if (!*statusOK) return false; + + MFnSet creaseSet( creaseSetObj, statusOK ); + if (!*statusOK) return false; + + *statusOK = creaseSet.addMembers( componentList ); + if (!*statusOK) return false; + + return true; + } + + MIntArray + getMayaFaceVertexAssignmentIds( const MFnMesh& meshFn, + const TfToken& interpolation, + const VtIntArray& assignmentIndices, + const int unauthoredValuesIndex) + { + MIntArray valueIds(meshFn.numFaceVertices(), -1); + + MItMeshFaceVertex itFV(meshFn.object()); + unsigned int fvi = 0; + for (itFV.reset(); !itFV.isDone(); itFV.next(), ++fvi) { + int valueId = 0; + if (interpolation == UsdGeomTokens->constant) { + valueId = 0; + } else if (interpolation == UsdGeomTokens->uniform) { + valueId = itFV.faceId(); + } else if (interpolation == UsdGeomTokens->vertex) { + valueId = itFV.vertId(); + } else if (interpolation == UsdGeomTokens->faceVarying) { + valueId = fvi; + } + + if (static_cast(valueId) < assignmentIndices.size()) { + // The data is indexed, so consult the indices array for the + // correct index into the data. + valueId = assignmentIndices[valueId]; + + if (valueId == unauthoredValuesIndex) { + // This component had no authored value, so leave it unassigned. + continue; + } + } + + valueIds[fvi] = valueId; + } + + return valueIds; + } + + bool + assignUVSetPrimvarToMesh(const UsdGeomPrimvar& primvar, MFnMesh& meshFn) + { + const TfToken& primvarName = primvar.GetPrimvarName(); + + // Get the raw data before applying any indexing. + VtVec2fArray uvValues; + if (!primvar.Get(&uvValues) || uvValues.empty()) { + TF_WARN("Could not read UV values from primvar '%s' on mesh: %s", + primvarName.GetText(), + primvar.GetAttr().GetPrimPath().GetText()); + return false; + } + + // This is the number of UV values assuming the primvar is NOT indexed. + VtIntArray assignmentIndices; + if (primvar.GetIndices(&assignmentIndices)) { + // The primvar IS indexed, so the indices array is what determines the + // number of UV values. + int unauthoredValuesIndex = primvar.GetUnauthoredValuesIndex(); + + // Replace any index equal to unauthoredValuesIndex with -1. + if (unauthoredValuesIndex != -1) { + for (int& index : assignmentIndices) { + if (index == unauthoredValuesIndex) { + index = -1; + } + } + } + + // Furthermore, if unauthoredValuesIndex is valid for uvValues, then + // remove it from uvValues and shift the indices (we don't want to + // import the unauthored value into Maya, where it has no meaning). + if (unauthoredValuesIndex >= 0 && + static_cast(unauthoredValuesIndex) < uvValues.size()) { + // This moves [unauthoredValuesIndex + 1, end) to + // [unauthoredValuesIndex, end - 1), erasing the + // unauthoredValuesIndex. + std::move( + uvValues.begin() + unauthoredValuesIndex + 1, + uvValues.end(), + uvValues.begin() + unauthoredValuesIndex); + uvValues.pop_back(); + + for (int& index : assignmentIndices) { + if (index > unauthoredValuesIndex) { + index = index - 1; + } + } + } + } + + // Go through the UV data and add the U and V values to separate + // MFloatArrays. + MFloatArray uCoords; + MFloatArray vCoords; + for (const GfVec2f& v : uvValues) { + uCoords.append(v[0]); + vCoords.append(v[1]); + } + + MStatus status{MS::kSuccess}; + MString uvSetName(primvarName.GetText()); + if (primvarName == UsdUtilsGetPrimaryUVSetName()) { + // We assume that the primary USD UV set maps to Maya's default 'map1' + // set which always exists, so we shouldn't try to create it. + uvSetName = "map1"; + } else { + status = meshFn.createUVSet(uvSetName); + if (status != MS::kSuccess) { + TF_WARN("Unable to create UV set '%s' for mesh: %s", + uvSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + } + + // The following two lines should have no effect on user-visible state but + // prevent a Maya crash in MFnMesh.setUVs after creating a crease set. + // XXX this workaround is needed pending a fix by Autodesk. + MString currentSet = meshFn.currentUVSetName(); + meshFn.setCurrentUVSetName(currentSet); + + // Create UVs on the mesh from the values we collected out of the primvar. + // We'll assign mesh components to these values below. + status = meshFn.setUVs(uCoords, vCoords, &uvSetName); + if (status != MS::kSuccess) { + TF_WARN("Unable to set UV data on UV set '%s' for mesh: %s", + uvSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + + const TfToken& interpolation = primvar.GetInterpolation(); + + // Build an array of value assignments for each face vertex in the mesh. + // Any assignments left as -1 will not be assigned a value. + MIntArray uvIds = getMayaFaceVertexAssignmentIds(meshFn, + interpolation, + assignmentIndices, + -1); + + MIntArray vertexCounts; + MIntArray vertexList; + status = meshFn.getVertices(vertexCounts, vertexList); + if (status != MS::kSuccess) { + TF_WARN("Could not get vertex counts for UV set '%s' on mesh: %s", + uvSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + + status = meshFn.assignUVs(vertexCounts, uvIds, &uvSetName); + if (status != MS::kSuccess) { + TF_WARN("Could not assign UV values to UV set '%s' on mesh: %s", + uvSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + + return true; + } + + bool + assignColorSetPrimvarToMesh(const UsdGeomMesh& mesh, + const UsdGeomPrimvar& primvar, + MFnMesh& meshFn) + { + + const TfToken& primvarName = primvar.GetPrimvarName(); + const SdfValueTypeName& typeName = primvar.GetTypeName(); + + MString colorSetName(primvarName.GetText()); + + // If the primvar is displayOpacity and it is a FloatArray, check if + // displayColor is authored. If not, we'll import this 'displayOpacity' + // primvar as a 'displayColor' color set. This supports cases where the + // user created a single channel value for displayColor. + // Note that if BOTH displayColor and displayOpacity are authored, they will + // be imported as separate color sets. We do not attempt to combine them + // into a single color set. + if (primvarName == UsdMayaMeshColorSetTokens->DisplayOpacityColorSetName && + typeName == SdfValueTypeNames->FloatArray) { + if (!UsdMayaRoundTripUtil::IsAttributeUserAuthored(mesh.GetDisplayColorPrimvar())) { + colorSetName = UsdMayaMeshColorSetTokens->DisplayColorColorSetName.GetText(); + } + } + + // We'll need to convert colors from linear to display if this color set is + // for display colors. + const bool isDisplayColor = + (colorSetName == UsdMayaMeshColorSetTokens->DisplayColorColorSetName.GetText()); + + // Get the raw data before applying any indexing. We'll only populate one + // of these arrays based on the primvar's typeName, and we'll also set the + // color representation so we know which array to use later. + VtFloatArray alphaArray; + VtVec3fArray rgbArray; + VtVec4fArray rgbaArray; + MFnMesh::MColorRepresentation colorRep; + size_t numValues = 0; + + MStatus status = MS::kSuccess; + + if (typeName == SdfValueTypeNames->FloatArray) { + colorRep = MFnMesh::kAlpha; + if (!primvar.Get(&alphaArray) || alphaArray.empty()) { + status = MS::kFailure; + } else { + numValues = alphaArray.size(); + } + } else if (typeName == SdfValueTypeNames->Float3Array || + typeName == SdfValueTypeNames->Color3fArray) { + colorRep = MFnMesh::kRGB; + if (!primvar.Get(&rgbArray) || rgbArray.empty()) { + status = MS::kFailure; + } else { + numValues = rgbArray.size(); + } + } else if (typeName == SdfValueTypeNames->Float4Array || + typeName == SdfValueTypeNames->Color4fArray) { + colorRep = MFnMesh::kRGBA; + if (!primvar.Get(&rgbaArray) || rgbaArray.empty()) { + status = MS::kFailure; + } else { + numValues = rgbaArray.size(); + } + } else { + TF_WARN("Unsupported color set primvar type '%s' for primvar '%s' on " + "mesh: %s", + typeName.GetAsToken().GetText(), + primvarName.GetText(), + primvar.GetAttr().GetPrimPath().GetText()); + return false; + } + + if (status != MS::kSuccess || numValues == 0) { + TF_WARN("Could not read color set values from primvar '%s' on mesh: %s", + primvarName.GetText(), + primvar.GetAttr().GetPrimPath().GetText()); + return false; + } + + VtIntArray assignmentIndices; + int unauthoredValuesIndex = -1; + if (primvar.GetIndices(&assignmentIndices)) { + // The primvar IS indexed, so the indices array is what determines the + // number of color values. + numValues = assignmentIndices.size(); + unauthoredValuesIndex = primvar.GetUnauthoredValuesIndex(); + } + + // Go through the color data and translate the values into MColors in the + // colorArray, taking into consideration that indexed data may have been + // authored sparsely. If the assignmentIndices array is empty then the data + // is NOT indexed. + // Note that with indexed data, the data is added to the arrays in ascending + // component ID order according to the primvar's interpolation (ascending + // face ID for uniform interpolation, ascending vertex ID for vertex + // interpolation, etc.). This ordering may be different from the way the + // values are ordered in the primvar. Because of this, we recycle the + // assignmentIndices array as we go to store the new mapping from component + // index to color index. + MColorArray colorArray; + for (size_t i = 0; i < numValues; ++i) { + int valueIndex = i; + + if (i < assignmentIndices.size()) { + // The data is indexed, so consult the indices array for the + // correct index into the data. + valueIndex = assignmentIndices[i]; + + if (valueIndex == unauthoredValuesIndex) { + // This component is unauthored, so just update the + // mapping in assignmentIndices and then skip the value. + // We don't actually use the value at the unassigned index. + assignmentIndices[i] = -1; + continue; + } + + // We'll be appending a new value, so the current length of the + // array gives us the new value's index. + assignmentIndices[i] = colorArray.length(); + } + + GfVec4f colorValue(1.0); + + switch(colorRep) { + case MFnMesh::kAlpha: + colorValue[3] = alphaArray[valueIndex]; + break; + case MFnMesh::kRGB: + colorValue[0] = rgbArray[valueIndex][0]; + colorValue[1] = rgbArray[valueIndex][1]; + colorValue[2] = rgbArray[valueIndex][2]; + break; + case MFnMesh::kRGBA: + colorValue[0] = rgbaArray[valueIndex][0]; + colorValue[1] = rgbaArray[valueIndex][1]; + colorValue[2] = rgbaArray[valueIndex][2]; + colorValue[3] = rgbaArray[valueIndex][3]; + break; + default: + break; + } + + if (isDisplayColor) { + colorValue = UsdMayaColorSpace::ConvertLinearToMaya(colorValue); + } + + MColor mColor(colorValue[0], colorValue[1], colorValue[2], colorValue[3]); + colorArray.append(mColor); + } + + // colorArray now stores all of the values and any unassigned components + // have had their indices set to -1, so update the unauthored values index. + unauthoredValuesIndex = -1; + + const bool clamped = UsdMayaRoundTripUtil::IsPrimvarClamped(primvar); + + status = meshFn.createColorSet(colorSetName, nullptr, clamped, colorRep); + if (status != MS::kSuccess) { + TF_WARN("Unable to create color set '%s' for mesh: %s", + colorSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + + // Create colors on the mesh from the values we collected out of the + // primvar. We'll assign mesh components to these values below. + status = meshFn.setColors(colorArray, &colorSetName, colorRep); + if (status != MS::kSuccess) { + TF_WARN("Unable to set color data on color set '%s' for mesh: %s", + colorSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + + const TfToken& interpolation = primvar.GetInterpolation(); + + // Build an array of value assignments for each face vertex in the mesh. + // Any assignments left as -1 will not be assigned a value. + MIntArray colorIds = getMayaFaceVertexAssignmentIds(meshFn, + interpolation, + assignmentIndices, + unauthoredValuesIndex); + + status = meshFn.assignColors(colorIds, &colorSetName); + if (status != MS::kSuccess) { + TF_WARN("Could not assign color values to color set '%s' on mesh: %s", + colorSetName.asChar(), + meshFn.fullPathName().asChar()); + return false; + } + + // we only visualize the colorset by default if it is "displayColor". + // this is a limitation and affects user experience. This needs further review. HS, 1-Nov-2019 + MStringArray colorSetNames; + if (meshFn.getColorSetNames(colorSetNames) == MS::kSuccess) + { + for (unsigned int i = 0u; i < colorSetNames.length(); ++i) + { + const MString colorSetName = colorSetNames[i]; + + if (std::string(colorSetName.asChar()) + == UsdMayaMeshColorSetTokens->DisplayColorColorSetName.GetString()) + { + const auto csRep = meshFn.getColorRepresentation(colorSetName); + + if (csRep == MFnMesh::kRGB || csRep == MFnMesh::kRGBA) + { + meshFn.setCurrentColorSetName(colorSetName); + } + break; + } + } + + MPlug plg = meshFn.findPlug("displayColors"); + if (!plg.isNull()) { + plg.setBool(true); + } + } + + return true; + } + + bool + assignConstantPrimvarToMesh(const UsdGeomPrimvar& primvar, MFnMesh& meshFn) + { + const TfToken& interpolation = primvar.GetInterpolation(); + if (interpolation != UsdGeomTokens->constant) { + return false; + } + + const TfToken& name = primvar.GetBaseName(); + const SdfValueTypeName& typeName = primvar.GetTypeName(); + const SdfVariability& variability = SdfVariabilityUniform; + + MObject attrObj = + UsdMayaReadUtil::FindOrCreateMayaAttr( + typeName, + variability, + meshFn, + name.GetText()); + if (attrObj.isNull()) { + return false; + } + + VtValue primvarData; + primvar.Get(&primvarData); + + MStatus status{MS::kSuccess}; + MPlug plug = meshFn.findPlug( + name.GetText(), + /* wantNetworkedPlug = */ true, + &status); + if (!status || plug.isNull()) { + return false; + } + + return UsdMayaReadUtil::SetMayaAttr(plug, primvarData); + } +} + // This can be customized for specific pipelines. bool UsdMayaMeshUtil::GetEmitNormalsTag(const MFnMesh& mesh, bool* value) @@ -82,7 +584,7 @@ UsdMayaMeshUtil::SetEmitNormalsTag( MFnMesh& meshFn, const bool emitNormals) { - MStatus status; + MStatus status{MS::kSuccess}; MFnNumericAttribute nAttr; MObject attr = nAttr.create(_meshTokens->USD_EmitNormals.GetText(), "", MFnNumericData::kBoolean, 0, &status); @@ -97,12 +599,11 @@ UsdMayaMeshUtil::SetEmitNormalsTag( bool UsdMayaMeshUtil::GetMeshNormals( - const MObject& meshObj, + const MFnMesh& mesh, VtArray* normalsArray, TfToken* interpolation) { - MStatus status; - MFnMesh mesh(meshObj); + MStatus status{MS::kSuccess}; // Sanity check first to make sure we can get this mesh's normals. int numNormals = mesh.numNormals(&status); @@ -126,8 +627,8 @@ UsdMayaMeshUtil::GetMeshNormals( normalsArray->resize(numFaceVertices); *interpolation = UsdGeomTokens->faceVarying; - - MItMeshFaceVertex itFV(meshObj); + + MItMeshFaceVertex itFV(mesh.object()); unsigned int fvi = 0; for (itFV.reset(); !itFV.isDone(); itFV.next(), ++fvi) { int normalId = itFV.normalId(); @@ -323,5 +824,300 @@ TfToken UsdMayaMeshUtil::GetSubdivFVLinearInterpolation(const MFnMesh& mesh) return sdFVLinearInterpolation; } -PXR_NAMESPACE_CLOSE_SCOPE +void +UsdMayaMeshUtil::assignPrimvarsToMesh(const UsdGeomMesh& mesh, + const MObject& meshObj, + const TfToken::Set& excludePrimvarSet) +{ + if(meshObj.apiType() != MFn::kMesh){ + return; + } + + MFnMesh meshFn(meshObj); + + // GETTING PRIMVARS + const std::vector primvars = mesh.GetPrimvars(); + TF_FOR_ALL(iter, primvars) + { + const UsdGeomPrimvar& primvar = *iter; + const TfToken name = primvar.GetBaseName(); + const TfToken fullName = primvar.GetPrimvarName(); + const SdfValueTypeName typeName = primvar.GetTypeName(); + const TfToken& interpolation = primvar.GetInterpolation(); + + // Exclude primvars using the full primvar name without "primvars:". + // This applies to all primvars; we don't care if it's a color set, a + // UV set, etc. + if (excludePrimvarSet.count(fullName) != 0) { + continue; + } + + // If the primvar is called either displayColor or displayOpacity check + // if it was really authored from the user. It may not have been + // authored by the user, for example if it was generated by shader + // values and not an authored colorset/entity. + // If it was not really authored, we skip the primvar. + if (name == UsdMayaMeshColorSetTokens->DisplayColorColorSetName || + name == UsdMayaMeshColorSetTokens->DisplayOpacityColorSetName) { + if (!UsdMayaRoundTripUtil::IsAttributeUserAuthored(primvar)) { + continue; + } + } + + // XXX: Maya stores UVs in MFloatArrays and color set data in MColors + // which store floats, so we currently only import primvars holding + // float-typed arrays. Should we still consider other precisions + // (double, half, ...) and/or numeric types (int)? + if (typeName == SdfValueTypeNames->TexCoord2fArray || + (UsdMayaReadUtil::ReadFloat2AsUV() && + typeName == SdfValueTypeNames->Float2Array)) { + // Looks for TexCoord2fArray types for UV sets first + // Otherwise, if env variable for reading Float2 + // as uv sets is turned on, we assume that Float2Array primvars + // are UV sets. + if (!assignUVSetPrimvarToMesh(primvar, meshFn)) { + TF_WARN("Unable to retrieve and assign data for UV set <%s> on " + "mesh <%s>", + name.GetText(), + mesh.GetPrim().GetPath().GetText()); + } + } else if (typeName == SdfValueTypeNames->FloatArray || + typeName == SdfValueTypeNames->Float3Array || + typeName == SdfValueTypeNames->Color3fArray || + typeName == SdfValueTypeNames->Float4Array || + typeName == SdfValueTypeNames->Color4fArray) { + if (!assignColorSetPrimvarToMesh(mesh, primvar, meshFn)) { + TF_WARN("Unable to retrieve and assign data for color set <%s> " + "on mesh <%s>", + name.GetText(), + mesh.GetPrim().GetPath().GetText()); + } + } else if (interpolation == UsdGeomTokens->constant) { + // Constant primvars get added as attributes on the mesh. + if (!assignConstantPrimvarToMesh(primvar, meshFn)) { + TF_WARN("Unable to assign constant primvar <%s> as attribute " + "on mesh <%s>", + name.GetText(), + mesh.GetPrim().GetPath().GetText()); + } + } + } +} + +void +UsdMayaMeshUtil::assignInvisibleFaces(const UsdGeomMesh& mesh, const MObject& meshObj) +{ + if(meshObj.apiType() != MFn::kMesh){ + return; + } + + MFnMesh meshFn(meshObj); + + // Set Holes + VtIntArray holeIndices; + mesh.GetHoleIndicesAttr().Get(&holeIndices); // not animatable + if (!holeIndices.empty()) { + MUintArray mayaHoleIndices; + mayaHoleIndices.setLength(holeIndices.size()); + for (size_t i = 0u; i < holeIndices.size(); ++i) { + mayaHoleIndices[i] = holeIndices[i]; + } + + if (meshFn.setInvisibleFaces(mayaHoleIndices) != MS::kSuccess) { + TF_RUNTIME_ERROR("Unable to set Invisible Faces on <%s>", + meshFn.fullPathName().asChar()); + } + } +} + +MStatus +UsdMayaMeshUtil::assignSubDivTagsToMesh( const UsdGeomMesh& mesh, + MObject& meshObj, + MFnMesh& meshFn ) +{ + // We may want to provide the option in the future, but for now, we + // default to using crease sets when setting crease data. + // + const bool USE_CREASE_SETS = true; + + MStatus statusOK{MS::kSuccess}; + + MDagPath meshPath; + statusOK = MDagPath::getAPathTo(meshObj,meshPath); + + if (!statusOK){ + return MS::kFailure; + } + + // USD does not support grouped verts and edges, so combine all components + // with the same weight into one set to reduce the overall crease set + // count. The user can always split the sets up later if desired. + // + // This structure is unused if crease sets aren't being created. + std::unordered_map elemsPerWeight; + + // Vert Creasing + VtArray subdCornerIndices; + VtArray subdCornerSharpnesses; + mesh.GetCornerIndicesAttr().Get(&subdCornerIndices); // not animatable + mesh.GetCornerSharpnessesAttr().Get(&subdCornerSharpnesses); // not animatable + if (!subdCornerIndices.empty()) { + if (subdCornerIndices.size() == subdCornerSharpnesses.size() ) { + statusOK.clear(); + + if (USE_CREASE_SETS) { + MItMeshVertex vertIt(meshObj); + for (unsigned int i=0; i < subdCornerIndices.size(); i++) { + + // Ignore zero-sharpness corners + if (subdCornerSharpnesses[i]==0) + continue; + + MSelectionList &elemList = + elemsPerWeight[ subdCornerSharpnesses[i] ]; + + int prevIndexDummy; // dummy param + statusOK = vertIt.setIndex(subdCornerIndices[i], prevIndexDummy); + if (!statusOK) + break; + statusOK = elemList.add(meshPath,vertIt.currentItem()); + if (!statusOK) + break; + } + + } else { + MUintArray mayaCreaseVertIds; + MDoubleArray mayaCreaseVertValues; + mayaCreaseVertIds.setLength( subdCornerIndices.size() ); + mayaCreaseVertValues.setLength( subdCornerIndices.size() ); + for (unsigned int i=0; i < subdCornerIndices.size(); i++) { + + // Ignore zero-sharpness corners + if (subdCornerSharpnesses[i]==0) + continue; + + mayaCreaseVertIds[i] = subdCornerIndices[i]; + mayaCreaseVertValues[i] = subdCornerSharpnesses[i]; + } + statusOK = meshFn.setCreaseVertices(mayaCreaseVertIds, mayaCreaseVertValues); + } + + if (!statusOK) { + TF_RUNTIME_ERROR("Unable to set Crease Vertices on <%s>: %s", + meshFn.fullPathName().asChar(), + statusOK.errorString().asChar()); + return MS::kFailure; + } + } else { + TF_RUNTIME_ERROR( + "Mismatch between Corner Indices & Sharpness on <%s>", + mesh.GetPrim().GetPath().GetText()); + return MS::kFailure; + } + } + + // Edge Creasing + VtArray subdCreaseLengths; + VtArray subdCreaseIndices; + VtArray subdCreaseSharpnesses; + mesh.GetCreaseLengthsAttr().Get(&subdCreaseLengths); + mesh.GetCreaseIndicesAttr().Get(&subdCreaseIndices); + mesh.GetCreaseSharpnessesAttr().Get(&subdCreaseSharpnesses); + if (!subdCreaseLengths.empty()) { + if (subdCreaseLengths.size() == subdCreaseSharpnesses.size() ) { + MUintArray mayaCreaseEdgeIds; + MDoubleArray mayaCreaseEdgeValues; + MIntArray connectedEdges; + unsigned int creaseIndexBase = 0; + + statusOK.clear(); + + for (unsigned int creaseGroup=0; + statusOK && creaseGroup: %s", + meshFn.fullPathName().asChar(), + statusOK.errorString().asChar()); + return MS::kFailure; + } + + } else { + TF_RUNTIME_ERROR( + "Mismatch between Crease Lengths & Sharpness on <%s>", + mesh.GetPrim().GetPath().GetText()); + return MS::kFailure; + } + } + + if (USE_CREASE_SETS) { + TF_FOR_ALL(weightList, elemsPerWeight) { + double creaseLevel = weightList->first; + MSelectionList &elemList = weightList->second; + + if (!addCreaseSet( meshFn.name().asChar(), + creaseLevel, elemList, &statusOK )){ + TF_RUNTIME_ERROR("Unable to set crease sets on <%s>: %s", + meshFn.fullPathName().asChar(), + statusOK.errorString().asChar()); + return MS::kFailure; + } + } + } + + return MS::kSuccess; +} + +PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/fileio/utils/meshUtil.h b/lib/fileio/utils/meshUtil.h index 6f5c246694..a87670d92d 100644 --- a/lib/fileio/utils/meshUtil.h +++ b/lib/fileio/utils/meshUtil.h @@ -13,8 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. // - -/// \file usdMaya/meshUtil.h +// Modifications copyright (C) 2020 Autodesk +// #ifndef PXRUSDMAYA_MESH_UTIL_H #define PXRUSDMAYA_MESH_UTIL_H @@ -26,7 +26,11 @@ #include "pxr/base/tf/staticTokens.h" #include "pxr/base/tf/token.h" #include "pxr/base/vt/array.h" +#include "pxr/usd/usd/attribute.h" +#include "pxr/usd/usdGeom/mesh.h" +#include +#include #include #include @@ -59,7 +63,7 @@ namespace UsdMayaMeshUtil /// Helper method for getting Maya mesh normals as a VtVec3fArray. MAYAUSD_CORE_PUBLIC bool GetMeshNormals( - const MObject& mesh, + const MFnMesh& mesh, VtArray* normalsArray, TfToken* interpolation); @@ -82,6 +86,15 @@ namespace UsdMayaMeshUtil MAYAUSD_CORE_PUBLIC TfToken GetSubdivFVLinearInterpolation(const MFnMesh& mesh); + MAYAUSD_CORE_PUBLIC + void assignPrimvarsToMesh(const UsdGeomMesh&, const MObject&, const TfToken::Set&); + + MAYAUSD_CORE_PUBLIC + void assignInvisibleFaces(const UsdGeomMesh& mesh, const MObject& meshObj); + + MAYAUSD_CORE_PUBLIC + MStatus assignSubDivTagsToMesh(const UsdGeomMesh&, MObject&, MFnMesh&); + } // namespace UsdMayaMeshUtil diff --git a/lib/usd/translators/meshReader.cpp b/lib/usd/translators/meshReader.cpp index 3ae1fa8ff7..c14aad784a 100644 --- a/lib/usd/translators/meshReader.cpp +++ b/lib/usd/translators/meshReader.cpp @@ -15,17 +15,43 @@ // // Modifications copyright (C) 2020 Autodesk // - #include "pxr/pxr.h" #include "../../fileio/primReaderRegistry.h" +#include "../../fileio/translators/translatorGprim.h" +#include "../../fileio/translators/translatorMaterial.h" #include "../../fileio/translators/translatorMesh.h" +#include "../../fileio/translators/translatorUtil.h" +#include "../../fileio/utils/meshUtil.h" +#include "../../utils/util.h" +#include "../../fileio/utils/readUtil.h" +#include "../../nodes/stageNode.h" + +#include #include "pxr/usd/usdGeom/mesh.h" PXR_NAMESPACE_OPEN_SCOPE -// Prim reader for mesh +namespace +{ + bool + assignMaterial(const UsdGeomMesh& mesh, + const UsdMayaPrimReaderArgs& args, + const MObject& meshObj, + UsdMayaPrimReaderContext* context) + { + // If a material is bound, create (or reuse if already present) and assign it + // If no binding is present, assign the mesh to the default shader + const TfToken& shadingMode = args.GetShadingMode(); + return UsdMayaTranslatorMaterial::AssignMaterial(shadingMode, + mesh, + meshObj, + context); + } +} + +// prim reader for mesh class MayaUsdPrimReaderMesh final : public UsdMayaPrimReader { public: @@ -48,14 +74,79 @@ TF_REGISTRY_FUNCTION_WITH_TAG(UsdMayaPrimReaderRegistry, UsdGeomMesh) bool MayaUsdPrimReaderMesh::Read(UsdMayaPrimReaderContext* context) { - const UsdPrim& usdPrim = _GetArgs().GetUsdPrim(); + if(!context){ + return false; + } + + MStatus status{MS::kSuccess}; + + const auto& prim = _GetArgs().GetUsdPrim(); + auto mesh = UsdGeomMesh(prim); + if (!mesh) { + return false; + } + + auto parentNode = context->GetMayaNode(prim.GetPath().GetParentPath(), true); + MObject transformObj; + bool retStatus = UsdMayaTranslatorUtil::CreateTransformNode(prim, + parentNode, + _GetArgs(), + context, + &status, + &transformObj); + if(!retStatus){ + return false; + } + + // get the USD stage node from the context's registry + MObject stageNode; + if (_GetArgs().GetUseAsAnimationCache()){ + stageNode = context->GetMayaNode(SdfPath(UsdMayaStageNodeTokens->MayaTypeName.GetString()),false); + } + + MayaUsd::TranslatorMeshRead meshRead(mesh, + prim, + transformObj, + stageNode, + _GetArgs().GetTimeInterval(), + _GetArgs().GetUseAsAnimationCache(), + &status); + CHECK_MSTATUS_AND_RETURN(status, false); + + // mesh is a shape, so read Gprim properties + UsdMayaTranslatorGprim::Read(mesh, meshRead.meshObject(), context); + + // undo/redo mesh object + context->RegisterNewMayaNode(meshRead.shapePath().GetString(), meshRead.meshObject()); + + // undo/redo deformable mesh (blenshape, PointBasedDeformer) + if (meshRead.pointsNumTimeSamples() > 0) { + if (_GetArgs().GetUseAsAnimationCache()){ + context->RegisterNewMayaNode(meshRead.pointBasedDeformerName().asChar(), + meshRead.pointBasedDeformerNode()); + } + else { + if(meshRead.blendObject().apiType() == MFn::kBlend){ + return false; + } + + MFnBlendShapeDeformer blendFnSet(meshRead.blendObject()); + context->RegisterNewMayaNode(blendFnSet.name().asChar(), + meshRead.blendObject()); + } + } + + // assign material + assignMaterial(mesh, _GetArgs(), meshRead.meshObject(), context); - auto parentNode = context->GetMayaNode(usdPrim.GetPath().GetParentPath(), true); + // assign primvars to mesh + UsdMayaMeshUtil::assignPrimvarsToMesh( mesh, + meshRead.meshObject(), + _GetArgs().GetExcludePrimvarNames()); + // assign invisible faces + UsdMayaMeshUtil::assignInvisibleFaces(mesh, meshRead.meshObject()); - return UsdMayaTranslatorMesh::Create(UsdGeomMesh(usdPrim), - parentNode, - _GetArgs(), - context); + return true; } PXR_NAMESPACE_CLOSE_SCOPE diff --git a/lib/utils/util.cpp b/lib/utils/util.cpp index 7e3fd68c7b..17094ce285 100644 --- a/lib/utils/util.cpp +++ b/lib/utils/util.cpp @@ -2013,3 +2013,21 @@ UsdMayaUtil::GetInfiniteBoundingBox() constexpr double inf = std::numeric_limits::infinity(); return MBoundingBox(MPoint(-inf, -inf, -inf), MPoint(inf, inf, inf)); } + +MString +UsdMayaUtil::convert(const TfToken& token) +{ + return MString(token.GetText(), token.size()); +} + +std::string +UsdMayaUtil::convert(const MString& str) +{ + return std::string(str.asChar(), str.length()); +} + +MString +UsdMayaUtil::convert(const std::string& str) +{ + return MString(str.data(), static_cast(str.size())); +} diff --git a/lib/utils/util.h b/lib/utils/util.h index 961c81fdb2..13537167c2 100644 --- a/lib/utils/util.h +++ b/lib/utils/util.h @@ -551,6 +551,15 @@ bool FindAncestorSceneAssembly( MAYAUSD_CORE_PUBLIC MBoundingBox GetInfiniteBoundingBox(); +MAYAUSD_CORE_PUBLIC +MString convert(const std::string&); + +MAYAUSD_CORE_PUBLIC +MString convert(const TfToken& token); + +MAYAUSD_CORE_PUBLIC +std::string convert(const MString&); + } // namespace UsdMayaUtil