From 328d59181960dcb00240685c819c5f93cc5b6cac Mon Sep 17 00:00:00 2001 From: Bradley Henke Date: Wed, 29 Jun 2022 14:56:15 -0700 Subject: [PATCH] IECoreUSD : Support IECore::Object references when determining primvar type. `IECore::Object` avoids some data duplication by writing an internal object's data a single time while saving, and then writing a reference path to the data container if other internal objects share the same pointer. One case in which this situation has presented itself is creating a "Pref" primvar to hold the rest positions of a primitive. In the following example, the created "Pref" primitive variable receives a shallow copy of the "P" primitive variable. So, the internal "data" and "indices" members point to the same shared data. Therefore, a reference is created when the primitive object saves itself. ``` box = IECoreScene.MeshPrimitive.createBox( imath.Box3f( imath.V3f(-1), imath.V3f(1) ) ) box['Pref'] = box['P'] ``` --- .../src/IECoreUSD/SceneCacheData.cpp | 49 +++++++++++++++---- .../IECoreUSD/SceneCacheFileFormatTest.py | 14 ++++++ 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/contrib/IECoreUSD/src/IECoreUSD/SceneCacheData.cpp b/contrib/IECoreUSD/src/IECoreUSD/SceneCacheData.cpp index f4a2f32972..e80d97047e 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/SceneCacheData.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/SceneCacheData.cpp @@ -661,7 +661,7 @@ void SceneCacheData::loadPrimVars( const SceneInterface::Path& currentPath, TfTo // variables SceneInterface::Path variablesPath; variablesPath.push_back( g_ioRoot ); - for ( auto& p : currentPath ) + for ( const auto& p : currentPath ) { // avoid injecting the internal root because // the path would be invalid in the IndexedIO hierarchy @@ -675,20 +675,21 @@ void SceneCacheData::loadPrimVars( const SceneInterface::Path& currentPath, TfTo variablesPath.insert( variablesPath.end(), g_staticIoVariablesPath.begin(), g_staticIoVariablesPath.end() ); - if ( auto variables = m_sceneio->directory( variablesPath, IndexedIO::MissingBehaviour::NullIfMissing ) ) + if ( const auto variables = m_sceneio->directory( variablesPath, IndexedIO::MissingBehaviour::NullIfMissing ) ) { IndexedIO::EntryIDList variableLists; variables->entryIds( variableLists ); - for( auto& var: variableLists ) + + for( const auto& var: variableLists ) { - auto it = find( g_defaultPrimVars.cbegin(), g_defaultPrimVars.cend(), var.value() ); + const auto it = find( g_defaultPrimVars.cbegin(), g_defaultPrimVars.cend(), var.value() ); if( it != g_defaultPrimVars.cend() ) { continue; } // interpolation - auto variableIO = variables->subdirectory( var, IndexedIO::MissingBehaviour::NullIfMissing ); + const auto variableIO = variables->subdirectory( var, IndexedIO::MissingBehaviour::NullIfMissing ); int interpolationValue = 0; TfToken usdInterpolation; if ( !variableIO || !variableIO->hasEntry( g_ioInterpolation ) ) @@ -701,17 +702,45 @@ void SceneCacheData::loadPrimVars( const SceneInterface::Path& currentPath, TfTo usdInterpolation = PrimitiveAlgo::toUSD( static_cast( interpolationValue ) ); // data type - auto dataType = variableIO->subdirectory( g_ioData, IndexedIO::MissingBehaviour::NullIfMissing ); - std::string dataTypeValue; - if( !dataType || !dataType->hasEntry( g_ioType ) ) + if( !variableIO->hasEntry( g_ioData ) ) + { + IECore::msg( IECore::Msg::Warning, "SceneCacheData::loadPrimVars", boost::format( "Unable to find data for Primitive Variable \"%s\" at location \"%s\"." ) % var % primPath ); + continue; + } + + ConstIndexedIOPtr dataIO; + const IndexedIO::Entry dataEntry = variableIO->entry( g_ioData ); + if( dataEntry.entryType() == IndexedIO::File) + { + if( dataEntry.dataType() != IndexedIO::InternedStringArray ) + { + IECore::msg( IECore::Msg::Warning, "SceneCacheData::loadPrimVars", boost::format( "Unable to find reference to data for Primitive Variable \"%s\" at location \"%s\"." ) % var % primPath ); + continue; + } + // IECore::Object has saved a reference to the data, so we need to follow the link to the referenced + // directory in order to find the dataType + IndexedIO::EntryIDList referencedPath( dataEntry.arrayLength() ); + InternedString *p = referencedPath.data(); + variableIO->read( g_ioData, p, dataEntry.arrayLength() ); + dataIO = variableIO->directory( referencedPath, IndexedIO::MissingBehaviour::NullIfMissing ); + } + else + { + // Get the data subdirectory + dataIO = variableIO->subdirectory( g_ioData, IndexedIO::MissingBehaviour::NullIfMissing ); + } + + if( !dataIO || !dataIO->hasEntry( g_ioType ) ) { IECore::msg( IECore::Msg::Warning, "SceneCacheData::loadPrimVars", boost::format( "Unable to find data type for Primitive Variable \"%s\" at location \"%s\"." ) % var % primPath ); continue; } - dataType->read( g_ioType, dataTypeValue ); + + std::string dataTypeValue; + dataIO->read( g_ioType, dataTypeValue ); // interpretation - auto interpretationData = dataType->subdirectory( g_ioData, IndexedIO::MissingBehaviour::NullIfMissing ); + const auto interpretationData = dataIO->subdirectory( g_ioData, IndexedIO::MissingBehaviour::NullIfMissing ); IntDataPtr interpretationValue = nullptr; if ( interpretationData && interpretationData->hasEntry( g_interpretation ) ) { diff --git a/contrib/IECoreUSD/test/IECoreUSD/SceneCacheFileFormatTest.py b/contrib/IECoreUSD/test/IECoreUSD/SceneCacheFileFormatTest.py index 56e379dc7b..fc68f1059a 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/SceneCacheFileFormatTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/SceneCacheFileFormatTest.py @@ -527,6 +527,9 @@ def testCustomPrimvars( self ): mesh["customPoint"] = IECoreScene.PrimitiveVariable( IECoreScene.PrimitiveVariable.Interpolation.Vertex, v ) mesh["customIndexedInt"] = IECoreScene.PrimitiveVariable( IECoreScene.PrimitiveVariable.Interpolation.Uniform, IECore.IntVectorData( [12] ), IECore.IntVectorData( [ 0 ] * vertexSize ) ) + # Test a shallow copy of a primvar which causes IECore.Object to write a reference into the IndexedIO file + mesh["Pref"] = mesh["P"] + box.writeObject( mesh, 1.0 ) del m, box @@ -557,6 +560,14 @@ def testCustomPrimvars( self ): customIndexedInt = prim.GetAttribute( "primvars:customIndexedInt" ) self.assertEqual( customIndexedInt.GetMetadata( "interpolation" ), pxr.UsdGeom.Tokens.uniform ) + # Pref + Pref = prim.GetAttribute( "primvars:Pref" ) + self.assertIsNotNone( Pref ) + self.assertFalse( Pref.GetMetadata( "custom" ) ) + self.assertEqual( Pref.GetMetadata( "typeName" ), pxr.Sdf.ValueTypeNames.Point3fArray ) + self.assertEqual( Pref.Get( 24.0 ), pxr.UsdGeom.Mesh( prim ).GetPointsAttr().Get( 24.0 ) ) + self.assertEqual( Pref.GetMetadata( "interpolation" ), pxr.UsdGeom.Tokens.vertex ) + # round trip exportPath = os.path.join( self.temporaryDirectory(), "testUSDExportCustomPrimVar.scc" ) stage.Export( exportPath ) @@ -581,6 +592,9 @@ def testCustomPrimvars( self ): # indices self.assertEqual( mesh["customIndexedInt"].indices, IECore.IntVectorData( [0] * vertexSize ) ) + # copy + self.assertEqual( mesh["Pref"], mesh["P"] ) + def testCornersAndCreases( self ): fileName = os.path.join( self.temporaryDirectory(), "testUSDCornerAndCreases.scc" ) m = IECoreScene.SceneCache( fileName, IECore.IndexedIO.OpenMode.Write )