void HdChangeTracker::TaskRemoved(SdfPath const& id) { TF_DEBUG(HD_TASK_REMOVED).Msg("Task Removed: %s\n", id.GetText()); _taskState.erase(id); }
void HdChangeTracker::InstancerRemoved(SdfPath const& id) { TF_DEBUG(HD_INSTANCER_REMOVED).Msg("Instancer Removed: %s\n", id.GetText()); _instancerState.erase(id); }
void HdChangeTracker::TaskInserted(SdfPath const& id) { TF_DEBUG(HD_TASK_ADDED).Msg("Task Added: %s\n", id.GetText()); _taskState[id] = AllDirty; }
SdfPathVector SdfPath::GetConciseRelativePaths(const SdfPathVector& paths) { SdfPathVector primPaths; SdfPathVector anchors; SdfPathVector labels; // initialize the vectors TF_FOR_ALL(iter, paths) { if(!iter->IsAbsolutePath()) { TF_WARN("argument to GetConciseRelativePaths contains a relative path."); return paths; } // first, get the prim paths SdfPath primPath = iter->GetPrimPath(); SdfPath anchor = primPath.GetParentPath(); primPaths.push_back(primPath); anchors.push_back(anchor); // we have to special case root anchors, since MakeRelativePath can't handle them if(anchor == SdfPath::AbsoluteRootPath()) labels.push_back(primPath); else labels.push_back(primPath.MakeRelativePath(anchor)); } // each ambiguous path must be raised to its parent bool ambiguous; do { ambiguous = false; // the next iteration of labels SdfPathVector newAnchors; SdfPathVector newLabels; // find ambiguous labels for(size_t i=0;i<labels.size();++i) { int ok = true; // search for some other path that makes this one ambiguous for(size_t j=0;j<labels.size();++j) { if(i != j && labels[i] == labels[j] && primPaths[i] != primPaths[j]) { ok = false; break; } } if(!ok) { // walk the anchor up one node SdfPath newAnchor = anchors[i].GetParentPath(); newAnchors.push_back(newAnchor); newLabels.push_back( newAnchor == SdfPath::AbsoluteRootPath() ? primPaths[i] : primPaths[i].MakeRelativePath( newAnchor ) ); ambiguous = true; } else { newAnchors.push_back(anchors[i]); newLabels.push_back(labels[i]); } } anchors = newAnchors; labels = newLabels; } while(ambiguous); // generate the final set from the anchors SdfPathVector result; for(size_t i=0; i<anchors.size();++i) { if(anchors[i] == SdfPath::AbsoluteRootPath()) { result.push_back( paths[i] ); } else { result.push_back( paths[i].MakeRelativePath( anchors[i] )); } } return result; }
void HdChangeTracker::InstancerInserted(SdfPath const& id) { TF_DEBUG(HD_INSTANCER_ADDED).Msg("Instancer Added: %s\n", id.GetText()); _instancerState[id] = AllDirty; }
std::string Item::getPath() const { SdfPath path = m_prim.GetPath(); return path.GetString(); }
SdfAttributeSpecHandle SdfAttributeSpec::_New( const SdfRelationshipSpecHandle& owner, const SdfPath& path, const std::string& name, const SdfValueTypeName& typeName, SdfVariability variability, bool custom) { if (!owner) { TF_CODING_ERROR("NULL owner"); return TfNullPtr; } if (!typeName) { TF_CODING_ERROR("Cannot create attribute spec <%s> with invalid type", owner->GetPath().AppendTarget(path). AppendProperty(TfToken(name)).GetText()); return TfNullPtr; } SdfChangeBlock block; // Determine the path of the relationship target SdfPath absPath = path.MakeAbsolutePath(owner->GetPath().GetPrimPath()); SdfPath targetPath = owner->GetPath().AppendTarget(absPath); // Check to make sure that the name is valid if (!Sdf_ChildrenUtils<Sdf_AttributeChildPolicy>::IsValidName(name)) { TF_CODING_ERROR( "Cannot create attribute on %s with invalid name: %s", targetPath.GetText(), name.c_str()); return TfNullPtr; } // Create the relationship target if it doesn't already exist. Note // that this does not automatically get added to the relationship's // target path list. SdfSpecHandle targetSpec = owner->_FindOrCreateTargetSpec(path); // AttributeSpecs are considered initially to have only required fields // only if they are not custom. bool hasOnlyRequiredFields = (!custom); // Create the relational attribute spec SdfPath attrPath = targetPath.AppendRelationalAttribute(TfToken(name)); if (!Sdf_ChildrenUtils<Sdf_AttributeChildPolicy>::CreateSpec( owner->GetLayer(), attrPath, SdfSpecTypeAttribute, hasOnlyRequiredFields)) { return TfNullPtr; } SdfAttributeSpecHandle spec = owner->GetLayer()->GetAttributeAtPath(attrPath); // Avoid expensive dormancy checks in the case of binary-backed data. SdfAttributeSpec *specPtr = get_pointer(spec); if (TF_VERIFY(specPtr)) { specPtr->SetField(SdfFieldKeys->Custom, custom); specPtr->SetField(SdfFieldKeys->TypeName, typeName.GetAsToken()); specPtr->SetField(SdfFieldKeys->Variability, variability); } return spec; }
HdTextureResource::ID UsdImagingGL_GetTextureResourceID(UsdPrim const& usdPrim, SdfPath const& usdPath, UsdTimeCode time, size_t salt) { if (!TF_VERIFY(usdPrim)) { return HdTextureResource::ID(-1); } if (!TF_VERIFY(usdPath != SdfPath())) { return HdTextureResource::ID(-1); } // If the texture name attribute doesn't exist, it might be badly specified // in scene data. UsdAttribute attr = _GetTextureResourceAttr(usdPrim, usdPath); SdfAssetPath asset; if (!attr || !attr.Get(&asset, time)) { TF_WARN("Unable to find texture attribute <%s> in scene data", usdPath.GetText()); return HdTextureResource::ID(-1); } HdTextureType textureType = HdTextureType::Uv; TfToken filePath = TfToken(asset.GetResolvedPath()); if (!filePath.IsEmpty()) { // If the resolved path contains a correct path, then we are // dealing with a ptex or uv textures. if (GlfIsSupportedPtexTexture(filePath)) { textureType = HdTextureType::Ptex; } else { textureType = HdTextureType::Uv; } } else { // If the path couldn't be resolved, then it might be a Udim as they // contain special characters in the path to identify them <Udim>. // Another option is that the path is just wrong and it can not be // resolved. filePath = TfToken(asset.GetAssetPath()); if (GlfIsSupportedUdimTexture(filePath)) { const GlfContextCaps& caps = GlfContextCaps::GetInstance(); if (!UsdImaging_UdimTilesExist(filePath, caps.maxArrayTextureLayers, _FindLayerHandle(attr, time))) { TF_WARN("Unable to find Texture '%s' with path '%s'. Fallback " "textures are not supported for udim", filePath.GetText(), usdPath.GetText()); return HdTextureResource::ID(-1); } if (!caps.arrayTexturesEnabled) { TF_WARN("OpenGL context does not support array textures, " "skipping UDIM Texture %s with path %s.", filePath.GetText(), usdPath.GetText()); return HdTextureResource::ID(-1); } textureType = HdTextureType::Udim; } else if (GlfIsSupportedPtexTexture(filePath)) { TF_WARN("Unable to find Texture '%s' with path '%s'. Fallback " "textures are not supported for ptex", filePath.GetText(), usdPath.GetText()); return HdTextureResource::ID(-1); } else { TF_WARN("Unable to find Texture '%s' with path '%s'. A black " "texture will be substituted in its place.", filePath.GetText(), usdPath.GetText()); return HdTextureResource::ID(-1); } } GlfImage::ImageOriginLocation origin = UsdImagingGL_ComputeTextureOrigin(usdPrim); // Hash on the texture filename. size_t hash = asset.GetHash(); // Hash in wrapping and filtering metadata. HdWrap wrapS = _GetWrapS(usdPrim, textureType); HdWrap wrapT = _GetWrapT(usdPrim, textureType); HdMinFilter minFilter = _GetMinFilter(usdPrim); HdMagFilter magFilter = _GetMagFilter(usdPrim); float memoryLimit = _GetMemoryLimit(usdPrim); boost::hash_combine(hash, origin); boost::hash_combine(hash, wrapS); boost::hash_combine(hash, wrapT); boost::hash_combine(hash, minFilter); boost::hash_combine(hash, magFilter); boost::hash_combine(hash, memoryLimit); // Salt the result to prevent collisions in non-shared imaging. // Note that the salt is ignored for fallback texture hashes above. boost::hash_combine(hash, salt); return HdTextureResource::ID(hash); }
HdTextureResourceSharedPtr UsdImagingGL_GetTextureResource(UsdPrim const& usdPrim, SdfPath const& usdPath, UsdTimeCode time) { if (!TF_VERIFY(usdPrim)) return HdTextureResourceSharedPtr(); if (!TF_VERIFY(usdPath != SdfPath())) return HdTextureResourceSharedPtr(); UsdAttribute attr = _GetTextureResourceAttr(usdPrim, usdPath); SdfAssetPath asset; if (!TF_VERIFY(attr) || !TF_VERIFY(attr.Get(&asset, time))) { return HdTextureResourceSharedPtr(); } HdTextureType textureType = HdTextureType::Uv; TfToken filePath = TfToken(asset.GetResolvedPath()); // If the path can't be resolved, it's either an UDIM texture // or the texture doesn't exists and we can to exit early. if (filePath.IsEmpty()) { filePath = TfToken(asset.GetAssetPath()); if (GlfIsSupportedUdimTexture(filePath)) { textureType = HdTextureType::Udim; } else { TF_DEBUG(USDIMAGING_TEXTURES).Msg( "File does not exist, returning nullptr"); TF_WARN("Unable to find Texture '%s' with path '%s'.", filePath.GetText(), usdPath.GetText()); return {}; } } else { if (GlfIsSupportedPtexTexture(filePath)) { textureType = HdTextureType::Ptex; } } GlfImage::ImageOriginLocation origin = UsdImagingGL_ComputeTextureOrigin(usdPrim); HdWrap wrapS = _GetWrapS(usdPrim, textureType); HdWrap wrapT = _GetWrapT(usdPrim, textureType); HdMinFilter minFilter = _GetMinFilter(usdPrim); HdMagFilter magFilter = _GetMagFilter(usdPrim); float memoryLimit = _GetMemoryLimit(usdPrim); TF_DEBUG(USDIMAGING_TEXTURES).Msg( "Loading texture: id(%s), type(%s)\n", usdPath.GetText(), textureType == HdTextureType::Uv ? "Uv" : textureType == HdTextureType::Ptex ? "Ptex" : "Udim"); HdTextureResourceSharedPtr texResource; TfStopwatch timer; timer.Start(); // Udim's can't be loaded through like other textures, because // we can't select the right factory based on the file type. // We also need to pass the layer context to the factory, // so each file gets resolved properly. GlfTextureHandleRefPtr texture; if (textureType == HdTextureType::Udim) { UdimTextureFactory factory(_FindLayerHandle(attr, time)); texture = GlfTextureRegistry::GetInstance().GetTextureHandle( filePath, origin, &factory); } else { texture = GlfTextureRegistry::GetInstance().GetTextureHandle( filePath, origin); } texResource = HdTextureResourceSharedPtr( new HdStSimpleTextureResource(texture, textureType, wrapS, wrapT, minFilter, magFilter, memoryLimit)); timer.Stop(); TF_DEBUG(USDIMAGING_TEXTURES).Msg(" Load time: %.3f s\n", timer.GetSeconds()); return texResource; }
static _Key _GetKey(const SdfPath& path) { return path.IsTargetPath() ? _Key(path.GetTargetPath()) : _Key(path.GetNameToken()); }
bool SdfBatchNamespaceEdit::Process( SdfNamespaceEditVector* processedEdits, const HasObjectAtPath& hasObjectAtPath, const CanEdit& canEdit, SdfNamespaceEditDetailVector* details, bool fixBackpointers) const { // Clear the resulting edits -- we'll build up the result as we go. if (processedEdits) { processedEdits->clear(); } // Track edits as we check them. SdfNamespaceEdit_Namespace ns(fixBackpointers); // Try each edit in sequence. for (const auto& edit : GetEdits()) { // Make sure paths are compatible. bool mismatch = false; if (edit.currentPath.IsPrimPath()) { mismatch = !edit.newPath.IsPrimPath(); } else if (edit.currentPath.IsPropertyPath()) { mismatch = !edit.newPath.IsPropertyPath(); } else { // Unsupported path type. if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Unsupported object type")); } return false; } if (mismatch && !edit.newPath.IsEmpty()) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Path type mismatch")); } return false; } // Get the original path for the object now at edit.currentPath. const SdfPath& from = ns.FindOrCreateOriginalPath(edit.currentPath); // Can't edit from removed namespace except if we're removing. // We allow the exception so it works to, say, remove a prim then // its properties rather than removing its properties then the prim. if (from.IsEmpty()) { if (edit.newPath.IsEmpty()) { // This edit has already happened so it's allowed. Do not // record it in processedEdits. continue; } if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Object was removed")); } return false; } // Make sure there's an object at from. if (hasObjectAtPath && !hasObjectAtPath(from)) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Object does not exist")); } return false; } // Extra checks if not removing. SdfPath to; if (!edit.newPath.IsEmpty()) { // Ignore no-op. Note that this doesn't catch the case where // then index isn't Same but has that effect. if (edit.currentPath == edit.newPath && edit.index == SdfNamespaceEdit::Same) { continue; } // Get the original path for the object now at edit.newPath's // parent. SdfPath newParent = edit.newPath.GetParentPath(); const SdfPath& toParent = ns.FindOrCreateOriginalPath(newParent); // Can't move under removed namespace. if (toParent.IsEmpty()) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "New parent was removed")); } return false; } // Make sure there is an object at to's parent. if (hasObjectAtPath && !hasObjectAtPath(toParent)) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "New parent does not exist")); } return false; } // Check for impossible namespace structure. if (edit.currentPath == edit.newPath) { // Ignore reordering. } else if (edit.currentPath.HasPrefix(edit.newPath)) { // Making object an ancestor of itself. if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Object cannot be an ancestor " "of itself")); } return false; } else if (edit.newPath.HasPrefix(edit.currentPath)) { // Making object a descendant of itself. if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Object cannot be a descendant " "of itself")); } return false; } else { // Can't move over an existing object. to = ns.GetOriginalPath(edit.newPath); if (!to.IsEmpty() && hasObjectAtPath && hasObjectAtPath(to)) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Object already exists")); } return false; } } // Get the real to path. to = edit.newPath.ReplacePrefix(newParent, toParent); } if (!fixBackpointers) { SdfPathVector targetPaths; edit.currentPath.GetAllTargetPathsRecursively(&targetPaths); for (const auto& targetPath : targetPaths) { SdfPath originalPath = ns.GetOriginalPath(targetPath); if (!originalPath.IsEmpty() && originalPath != targetPath) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "Current target was edited")); } return false; } } edit.newPath.GetAllTargetPathsRecursively(&targetPaths); for (const auto& targetPath : targetPaths) { SdfPath originalPath = ns.GetOriginalPath(targetPath); if (!originalPath.IsEmpty() && originalPath != targetPath) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, "New target was edited")); } return false; } } } // Check if actual edit is allowed. std::string whyNot; if (canEdit && !canEdit(SdfNamespaceEdit(from, to, edit.index), &whyNot)) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, whyNot)); } return false; } // Apply edit to state. if (!ns.Apply(edit, &whyNot)) { if (details) { details->push_back( SdfNamespaceEditDetail(SdfNamespaceEditDetail::Error, edit, whyNot)); } return false; } // Save this edit. if (processedEdits) { processedEdits->push_back(edit); } } // Analyze processedEdits. if (processedEdits) { // XXX: We'd like to compute a minimal sequence of edits but for // now we just return the input sequence. The primary // complication with a minimal sequence is that edits may // overlap in namespace so they must be ordered to avoid // illegal edits and incorrect results. For example if // we start with /A/B and /A/C and rename C to D then B to // C we must maintain that order, otherwise we'd rename B // to C when there's already an object named C. // // To make matters worse, if the above had a final rename // D to B then the final result is to exchange the names of // B and C. We can't eliminate the C to D rename even // though D does not appear in the final result because // exchanging names is not a valid operation and no ordering // of two operations yields the correct result. // // Another requirement is that children must be added to a // parent in the input order to ensure the right final // ordering. (That's only relevant for prims.) // // A final requirement is that removed objects first be // edited to their final location before removal. This // allows the client to know that final location to fix up // backpointers before making them dangle. Clients may // not need to keep dangling backpointers but we can't // know that here. } return true; }
SdfPath SdfPath::MakeAbsolutePath(const SdfPath & anchor) const { if (anchor == SdfPath()) { TF_WARN("MakeAbsolutePath(): anchor is the empty path."); return SdfPath(); } // Check that anchor is an absolute path if (!anchor.IsAbsolutePath()) { TF_WARN("MakeAbsolutePath() requires an absolute path as an argument."); return SdfPath(); } // Check that anchor is a component path if (!anchor.IsAbsoluteRootOrPrimPath() && !anchor.IsPrimVariantSelectionPath()) { TF_WARN("MakeAbsolutePath() requires a prim path as an argument."); return SdfPath(); } // If we're invalid, just return a copy of ourselves. if (IsEmpty()) return *this; SdfPath result = *this; // If we're not already absolute, do our own path using anchor as the // relative base. if (!IsAbsolutePath()) { // This list winds up in reverse order to what one might at // first expect. vector<Sdf_PathNodeConstRefPtr> relNodes; Sdf_PathNodeConstRefPtr relRoot = Sdf_PathNode::GetRelativeRootNode(); Sdf_PathNodeConstRefPtr curNode = _pathNode; // Walk up looking for oldPrefix node. while (curNode) { if (curNode == relRoot) { break; } relNodes.push_back(curNode); curNode = curNode->GetParentNode(); } if (!curNode) { // Didn't find relative root // should never get here since all relative paths should have a // relative root node // CODE_COVERAGE_OFF TF_CODING_ERROR("Didn't find relative root"); return SdfPath(); // CODE_COVERAGE_ON } result = anchor; // Got the list, now add nodes similar to relNodes to anchor // relNodes needs to be iterated in reverse since the closest ancestor // node was pushed on last. vector<Sdf_PathNodeConstRefPtr>::reverse_iterator it = relNodes.rbegin(); while (it != relNodes.rend()) { result = _AppendNode(result, *it); ++it; } } // Now make target path absolute (recursively) if we need to. // We need to use result's prim path as the anchor for the target path. SdfPath const &targetPath = result.GetTargetPath(); if (!targetPath.IsEmpty()) { SdfPath primPath = result.GetPrimPath(); SdfPath newTargetPath = targetPath.MakeAbsolutePath(primPath); result = result.ReplaceTargetPath(newTargetPath); } return result; }
SdfPath SdfPath::_ReplacePrefix(const SdfPath &oldPrefix, const SdfPath &newPrefix, bool fixTargetPaths) const { if (*this == oldPrefix) { // Base case: we've reached oldPrefix. return newPrefix; } if (GetPathElementCount() == 0) { // Empty paths have nothing to replace. return *this; } // If we've recursed above the oldPrefix, we can bail as long as there // are no target paths we need to fix. if (GetPathElementCount() <= oldPrefix.GetPathElementCount() && (!fixTargetPaths || !_pathNode->ContainsTargetPath())) { // We'll never see oldPrefix beyond here, so return. return *this; } // Recursively translate the parent. SdfPath parent = GetParentPath()._ReplacePrefix(oldPrefix, newPrefix, fixTargetPaths); // Translation of the parent may fail; it will have emitted an error. // Return here so we don't deref an invalid _pathNode below. if (parent.IsEmpty()) return SdfPath(); // Append the tail component. Use _AppendNode() except in these cases: // - For prims and properties, we construct child nodes directly // so as to not expand out ".." components and to avoid the cost // of unnecessarily re-validating identifiers. // - For embedded target paths, translate the target path. switch (_pathNode->GetNodeType()) { case Sdf_PathNode::PrimNode: return SdfPath(Sdf_PathNode::FindOrCreatePrim(parent._pathNode, _pathNode->GetName())); case Sdf_PathNode::PrimPropertyNode: return SdfPath(Sdf_PathNode::FindOrCreatePrimProperty( parent._pathNode, _pathNode->GetName())); case Sdf_PathNode::TargetNode: if (fixTargetPaths) { return parent.AppendTarget( _pathNode->GetTargetPath() ._ReplacePrefix(oldPrefix, newPrefix, fixTargetPaths)); } else { return _AppendNode(parent, _pathNode); } case Sdf_PathNode::MapperNode: if (fixTargetPaths) { return parent.AppendMapper( _pathNode->GetTargetPath() ._ReplacePrefix(oldPrefix, newPrefix, fixTargetPaths)); } else { return _AppendNode(parent, _pathNode); } default: return _AppendNode(parent, _pathNode); } }
// Overload hash_value for SdfPath. size_t hash_value(SdfPath const &path) { return path.GetHash(); }
void HdChangeTracker::BprimInserted(SdfPath const& id, HdDirtyBits initialDirtyState) { TF_DEBUG(HD_BPRIM_ADDED).Msg("Bprim Added: %s\n", id.GetText()); _bprimState[id] = initialDirtyState; }
void GusdRefiner::addPrimitive( const GT_PrimitiveHandle& gtPrimIn ) { if(!gtPrimIn) { std::cout << "Attempting to add invalid prim" << std::endl; return; } GT_PrimitiveHandle gtPrim = gtPrimIn; // copy to a non-const handle int primType = gtPrim->getPrimitiveType(); DBG( cerr << "GusdRefiner::addPrimitive, " << gtPrim->className() << endl ); string primName; // Types can register a function to provide a prim name. // Volumes do this to return a name stored in the f3d file. This is // important for consistant cluster naming. string n; if( GusdPrimWrapper::getPrimName( gtPrim, n )) { primName = n; } bool refinePackedPrims = m_refinePackedPrims; bool primHasNameAttr = false; if( primName.empty() ) { GT_AttributeListHandle primAttrs; if( primType == GT_GEO_PACKED ) { primAttrs = UTverify_cast<const GT_GEOPrimPacked*>(gtPrim.get())->getInstanceAttributes(); } if( !primAttrs ) { primAttrs = gtPrim->getUniformAttributes(); } if( !primAttrs ) { primAttrs = gtPrim->getDetailAttributes(); } GT_DataArrayHandle dah; if( primAttrs ) { dah = primAttrs->get( m_pathAttrName.c_str() ); } if( dah && dah->isValid() ) { const char *s = dah->getS(0); if( s != NULL ) { primName = s; primHasNameAttr = true; } } if( primAttrs ) { GT_DataArrayHandle overXformsAttr = primAttrs->get( GUSD_OVERTRANSFORMS_ATTR ); if( overXformsAttr ) { if( overXformsAttr->getI32(0) != 0 ) { refinePackedPrims = false; } } } } // The following is only necessary for point instancers. Prototypes // can't be point instancers. if (!m_buildPrototypes) { // Check per prim if we are building a point instancer. This may cause // problems for point instancers with discontiguous packed prims. bool localBuildPointInstancer = false; // If we have imported USD geometry get the type to see if it is a // point instancer we need to overlay. if(auto packedUSD = dynamic_cast<const GusdGT_PackedUSD*>( gtPrim.get() )) { if(packedUSD->getFileName()) { // Get the usd src prim path used for point instancers const SdfPath& instancerPrimPath = packedUSD->getSrcPrimPath(); GusdStageCacheReader cache; if(UsdPrim prim = cache.GetPrimWithVariants( packedUSD->getFileName(), instancerPrimPath).first) { // Get the type name of the usd file to overlay m_pointInstancerType = prim.GetTypeName(); // Make sure to set buildPointInstancer to true if we are overlaying a // point instancer if (m_pointInstancerType == _tokens->PointInstancer || m_pointInstancerType == _tokens->PxPointInstancer) { localBuildPointInstancer = true; } } } } // If we find either an instancepath or usdinstancepath attribute, build a // point instancer. GT_Owner owner; if(gtPrim->findAttribute("instancepath", owner, 0) || gtPrim->findAttribute("usdinstancepath", owner, 0) ) { localBuildPointInstancer = true; } if (m_buildPointInstancer || localBuildPointInstancer) { // If we are building point instancer, stash prims that can be // point instanced. Build the point instancer in the finish method. // If given a prim path, pass it to the collector for a custom // usd scope. Otherwise pass an empty SdfPath. SdfPath instancerPrimPath; if( !primName.empty() ) { instancerPrimPath = SdfPath(createPrimPath(primName)); } if( auto packedUSD = dynamic_cast<const GusdGT_PackedUSD*>( gtPrim.get() )) { // Point instancer from packed usd instancerPrimPath = instancerPrimPath.IsEmpty() ? packedUSD->getSrcPrimPath() : instancerPrimPath; m_collector.addInstPrim( instancerPrimPath, gtPrim ); return; } else if( gtPrim->getPrimitiveType() == GT_PRIM_INSTANCE ) { // Point instancer from packed primitives // A GT_PrimInstance can container more than one instance. Create // an entry for each. auto instPrim = UTverify_cast<const GT_PrimInstance*>( gtPrim.get() ); // TODO: If we put all geometry packed prims here, then we break // grouping prims for purpose for( size_t i = 0; i < instPrim->entries(); ++i ) { m_collector.addInstPrim( instancerPrimPath, gtPrim, i ); } return; } if( primType == GT_PRIM_PARTICLE || primType == GT_PRIM_POINT_MESH ) { // Point instancer from points with instancepath attribute // Check for the usdprototypespath attribute in case it is not // a point or primitivie attribute. GT_AttributeListHandle uniformAttrs = gtPrim->getUniformAttributes(); uniformAttrs = findAndAddStringAttribute(uniformAttrs, "usdprototypespath", gtPrim); // Find and add a custom prototype scope attribute. uniformAttrs = findAndAddStringAttribute(uniformAttrs, "usdprototypesscope", gtPrim); gtPrim = new GusdGT_PointInstancer( gtPrim->getPointAttributes(), uniformAttrs ); primType = gtPrim->getPrimitiveType(); } } } // We must refine packed prims that don't have a name if( !primHasNameAttr && !refinePackedPrims ) { refinePackedPrims = true; } if( primName.empty() && gtPrim->getPrimitiveType() == GusdGT_PackedUSD::getStaticPrimitiveType() ) { auto packedUsdPrim = UTverify_cast<const GusdGT_PackedUSD *>(gtPrim.get()); SdfPath path = packedUsdPrim->getPrimPath().StripAllVariantSelections(); if( m_useUSDIntrinsicNames ) { primName = path.GetString(); } else { primName = path.GetName(); } // We want prototypes to be children of the point instancer, so we make // the usd path a relative scope of just the usd prim name if ( m_buildPrototypes && !primName.empty() && primName[0] == '/' ) { size_t idx = primName.find_last_of("/"); primName = primName.substr(idx+1); } } // If the prim path was not explicitly set, try to come up with a reasonable // default. bool addNumericSuffix = false; if( primName.empty() ) { int t = gtPrim->getPrimitiveType(); if( t == GT_PRIM_POINT_MESH || t == GT_PRIM_PARTICLE ) primName = "points"; else if( t == GT_PRIM_POLYGON_MESH || t == GT_PRIM_SUBDIVISION_MESH ) primName = "mesh"; else if( t == GT_PRIM_CURVE_MESH ) primName = "curve"; else if( t == GusdGT_PointInstancer::getStaticPrimitiveType() ) primName = "instances"; else if(const char *n = GusdPrimWrapper::getUsdName( t )) primName = n; else primName = "obj"; if( !primName.empty() ) { addNumericSuffix = true; } } string primPath = createPrimPath(primName); TfToken purpose = UsdGeomTokens->default_; { GT_Owner own = GT_OWNER_PRIMITIVE; GT_DataArrayHandle dah = gtPrim->findAttribute( GUSD_PURPOSE_ATTR, own, 0 ); if( dah && dah->isValid() ) { purpose = TfToken(dah->getS(0)); } } if( primType == GT_PRIM_INSTANCE ) { auto inst = UTverify_cast<const GT_PrimInstance*>(gtPrim.get()); const GT_PrimitiveHandle geometry = inst->geometry(); if ( geometry->getPrimitiveType() == GT_GEO_PACKED ) { // If we find a packed prim that has a name, this become a group (xform) in // USD. If it doesn't have a name, we just accumulate the transform and recurse. auto packedGeo = UTverify_cast<const GT_GEOPrimPacked*>(geometry.get()); for( GT_Size i = 0; i < inst->transforms()->entries(); ++i ) { UT_Matrix4D m; inst->transforms()->get(i)->getMatrix(m); UT_Matrix4D newCtm = m_localToWorldXform; newCtm = m* m_localToWorldXform; SdfPath newPath = m_pathPrefix; bool recurse = true; if( primHasNameAttr || ( m_forceGroupTopPackedPrim && m_isTopLevel )) { // m_forceGroupTopPackedPrim is used when we are writing instance // prototypes. We need to add instance id attributes to the top // level group. Here we make sure that we create that group, even // if the user hasn't named it. newPath = m_collector.add( SdfPath(primPath), addNumericSuffix, gtPrim, newCtm, purpose, m_writeCtrlFlags ); // If we are just writing transforms and encounter a packed prim, we // just want to write it's transform and not refine it further. recurse = refinePackedPrims; } if( recurse ) { GusdRefiner childRefiner( m_collector, newPath, m_pathAttrName, newCtm ); childRefiner.m_refinePackedPrims = refinePackedPrims; childRefiner.m_forceGroupTopPackedPrim = m_forceGroupTopPackedPrim; childRefiner.m_isTopLevel = false; childRefiner.m_writeCtrlFlags = m_writeCtrlFlags; childRefiner.m_writeCtrlFlags.update( geometry ); #if UT_MAJOR_VERSION_INT >= 16 childRefiner.refineDetail( packedGeo->getPackedDetail(), m_refineParms ); #else childRefiner.refineDetail( packedGeo->getPrim()->getPackedDetail(), m_refineParms ); #endif } } return; } } if( (primType != GT_GEO_PACKED || !refinePackedPrims) && GusdPrimWrapper::isGTPrimSupported(gtPrim) ) { UT_Matrix4D m; if( primType == GT_GEO_PACKED ) { // packed fragment UTverify_cast<const GT_GEOPrimPacked*>(gtPrim.get())->getFullTransform()->getMatrix(m); } else { gtPrim->getPrimitiveTransform()->getMatrix(m); } UT_Matrix4D newCtm = m_localToWorldXform; newCtm = m* m_localToWorldXform; m_collector.add( SdfPath(primPath), addNumericSuffix, gtPrim, newCtm, purpose, m_writeCtrlFlags ); } else { gtPrim->refine( *this, &m_refineParms ); } }
/// 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(); }
void Sdf_ConnectionListEditor<ChildPolicy>::_OnEditShared( SdfListOpType op, SdfSpecType specType, const std::vector<SdfPath>& oldItems, const std::vector<SdfPath>& newItems) const { // XXX The following code tries to manage lifetime of the target // specs associated with this list, but it slightly buggy: if // multiple lists mention the same target -- ex. if a target is // added, appended, and prepended -- then this proxy for a single // list has no way to know if the target also exists in those // other lists, and so it cannot mangae lifetime on its own. if (op == SdfListOpTypeOrdered || op == SdfListOpTypeDeleted) { // These ops do not affect target spec lifetime, so there's // nothing to do. return; } const SdfPath propertyPath = GetPath(); SdfLayerHandle layer = GetLayer(); const std::set<value_type> oldItemSet(oldItems.begin(), oldItems.end()); const std::set<value_type> newItemSet(newItems.begin(), newItems.end()); // Need to remove all children in oldItems that are not in newItems. std::vector<SdfPath> childrenToRemove; std::set_difference(oldItemSet.begin(), oldItemSet.end(), newItemSet.begin(), newItemSet.end(), std::back_inserter(childrenToRemove)); TF_FOR_ALL(child, childrenToRemove) { if (!Sdf_ChildrenUtils<ChildPolicy>::RemoveChild( layer, propertyPath, *child)) { // Some data backends procedurally generate the children specs based // on the listops as an optimization, so if we failed to remove a // child here, it could be that. If no spec is present, then we // consider things to be okay and do not issue an error. const SdfPath specPath = ChildPolicy::GetChildPath(propertyPath, *child); if (layer->GetObjectAtPath(specPath)) { TF_CODING_ERROR("Failed to remove spec at <%s>", specPath.GetText()); } } } // Need to add all children in newItems that are not in oldItems. std::vector<SdfPath> childrenToAdd; std::set_difference(newItemSet.begin(), newItemSet.end(), oldItemSet.begin(), oldItemSet.end(), std::back_inserter(childrenToAdd)); TF_FOR_ALL(child, childrenToAdd) { const SdfPath specPath = ChildPolicy::GetChildPath(propertyPath, *child); if (layer->GetObjectAtPath(specPath)) { continue; } if (!Sdf_ChildrenUtils<ChildPolicy>::CreateSpec(layer, specPath, specType)) { TF_CODING_ERROR("Failed to create spec at <%s>", specPath.GetText()); } } }
PxrUsdMayaShadingModeExportContext::AssignmentVector PxrUsdMayaShadingModeExportContext::GetAssignments() const { AssignmentVector ret; MStatus status; MFnDependencyNode seDepNode(_shadingEngine, &status); if (!status) { return ret; } MPlug dsmPlug = seDepNode.findPlug("dagSetMembers", true, &status); if (!status) { return ret; } SdfPathSet seenBoundPrimPaths; for (unsigned int i = 0; i < dsmPlug.numConnectedElements(); i++) { MPlug dsmElemPlug(dsmPlug.connectionByPhysicalIndex(i)); MStatus status = MS::kFailure; MFnDagNode dagNode(PxrUsdMayaUtil::GetConnected(dsmElemPlug).node(), &status); if (!status) { continue; } MDagPath dagPath; if (!dagNode.getPath(dagPath)) continue; SdfPath usdPath = PxrUsdMayaUtil::MDagPathToUsdPath(dagPath, _mergeTransformAndShape); // If _overrideRootPath is not empty, replace the root namespace with it if (!_overrideRootPath.IsEmpty() ) { usdPath = usdPath.ReplacePrefix(usdPath.GetPrefixes()[0], _overrideRootPath); } // If this path has already been processed, skip it. if (!seenBoundPrimPaths.insert(usdPath).second) continue; // If the bound prim's path is not below a bindable root, skip it. if (SdfPathFindLongestPrefix(_bindableRoots.begin(), _bindableRoots.end(), usdPath) == _bindableRoots.end()) { continue; } MObjectArray sgObjs, compObjs; // Assuming that instancing is not involved. status = dagNode.getConnectedSetsAndMembers(0, sgObjs, compObjs, true); if (!status) continue; for (size_t j = 0; j < sgObjs.length(); j++) { // If the shading group isn't the one we're interested in, skip it. if (sgObjs[j] != _shadingEngine) continue; VtIntArray faceIndices; if (!compObjs[j].isNull()) { MItMeshPolygon faceIt(dagPath, compObjs[j]); faceIndices.reserve(faceIt.count()); for ( faceIt.reset() ; !faceIt.isDone() ; faceIt.next() ) { faceIndices.push_back(faceIt.index()); } } ret.push_back(std::make_pair(usdPath, faceIndices)); } } return ret; }
TfToken usdWriteJob::writeVariants(const UsdPrim &usdRootPrim) { // Init parameters for filtering and setting the active variant std::string defaultModelingVariant; // Get the usdVariantRootPrimPath (optionally filter by renderLayer prefix) MayaPrimWriterPtr firstPrimWriterPtr = *mJobCtx.mMayaPrimWriterList.begin(); std::string firstPrimWriterPathStr( firstPrimWriterPtr->getDagPath().fullPathName().asChar() ); std::replace( firstPrimWriterPathStr.begin(), firstPrimWriterPathStr.end(), '|', '/'); std::replace( firstPrimWriterPathStr.begin(), firstPrimWriterPathStr.end(), ':', '_'); // replace namespace ":" with "_" SdfPath usdVariantRootPrimPath(firstPrimWriterPathStr); usdVariantRootPrimPath = usdVariantRootPrimPath.GetPrefixes()[0]; // Create a new usdVariantRootPrim and reference the Base Model UsdRootPrim // This is done for reasons as described above under mArgs.usdModelRootOverridePath UsdPrim usdVariantRootPrim = mJobCtx.mStage->DefinePrim(usdVariantRootPrimPath); TfToken defaultPrim = usdVariantRootPrim.GetName(); usdVariantRootPrim.GetReferences().AppendInternalReference(usdRootPrim.GetPath()); usdVariantRootPrim.SetActive(true); usdRootPrim.SetActive(false); // Loop over all the renderLayers for (unsigned int ir=0; ir < mRenderLayerObjs.length(); ++ir) { SdfPathTable<bool> tableOfActivePaths; MFnRenderLayer renderLayerFn( mRenderLayerObjs[ir] ); MString renderLayerName = renderLayerFn.name(); std::string variantName(renderLayerName.asChar()); // Determine default variant. Currently unsupported //MPlug renderLayerDisplayOrderPlug = renderLayerFn.findPlug("displayOrder", true); //int renderLayerDisplayOrder = renderLayerDisplayOrderPlug.asShort(); // The Maya default RenderLayer is also the default modeling variant if (mRenderLayerObjs[ir] == MFnRenderLayer::defaultRenderLayer()) { defaultModelingVariant=variantName; } // Make the renderlayer being looped the current one MGlobal::executeCommand(MString("editRenderLayerGlobals -currentRenderLayer ")+ renderLayerName, false, false); // == ModelingVariants == // Identify prims to activate // Put prims and parent prims in a SdfPathTable // Then use that membership to determine if a prim should be Active. // It has to be done this way since SetActive(false) disables access to all child prims. MObjectArray renderLayerMemberObjs; renderLayerFn.listMembers(renderLayerMemberObjs); std::vector< SdfPath > activePaths; for (unsigned int im=0; im < renderLayerMemberObjs.length(); ++im) { MFnDagNode dagFn(renderLayerMemberObjs[im]); MDagPath dagPath; dagFn.getPath(dagPath); dagPath.extendToShape(); SdfPath usdPrimPath; if (!TfMapLookup(mDagPathToUsdPathMap, dagPath, &usdPrimPath)) { continue; } usdPrimPath = usdPrimPath.ReplacePrefix(usdPrimPath.GetPrefixes()[0], usdVariantRootPrimPath); // Convert base to variant usdPrimPath tableOfActivePaths[usdPrimPath] = true; activePaths.push_back(usdPrimPath); //UsdPrim usdPrim = mStage->GetPrimAtPath(usdPrimPath); //usdPrim.SetActive(true); } if (!tableOfActivePaths.empty()) { { // == BEG: Scope for Variant EditContext // Create the variantSet and variant UsdVariantSet modelingVariantSet = usdVariantRootPrim.GetVariantSets().AppendVariantSet("modelingVariant"); modelingVariantSet.AppendVariant(variantName); modelingVariantSet.SetVariantSelection(variantName); // Set the Edit Context UsdEditTarget editTarget = modelingVariantSet.GetVariantEditTarget(); UsdEditContext editContext(mJobCtx.mStage, editTarget); // == Activate/Deactivate UsdPrims UsdPrimRange rng = UsdPrimRange::AllPrims(mJobCtx.mStage->GetPseudoRoot()); std::vector<UsdPrim> primsToDeactivate; for (auto it = rng.begin(); it != rng.end(); ++it) { UsdPrim usdPrim = *it; // For all xformable usdPrims... if (usdPrim && usdPrim.IsA<UsdGeomXformable>()) { bool isActive=false; for (size_t j=0;j<activePaths.size();j++) { //primPathD.HasPrefix(primPathA); SdfPath activePath=activePaths[j]; if (usdPrim.GetPath().HasPrefix(activePath) || activePath.HasPrefix(usdPrim.GetPath())) { isActive=true; break; } } if (isActive==false) { primsToDeactivate.push_back(usdPrim); it.PruneChildren(); } } } // Now deactivate the prims (done outside of the UsdPrimRange // so not to modify the iterator while in the loop) for ( UsdPrim const& prim : primsToDeactivate ) { prim.SetActive(false); } } // == END: Scope for Variant EditContext } } // END: RenderLayer iterations // Set the default modeling variant UsdVariantSet modelingVariantSet = usdVariantRootPrim.GetVariantSet("modelingVariant"); if (modelingVariantSet.IsValid()) { modelingVariantSet.SetVariantSelection(defaultModelingVariant); } return defaultPrim; }
void SdfAttributeSpec::ChangeMapperPath( const SdfPath& oldPath, const SdfPath& newPath) { if (!PermissionToEdit()) { TF_CODING_ERROR("Change mapper path: Permission denied."); return; } const SdfPath& attrPath = GetPath(); // Absolutize. SdfPath oldAbsPath = oldPath.MakeAbsolutePath(attrPath.GetPrimPath()); SdfPath newAbsPath = newPath.MakeAbsolutePath(attrPath.GetPrimPath()); // Validate. if (oldAbsPath == newAbsPath) { // Nothing to do. return; } if (!newAbsPath.IsPropertyPath()) { TF_CODING_ERROR("cannot change connection path for attribute %s's " "mapper at connection path <%s> to <%s> because it's " "not a property path", attrPath.GetString().c_str(), oldAbsPath.GetString().c_str(), newAbsPath.GetString().c_str()); return; } SdfPathVector mapperPaths = GetFieldAs<SdfPathVector>(SdfChildrenKeys->MapperChildren); // Check that a mapper actually exists at the old path. SdfPathVector::iterator mapperIt = std::find(mapperPaths.begin(), mapperPaths.end(), oldAbsPath); if (mapperIt == mapperPaths.end()) { TF_CODING_ERROR("Change mapper path: No mapper exists for " "connection path <%s>.", oldAbsPath.GetText()); return; } // Check that no mapper already exists at the new path. const bool mapperExistsAtNewPath = (std::find(mapperPaths.begin(), mapperPaths.end(), newAbsPath) != mapperPaths.end()); if (mapperExistsAtNewPath) { TF_CODING_ERROR("Change mapper path: Mapper already exists for " "connection path <%s>.", newAbsPath.GetText()); return; } // Things look OK -- let's go ahead and move the mapper over to the // new path. SdfChangeBlock block; const SdfPath oldMapperSpecPath = attrPath.AppendMapper(oldAbsPath); const SdfPath newMapperSpecPath = attrPath.AppendMapper(newAbsPath); _MoveSpec(oldMapperSpecPath, newMapperSpecPath); *mapperIt = newAbsPath; SetField(SdfChildrenKeys->MapperChildren, VtValue(mapperPaths)); }
SdfPath SdfPath::MakeRelativePath(const SdfPath & anchor) const { TRACE_FUNCTION(); // Check that anchor is a valid path if ( anchor == SdfPath() ) { TF_WARN("MakeRelativePath(): anchor is the invalid path."); return SdfPath(); } // Check that anchor is an absolute path if (!anchor.IsAbsolutePath()) { TF_WARN("MakeRelativePath() requires an absolute path as an argument."); return SdfPath(); } // Check that anchor is a component path if (!anchor.IsAbsoluteRootOrPrimPath() && !anchor.IsPrimVariantSelectionPath()) { TF_WARN("MakeRelativePath() requires a component path as an argument (got '%s').", anchor.GetString().c_str()); return SdfPath(); } // If we're invalid, just return a copy of ourselves. if (!_pathNode) { return SdfPath(); } if (!IsAbsolutePath()) { // Canonicalize... make sure the relative path has the // fewest possible dot-dots. SdfPath absPath = MakeAbsolutePath(anchor); return absPath.MakeRelativePath(anchor); } // We are absolute, we want to be relative // This list winds up in reverse order to what one might at first expect. vector<Sdf_PathNodeConstRefPtr> relNodes; // We need to crawl up the this path until we are the same length as // the anchor. // Then we crawl up both till we find the matching nodes. // As we crawl, we build the relNodes vector. size_t thisCount = _pathNode->GetElementCount(); size_t anchorCount = anchor._pathNode->GetElementCount(); // these pointers avoid construction/destruction // of ref pointers Sdf_PathNodeConstRefPtr curThisNode = _pathNode; Sdf_PathNodeConstRefPtr curAnchorNode = anchor._pathNode; // walk to the same depth size_t dotdotCount = 0; while (thisCount > anchorCount) { relNodes.push_back(curThisNode); curThisNode = curThisNode->GetParentNode(); --thisCount; } while (thisCount < anchorCount) { ++dotdotCount; curAnchorNode = curAnchorNode->GetParentNode(); --anchorCount; } // now we're at the same depth TF_AXIOM(thisCount == anchorCount); // walk to a common prefix while (curThisNode != curAnchorNode) { ++dotdotCount; relNodes.push_back(curThisNode); curThisNode = curThisNode->GetParentNode(); curAnchorNode = curAnchorNode->GetParentNode(); } // Now relNodes the nodes of this path after the prefix // common to anchor and this path. SdfPath result = ReflexiveRelativePath(); // Start by adding dotdots while (dotdotCount--) { result = result.GetParentPath(); } // Now add nodes similar to relNodes to the ReflexiveRelativePath() // relNodes needs to be iterated in reverse since the closest ancestor // node was pushed on last. vector<Sdf_PathNodeConstRefPtr>::reverse_iterator it = relNodes.rbegin(); while (it != relNodes.rend()) { result = _AppendNode(result, *it); ++it; } return result; }