예제 #1
0
void 
_WritePrefixedAttrs(
        const UsdTimeCode &usdTime, 
        const _PrimEntry& entry)
{
    MStatus status;
    MFnDependencyNode depFn(entry.mayaDagPath.node());
    for (const auto& attrEntry : entry.attrs) {
        MPlug plg = depFn.findPlug(attrEntry.mayaAttributeName.c_str(), true);
        UsdAttribute usdAttr;

        if (attrEntry.isPrimvar) {
            // Treat as custom primvar.
            TfToken interpolation = _GetPrimvarInterpolation(
                    depFn, attrEntry.mayaAttributeName);
            UsdGeomImageable imageable(entry.usdPrim);
            if (!imageable) {
                TF_RUNTIME_ERROR(
                        "Cannot create primvar for non-UsdGeomImageable "
                        "USD prim <%s>",
                        entry.usdPrim.GetPath().GetText());
                continue;
            }
            UsdGeomPrimvar primvar = UsdMayaWriteUtil::GetOrCreatePrimvar(
                    plg,
                    imageable,
                    attrEntry.usdAttributeName,
                    interpolation,
                    -1,
                    false);
            if (primvar) {
                usdAttr = primvar.GetAttr();
            }
        }
        else {
            // Treat as custom attribute.
            usdAttr = UsdMayaWriteUtil::GetOrCreateUsdAttr(
                    plg, entry.usdPrim, attrEntry.usdAttributeName, true);
        }

        if (usdAttr) {
            UsdMayaWriteUtil::SetUsdAttr(plg, usdAttr, usdTime);
        }
        else {
            TF_RUNTIME_ERROR(
                    "Could not create attribute '%s' for "
                    "USD prim <%s>",
                    attrEntry.usdAttributeName.c_str(),
                    entry.usdPrim.GetPath().GetText());
            continue;
        }
    }
}
예제 #2
0
NdrNodeDiscoveryResultVec
UsdHydraDiscoveryPlugin::DiscoverNodes(const Context &context)
{
    NdrNodeDiscoveryResultVec result;

    static std::string shaderDefsFile = _GetShaderResourcePath(
            "shaderDefs.usda");
    if (shaderDefsFile.empty())
        return result;

    auto resolverContext = ArGetResolver().CreateDefaultContextForAsset(
            shaderDefsFile);

    const UsdStageRefPtr stage = UsdStage::Open(shaderDefsFile, 
            resolverContext);

    if (!stage) {
        TF_RUNTIME_ERROR("Could not open file '%s' on a USD stage.", 
                         shaderDefsFile.c_str());
        return result;
    }

    ArResolverContextBinder binder(resolverContext);
    const TfToken discoveryType(ArGetResolver().GetExtension(
            shaderDefsFile));

    auto rootPrims = stage->GetPseudoRoot().GetChildren();
    for (const auto &shaderDef : rootPrims) {
        UsdShadeShader shader(shaderDef);
        if (!shader) {
            continue;
        }

        auto discoveryResults = UsdShadeShaderDefUtils::GetNodeDiscoveryResults(
                shader, shaderDefsFile);

        result.insert(result.end(), discoveryResults.begin(), 
                      discoveryResults.end());

        if (discoveryResults.empty()) {
            TF_RUNTIME_ERROR("Found shader definition <%s> with no valid "
                "discovery results. This is likely because there are no "
                "resolvable info:sourceAsset values.", 
                shaderDef.GetPath().GetText());
        }
    }

    return result;
}
예제 #3
0
파일: vtExtractor.cpp 프로젝트: JT-a/USD
void
Hd_VtExtractor::Extract(const VtValue &value)
{
    // Type dispatch to _Extractor.
    //
    // Iterate over each acceptable type T, calling typeChecker<T>(), which
    // internally calls value.IsHolding<T>(); if the VtValue IS holding a T,
    // then pass the held value to _Extractor::Extract<T>(value).
    _Extractor e;

    boost::mpl::for_each<_AcceptedTypes>(_TypeChecker<_Extractor>(value, e));

    // Guarantee that the _Extractor::Extract method was called exactly once
    // and issue a runtime error otherwise. (The only case where Extract wont
    // be called is when the VtValue is holding an unacceptable type).
    if (e.GetNumComponents() == 0) {
        TF_RUNTIME_ERROR("Trying to extract a VtValue holding "
                         "unacceptable type: %s",
                         value.GetType().GetTypeName().c_str());
        return;
    }

    const _GLDataType &glDataType  = e.GetGLDataType();

    _glComponentType = glDataType.componentType;
    _glElementType   = glDataType.elementType;
    _size            = e.GetSize();
    _numComponents   = e.GetNumComponents();
    _data            = e.GetData();
}
예제 #4
0
void
HdxIntersector::SetResolution(GfVec2i const& widthHeight)
{
    TRACE_FUNCTION();

    // Make sure we're in a sane GL state before attempting anything.
    if (GlfHasLegacyGraphics()) {
        TF_RUNTIME_ERROR("framebuffer object not supported");
        return;
    }

    if (!_drawTarget) {
        // Initialize the shared draw target late to ensure there is a valid GL
        // context, which may not be the case at constructon time.
        _Init(widthHeight);
        return;
    }

    if (widthHeight == _drawTarget->GetSize()){
        return;
    }

    // Make sure master draw target is always modified on the shared context,
    // so we access it consistently.
    GlfSharedGLContextScopeHolder sharedContextHolder;
    {
        _drawTarget->Bind();
        _drawTarget->SetSize(widthHeight);
        _drawTarget->Unbind();
    }
}
예제 #5
0
static bool
_GetMetadataUnchecked(
    const MFnDependencyNode& node,
    const TfToken& key,
    VtValue* value)
{
    VtValue fallback = SdfSchema::GetInstance().GetFallback(key);
    if (fallback.IsEmpty()) {
        return false;
    }

    std::string mayaAttrName = _GetMayaAttrNameForMetadataKey(key);
    MPlug plug = node.findPlug(mayaAttrName.c_str());
    if (plug.isNull()) {
        return false;
    }

    TfType ty = fallback.GetType();
    VtValue result = UsdMayaWriteUtil::GetVtValue(plug, ty, TfToken());
    if (result.IsEmpty()) {
        TF_RUNTIME_ERROR(
                "Cannot convert plug '%s' into metadata '%s' (%s)",
                plug.name().asChar(),
                key.GetText(),
                ty.GetTypeName().c_str());
        return false;
    }

    *value = result;
    return true;
}
예제 #6
0
파일: registry.cpp 프로젝트: 400dama/USD
void
KindRegistry::_RegisterDefaults()
{
    // Initialize builtin kind hierarchy.
    _Register(KindTokens->subcomponent);
    _Register(KindTokens->model);
    _Register(KindTokens->component, KindTokens->model);
    _Register(KindTokens->group, KindTokens->model);
    _Register(KindTokens->assembly, KindTokens->group);

    // Check plugInfo for extensions to the kind hierarchy.
    //
    // XXX We only do this once, and do not re-build the kind hierarchy
    //     if someone manages to add more plugins while the app is running.
    //     This allows the KindRegistry to be threadsafe without locking.
    const PlugPluginPtrVector& plugins = 
        PlugRegistry::GetInstance().GetAllPlugins();
    TF_FOR_ALL(plug, plugins){
        JsObject kinds;
        const JsObject &metadata = (*plug)->GetMetadata();
        if (!_GetKey(metadata, _tokens->PluginKindsKey, &kinds)) continue;

        TF_FOR_ALL(kindEntry, kinds){
            // Each entry is a map from kind -> metadata dict.
            TfToken  kind(kindEntry->first);
            JsObject   kindDict;
            if (!_GetKey(kinds, kind, &kindDict)){
                TF_RUNTIME_ERROR("Expected dict for kind '%s'",
                                 kind.GetText());
                continue;
            }

            // Check for baseKind.
            TfToken baseKind;
            JsObject::const_iterator i = kindDict.find("baseKind");
            if (i != kindDict.end()) {
                if (i->second.IsString())  {
                    baseKind = TfToken(i->second.GetString());
                } else {
                    TF_RUNTIME_ERROR("Expected string for baseKind");
                    continue;
                }
            }
            _Register(kind, baseKind);
        }
예제 #7
0
void _TestGusdTfErrorScope()
{
    std::cout << "Test GusdTfErrorScope" << std::endl;

    // Severity is user-configured. Test each severity level.
    for(int i = UT_ERROR_MESSAGE; i < UT_NUM_ERROR_SEVERITIES; ++i) {
        
        auto sev = static_cast<UT_ErrorSeverity>(i);

        UT_ErrorManager::Scope scope;
        {
            GusdTfErrorScope tfErrScope(sev);
            TF_CODING_ERROR("(coding error)");
            TF_RUNTIME_ERROR("(runtime error)");
        }
        TF_AXIOM(scope.getErrorManager().getNumErrors() == 2);
        TF_AXIOM(scope.getSeverity() == sev);
        TF_AXIOM(TfStringContains(GusdGetErrors(), "(coding error)"));
        TF_AXIOM(TfStringContains(GusdGetErrors(), "(runtime error)"));
    }

    // Setting severity of NONE means errors are ignored.
    {
        UT_ErrorManager::Scope scope;
        {
            GusdTfErrorScope tfErrScope(UT_ERROR_NONE);
            TF_CODING_ERROR("(coding error)");
            TF_RUNTIME_ERROR("(runtime error)");
        }
        TF_AXIOM(scope.getErrorManager().getNumErrors() == 0);
        TF_AXIOM(scope.getSeverity() == UT_ERROR_NONE);
    }

    // Test workaround for errors containing '<>' chars, which normally
    // won't display in node MMB menus due to HTML formatting.
    {
        UT_ErrorManager::Scope scope;
        {
            GusdTfErrorScope tfErrScope;
            TF_CODING_ERROR("<foo>");
        }
        TF_AXIOM(TfStringContains(GusdGetErrors(), "[foo]"));
    }
}
예제 #8
0
void 
UsdImagingMaterialAdapter::_GetMaterialNetworkMap(UsdPrim const &usdPrim, 
    HdMaterialNetworkMap *materialNetworkMap) const
{
    UsdShadeMaterial material(usdPrim);
    if (!material) {
        TF_RUNTIME_ERROR("Expected material prim at <%s> to be of type "
                         "'UsdShadeMaterial', not type '%s'; ignoring",
                         usdPrim.GetPath().GetText(),
                         usdPrim.GetTypeName().GetText());
        return;
    }
    const TfToken context = _GetMaterialNetworkSelector();
    if (UsdShadeShader s = material.ComputeSurfaceSource(context)) {
        _WalkGraph(s, &materialNetworkMap->map[UsdImagingTokens->bxdf],
                  _GetShaderSourceTypes());
    }
    if (UsdShadeShader d = material.ComputeDisplacementSource(context)) {
        _WalkGraph(d, &materialNetworkMap->map[UsdImagingTokens->displacement],
                  _GetShaderSourceTypes());
    }
}
static
bool
_AssignMaterialFaceSet(
        const MObject& shadingEngine,
        const MDagPath& shapeDagPath,
        const VtIntArray& faceIndices)
{
    MStatus status;

    // Create component object using single indexed
    // components, i.e. face indices.
    MFnSingleIndexedComponent compFn;
    MObject faceComp = compFn.create(MFn::kMeshPolygonComponent, &status);
    if (!status) {
        TF_RUNTIME_ERROR("Failed to create face component.");
        return false;
    }

    MIntArray mFaces;
    TF_FOR_ALL(fIdxIt, faceIndices) {
        mFaces.append(*fIdxIt);
    }
예제 #10
0
bool
HdxIntersector::Query(HdxIntersector::Params const& params,
                      HdRprimCollection const& col,
                      HdEngine* engine,
                      HdxIntersector::Result* result)
{
    TRACE_FUNCTION();

    // Make sure we're in a sane GL state before attempting anything.
    if (GlfHasLegacyGraphics()) {
        TF_RUNTIME_ERROR("framebuffer object not supported");
        return false;
    }
    GlfGLContextSharedPtr context = GlfGLContext::GetCurrentGLContext();
    if (!TF_VERIFY(context)) {
        TF_RUNTIME_ERROR("Invalid GL context");
        return false;
    }
    if (!_drawTarget) {
        // Initialize the shared draw target late to ensure there is a valid GL
        // context, which may not be the case at constructon time.
        _Init(GfVec2i(128,128));
    }

    GfVec2i size(_drawTarget->GetSize());
    GfVec4i viewport(0, 0, size[0], size[1]);

    if (!TF_VERIFY(_renderPass)) {
        return false;
    }
    _renderPass->SetRprimCollection(col);

    // Setup state based on incoming params.
    _renderPassState->SetAlphaThreshold(params.alphaThreshold);
    _renderPassState->SetClipPlanes(params.clipPlanes);
    _renderPassState->SetCullStyle(params.cullStyle);
    _renderPassState->SetCamera(params.viewMatrix, params.projectionMatrix, viewport);
    _renderPassState->SetLightingEnabled(false);

    // Use a separate drawTarget (framebuffer object) for each GL context
    // that uses this renderer, but the drawTargets share attachments/textures.
    GlfDrawTargetRefPtr drawTarget = GlfDrawTarget::New(size);

    // Clone attachments into this context. Note that this will do a
    // light-weight copy of the textures, it does not produce a full copy of the
    // underlying images.
    drawTarget->Bind();
    drawTarget->CloneAttachments(_drawTarget);

    //
    // Setup GL raster state
    //

    GLenum drawBuffers[3] = { GL_COLOR_ATTACHMENT0,
                              GL_COLOR_ATTACHMENT1,
                              GL_COLOR_ATTACHMENT2 };
    glDrawBuffers(3, drawBuffers);
    
    glDisable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    glDisable(GL_BLEND);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_LEQUAL);  

    glClearColor(0,0,0,0);
    glClearStencil(0);
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT);

    glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);

    GLF_POST_PENDING_GL_ERRORS();
    
    //
    // Execute the picking pass
    //
    {
        GLuint vao;
        glGenVertexArrays(1, &vao);
        glBindVertexArray(vao);
        // Setup stencil state and prevent writes to color buffer.
        glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_ALWAYS, 1, 1);
        glStencilOp(GL_KEEP,     // stencil failed
                    GL_KEEP,     // stencil passed, depth failed
                    GL_REPLACE); // stencil passed, depth passed

        //
        // Condition the stencil buffer.
        //
        params.depthMaskCallback();
        // we expect any GL state changes are restored.

        // Disable stencil updates and setup the stencil test.
        glStencilFunc(GL_LESS, 0, 1);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
        // Clear depth incase the depthMaskCallback pollutes the depth buffer.
        glClear(GL_DEPTH_BUFFER_BIT);
        // Restore color outputs & setup state for rendering
        glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
        glDisable(GL_CULL_FACE);
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
        glFrontFace(GL_CCW);

        //
        // Enable conservative rasterization, if available.
        //
        // XXX: This wont work until it's in the Glew build.
        bool convRstr = glewIsSupported("GL_NV_conservative_raster");
        if (convRstr) {
            // XXX: this should come from Glew
            #define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346
            glEnable(GL_CONSERVATIVE_RASTERIZATION_NV);
        }

        // 
        // Execute
        //
        // XXX: make intersector a Task
        HdTaskSharedPtrVector tasks;
        tasks.push_back(boost::make_shared<HdxIntersector_DrawTask>(_renderPass,
                                                               _renderPassState,
                                                            params.renderTags));
        engine->Execute(*_index, tasks);

        glDisable(GL_STENCIL_TEST);

        if (convRstr) {
            // XXX: this should come from Glew
            #define GL_CONSERVATIVE_RASTERIZATION_NV 0x9346
            glDisable(GL_CONSERVATIVE_RASTERIZATION_NV);
        }

        // Restore
        glBindVertexArray(0);
        glDeleteVertexArrays(1, &vao);
    }

    GLF_POST_PENDING_GL_ERRORS();

    //
    // Capture the result buffers to be resolved later.
    //
    size_t len = size[0] * size[1];
    std::unique_ptr<unsigned char[]> primId(new unsigned char[len*4]);
    std::unique_ptr<unsigned char[]> instanceId(new unsigned char[len*4]);
    std::unique_ptr<unsigned char[]> elementId(new unsigned char[len*4]);
    std::unique_ptr<float[]> depths(new float[len]);

    glBindTexture(GL_TEXTURE_2D,
        drawTarget->GetAttachments().at("primId")->GetGlTextureName());
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &primId[0]);

    glBindTexture(GL_TEXTURE_2D,
        drawTarget->GetAttachments().at("instanceId")->GetGlTextureName());
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &instanceId[0]);

    glBindTexture(GL_TEXTURE_2D,
        drawTarget->GetAttachments().at("elementId")->GetGlTextureName());
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, &elementId[0]);

    glBindTexture(GL_TEXTURE_2D,
        drawTarget->GetAttachments().at("depth")->GetGlTextureName());
    glGetTexImage(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
                    &depths[0]);

    glBindTexture(GL_TEXTURE_2D, 0);

    GLF_POST_PENDING_GL_ERRORS();

    if (result) {
        *result = HdxIntersector::Result(
            std::move(primId), std::move(instanceId), std::move(elementId),
            std::move(depths), _index, params, viewport);
    }

    drawTarget->Unbind();
    GLF_POST_PENDING_GL_ERRORS();

    return true;
}
/// Finds the existing SkelRoot which is shared by all \p paths.
/// If no SkelRoot is found, and \p config is "auto", then attempts to
/// find a common ancestor of \p paths which can be converted to SkelRoot.
/// \p outMadeSkelRoot must be non-null; it will be set to indicate whether
/// any auto-typename change actually occurred (true) or whether there was
/// already a SkelRoot, so no renaming was necessary (false).
/// If an existing, common SkelRoot cannot be found for all paths, and if
/// it's not possible to create one, returns an empty SdfPath.
static SdfPath
_VerifyOrMakeSkelRoot(const UsdStagePtr& stage,
                      const SdfPath& path,
                      const TfToken& config)
{
    if (config != UsdMayaJobExportArgsTokens->auto_ &&
        config != UsdMayaJobExportArgsTokens->explicit_) {
        return SdfPath();
    }

    // Only try to auto-rename to SkelRoot if we're not already a
    // descendant of one. Otherwise, verify that the user tagged it in a sane
    // way.

    if (UsdSkelRoot root = UsdSkelRoot::Find(stage->GetPrimAtPath(path))) {

        // Verify that the SkelRoot isn't nested in another SkelRoot.
        // This is necessary because UsdSkel doesn't handle nested skel roots
        // very well currently; this restriction may be loosened in the future.
        if (UsdSkelRoot root2 = UsdSkelRoot::Find(root.GetPrim().GetParent())) {
            TF_RUNTIME_ERROR("The SkelRoot <%s> is nested inside another "
                    "SkelRoot <%s>. This might cause unexpected behavior.",
                    root.GetPath().GetText(), root2.GetPath().GetText());
            return SdfPath();
        }
        else {
            return root.GetPath();
        }
    } else if(config == UsdMayaJobExportArgsTokens->auto_) {
        // If auto-generating the SkelRoot, find the rootmost
        // UsdGeomXform and turn it into a SkelRoot.
        // XXX: It might be good to also consider model hierarchy here, and not
        // go past our ancestor component when trying to generate the SkelRoot.
        // (Example: in a scene with /World, /World/Char_1, /World/Char_2, we
        // might want SkelRoots to stop at Char_1 and Char_2.) Unfortunately,
        // the current structure precludes us from accessing model hierarchy
        // here.
        if (UsdPrim root = _FindRootmostXformOrSkelRoot(stage, path)) {
            UsdSkelRoot::Define(stage, root.GetPath());
            return root.GetPath();
        }
        else {
            if (path.IsRootPrimPath()) {
                // This is the most common problem when we can't obtain a
                // SkelRoot.
                // Show a nice error with useful information about root prims.
                TF_RUNTIME_ERROR("The prim <%s> is a root prim, so it has no "
                        "ancestors that can be converted to a SkelRoot. (USD "
                        "requires that skinned meshes and skeletons be "
                        "encapsulated under a SkelRoot.) Try grouping this "
                        "prim under a parent group.",
                        path.GetText());
            }
            else {
                // Show generic error as a last resort if we don't know exactly
                // what went wrong.
                TF_RUNTIME_ERROR("Could not find an ancestor of the prim <%s> "
                        "that can be converted to a SkelRoot. (USD requires "
                        "that skinned meshes and skeletons be encapsulated "
                        "under a SkelRoot.)",
                        path.GetText());
            }
            return SdfPath();
        }
    }
    return SdfPath();
}
/* static */
std::vector<UsdMayaUserTaggedAttribute>
UsdMayaUserTaggedAttribute::GetUserTaggedAttributesForNode(
        const MObject& mayaNode)
{
    std::vector<UsdMayaUserTaggedAttribute> result;

    MStatus status;
    const MFnDependencyNode depNodeFn(mayaNode, &status);
    if (status != MS::kSuccess) {
        return result;
    }

    const MPlug exportedAttrsJsonPlug =
        depNodeFn.findPlug(
            _tokens->USD_UserExportedAttributesJson.GetText(),
            true,
            &status);
    if (status != MS::kSuccess || exportedAttrsJsonPlug.isNull()) {
        // No attributes specified for export on this node.
        return result;
    }

    const std::string exportedAttrsJsonString(
        exportedAttrsJsonPlug.asString().asChar());
    if (exportedAttrsJsonString.empty()) {
        return result;
    }

    JsParseError jsError;
    const JsValue jsValue = JsParseString(exportedAttrsJsonString, &jsError);
    if (!jsValue) {
        TF_RUNTIME_ERROR(
            "Failed to parse USD exported attributes JSON on node '%s' "
            "at line %d, column %d: %s",
            UsdMayaUtil::GetMayaNodeName(mayaNode).c_str(),
            jsError.line, jsError.column, jsError.reason.c_str());
        return result;
    }

    // If an attribute is multiply-defined, we'll use the first tag encountered
    // and issue warnings for the subsequent definitions. JsObject is really
    // just a std::map, so we'll be considering attributes in sorted order.
    std::set<std::string> processedAttributeNames;
    const JsObject& exportedAttrs = jsValue.GetJsObject();
    for (const auto& exportedAttr : exportedAttrs) {
        const std::string mayaAttrName = exportedAttr.first;

        const MPlug attrPlug =
            depNodeFn.findPlug(mayaAttrName.c_str(), true, &status);
        if (status != MS::kSuccess || attrPlug.isNull()) {
            TF_RUNTIME_ERROR(
                "Could not find attribute '%s' for USD export on node '%s'",
                mayaAttrName.c_str(),
                UsdMayaUtil::GetMayaNodeName(mayaNode).c_str());
            continue;
        }

        const JsObject& attrMetadata = exportedAttr.second.GetJsObject();

        // Check if this is a particular type of attribute (e.g. primvar or
        // usdRi attribute). If we don't recognize the type specified, we'll
        // fall back to a regular USD attribute.
        const TfToken usdAttrType(
            _GetExportAttributeMetadata(attrMetadata, _tokens->usdAttrType));

        // Check whether an interpolation type was specified. This is only
        // relevant for primvars.
        const TfToken interpolation(
            _GetExportAttributeMetadata(attrMetadata,
                                        UsdGeomTokens->interpolation));

        // Check whether it was specified that the double precision Maya
        // attribute type should be mapped to a single precision USD type.
        // If it wasn't specified, use the fallback value.
        const bool translateMayaDoubleToUsdSinglePrecision(
            _GetExportAttributeMetadata(
                attrMetadata,
                _tokens->translateMayaDoubleToUsdSinglePrecision,
                GetFallbackTranslateMayaDoubleToUsdSinglePrecision()));

        // Check whether the USD attribute name should be different than the
        // Maya attribute name.
        std::string usdAttrName =
            _GetExportAttributeMetadata(attrMetadata, _tokens->usdAttrName);
        if (usdAttrName.empty()) {
            const auto& tokens = UsdMayaUserTaggedAttributeTokens;
            if (usdAttrType == tokens->USDAttrTypePrimvar ||
                    usdAttrType == tokens->USDAttrTypeUsdRi) {
                // Primvars and UsdRi attributes will be given a type-specific
                // namespace, so just use the Maya attribute name.
                usdAttrName = mayaAttrName;
            } else {
                // For regular USD attributes, when no name was specified we
                // prepend the userProperties namespace to the Maya attribute
                // name to get the USD attribute name.
                usdAttrName = _tokens->UserPropertiesNamespace.GetString() +
                              mayaAttrName;
            }
        }

        const auto& insertIter = processedAttributeNames.emplace(usdAttrName);
        if (!insertIter.second) {
            TF_RUNTIME_ERROR(
                "Ignoring duplicate USD export tag for attribute '%s' "
                "on node '%s'",
                usdAttrName.c_str(),
                UsdMayaUtil::GetMayaNodeName(mayaNode).c_str());
            continue;
        }

        result.emplace_back(attrPlug,
                            usdAttrName,
                            usdAttrType,
                            interpolation,
                            translateMayaDoubleToUsdSinglePrecision);
    }

    return result;
}