USD Importer fails to create Geometry Cache when cache-root detection selects a UsdGeomScope

Importing a USD stage containing an animated UsdGeomMesh can fail to create both the UGeometryCache asset and UGeometryCacheUsdComponent when non-geometry prims exist alongside the animated geometry.

The animated mesh remains loaded and time-varying. The failure occurs because USDCore may select a UsdGeomScope as the Geometry Cache root, but Unreal does not register a Geometry Cache translator for UsdGeomScope.

Expected result

USDCore should select /root/render/geo/animatedGeometry, or another translatable UsdGeomXformable, as the Geometry Cache root.

Unreal should create a UGeometryCache asset and corresponding Geometry Cache component.

Cause

RecursiveCheckForGeometryCache correctly treats Material, Shader, and RenderVar prims as uncollapsible. This prevents /root and /root/render from becoming Geometry Cache roots.

FindValidGeometryCacheRoot then descends into /root/render/geo. USDCore allows a UsdGeomScope containing meshes to propagate the collapsible mesh state:

Engine/Plugins/Runtime/USDCore/Source/USDUtilities/Private/Objects/USDInfoCache.cpp

The relevant logic is around RecursiveCheckForGeometryCache, where UsdGeomScope is treated as an exception and inherits its children’s mesh state.

FindValidGeometryCacheRoot subsequently marks /root/render/geo as EGeometryCachePrimState::ValidRoot. It stops searching before reaching the child UsdGeomXform.

This selected root cannot be translated because Geometry Cache translation is registered only for UsdGeomMesh and UsdGeomXformable:

Engine/Plugins/Importers/USDImporter/Source/USDSchemas/Private/USDSchemasModule.cpp

The import geometry traversal also searches for UsdGeomXformable prims, not UsdGeomScope prims:

Engine/Plugins/Importers/USDImporter/Source/USDStageImporter/Private/USDStageImporter.cpp

This leaves the animated mesh present but without a translatable prim marked as the Geometry Cache root.

Actual result

No UGeometryCache asset is created.

No UUsdGeometryCacheComponent or UGeometryCacheUsdComponent is created in the imported level hierarchy.

The animated mesh and all contributing layers remain valid in the composed USD stage.

Proposed fix

diff --git a/Engine/Plugins/Runtime/USDCore/Source/USDUtilities/Private/Objects/USDInfoCache.cpp b/Engine/Plugins/Runtime/USDCore/Source/USDUtilities/Private/Objects/USDInfoCache.cpp
 
--- a/Engine/Plugins/Runtime/USDCore/Source/USDUtilities/Private/Objects/USDInfoCache.cpp
 
+++ b/Engine/Plugins/Runtime/USDCore/Source/USDUtilities/Private/Objects/USDInfoCache.cpp
 
 #include "pxr/usd/usdSkel/root.h"
 
[Content removed]15 @@
 
 {
 
 FWriteScopeLock PrimLock = Impl.LockForWriting(*Info);
 
 
 
 // A prim is considered a valid root if its subtree has no uncollapsible branch and a valid depth.
 
 // A valid depth is positive, meaning it has an animated mesh, and doesn't exceed the limit.
 
 bool bIsValidDepth = Info->GeometryCacheDepth > -1 && Info->GeometryCacheDepth <= GGeometryCacheMaxDepth;
 
- if (!EnumHasAnyFlags(Info->GeometryCacheState, EGeometryCachePrimState::Uncollapsible) && bIsValidDepth)
 
+
 
+ // Geometry Cache translators are registered for Mesh and Xformable prims, not Scope prims.
 
+ // Keep traversing through a Scope instead of selecting a root that cannot be translated.
 
+ const bool bCanTranslateGeometryCacheRoot = !!pxr::UsdGeomXformable(UsdPrim);
 
+
 
+ if (bCanTranslateGeometryCacheRoot
 
+ && !EnumHasAnyFlags(Info->GeometryCacheState, EGeometryCachePrimState::Uncollapsible) && bIsValidDepth)
 
 {
 
 OutState = EGeometryCachePrimState::ValidRoot;
 
 Info->GeometryCacheState = EGeometryCachePrimState::ValidRoot;
 
 return;

Steps to Reproduce
1. Create a stage with this composed hierarchy:

/root Xform

└── render Scope

└── geo Scope

└── animatedGeometry      Xform

  └── mesh          Mesh with time\-sampled points

2. Confirm that mesh.GetPointsAttr().ValueMightBeTimeVarying() returns true.

3. Import the stage using USD “Import Into Level” with geometry and actors enabled.

Hi Manuel,

Apologies for the delay responding and thank you for the report and proposed fix.

I tried constructing a simple scene like you describe above, but wasn’t able to repro the issue exactly. Using the legacy USD importer, I get a UGeometryCache asset, but attached (via the GeometryCacheUSDComponent) to /<filename>/root when I’d expect /<filename>/render/geo/animatedGeometry/mesh (note that the newer Interchange USD importer appears to create the expected hierarchy).

Any chance you could attach a simple scene, as well as any relevant import settings? I wasn’t sure from your description if you have collapsing enabled for example.

Thanks,

Antony