diff --git a/Changes b/Changes index fc224a6c6c..14504bf853 100644 --- a/Changes +++ b/Changes @@ -1,6 +1,11 @@ 10.5.x.x (relative to 10.5.12.0) ======== +Features +-------- + +- PointInstancerAlgo : Added support for the env var IECOREUSD_POINTINSTANCER_RELATIVEPROTOTYPES. If this is set to "1", then when USD PointInstancers are loaded as point clouds, if they contain prototype paths beneath themselves in the hierarchy, those prototype paths will be loaded as relative paths, starting with "./". This aligns with how Gaffer will now handle prototype paths, and allows point instancers to be relocated in the hierarchy. + Fixes ----- diff --git a/contrib/IECoreUSD/src/IECoreUSD/PointInstancerAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/PointInstancerAlgo.cpp index db064e4f13..de9f01c88b 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/PointInstancerAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/PointInstancerAlgo.cpp @@ -53,6 +53,19 @@ using namespace IECoreUSD; namespace { +bool checkEnvFlag( const char *envVar, bool def ) +{ + const char *value = getenv( envVar ); + if( value ) + { + return std::string( value ) != "0"; + } + else + { + return def; + } +} + IECore::ObjectPtr readPointInstancer( pxr::UsdGeomPointInstancer &pointInstancer, pxr::UsdTimeCode time, const Canceller *canceller ) { pxr::VtVec3fArray pointsData; @@ -108,16 +121,31 @@ IECore::ObjectPtr readPointInstancer( pxr::UsdGeomPointInstancer &pointInstancer // Prototype paths + const static bool g_relativePrototypes = checkEnvFlag( "IECOREUSD_POINTINSTANCER_RELATIVE_PROTOTYPES", false ); + pxr::SdfPathVector targets; Canceller::check( canceller ); pointInstancer.GetPrototypesRel().GetForwardedTargets( &targets ); + const pxr::SdfPath &primPath = pointInstancer.GetPath(); + IECore::StringVectorDataPtr prototypeRootsData = new IECore::StringVectorData(); auto &prototypeRoots = prototypeRootsData->writable(); prototypeRoots.reserve( targets.size() ); for( const auto &t : targets ) { - prototypeRoots.push_back( t.GetString() ); + if( !g_relativePrototypes || !t.HasPrefix( primPath ) ) + { + prototypeRoots.push_back( t.GetString() ); + } + else + { + // The ./ prefix shouldn't be necessary - we want to just use the absence of a leading + // slash to indicate relative paths. We can remove the prefix here once we deprecate the + // GAFFERSCENE_INSTANCER_EXPLICIT_ABSOLUTE_PATHS env var and have Gaffer always require a leading + // slash for absolute paths. + prototypeRoots.push_back( "./" + t.MakeRelativePath( primPath ).GetString() ); + } } newPoints->variables["prototypeRoots"] = IECoreScene::PrimitiveVariable( IECoreScene::PrimitiveVariable::Constant, prototypeRootsData ); diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index d5eda47c4b..c9a52115cf 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -42,6 +42,8 @@ import shutil import tempfile import imath +import subprocess +import sys import threading import time @@ -4486,6 +4488,40 @@ def testAssetPathSlashes ( self ) : self.assertNotIn( "\\", xform.readAttribute( "render:testAsset", 0 ).value ) self.assertTrue( pathlib.Path( xform.readAttribute( "render:testAsset", 0 ).value ).is_file() ) + def _testPointInstancerRelativePrototypes( self ) : + + root = IECoreScene.SceneInterface.create( + os.path.join( os.path.dirname( __file__ ), "data", "pointInstancerWeirdPrototypes.usda" ), + IECore.IndexedIO.OpenMode.Read + ) + pointInstancer = root.child( "inst" ) + obj = pointInstancer.readObject(0.0) + + if os.environ.get( "IECOREUSD_POINTINSTANCER_RELATIVE_PROTOTYPES", "0" ) != "0" : + self.assertEqual( obj["prototypeRoots"].data, IECore.StringVectorData( [ './Prototypes/sphere', '/cube' ] ) ) + else : + self.assertEqual( obj["prototypeRoots"].data, IECore.StringVectorData( [ '/inst/Prototypes/sphere', '/cube' ] ) ) + + def testPointInstancerRelativePrototypes( self ) : + + for relative in [ "0", "1", None ] : + + with self.subTest( relative = relative ) : + + env = os.environ.copy() + if relative is not None : + env["IECOREUSD_POINTINSTANCER_RELATIVE_PROTOTYPES"] = relative + else : + env.pop( "IECOREUSD_POINTINSTANCER_RELATIVE_PROTOTYPES", None ) + + try : + subprocess.check_output( + [ sys.executable, __file__, "USDSceneTest._testPointInstancerRelativePrototypes" ], + env = env, stderr = subprocess.STDOUT + ) + except subprocess.CalledProcessError as e : + self.fail( e.output ) + @unittest.skipIf( not haveVDB, "No IECoreVDB" ) def testUsdVolVolumeSlashes( self ) : diff --git a/contrib/IECoreUSD/test/IECoreUSD/data/pointInstancerWeirdPrototypes.usda b/contrib/IECoreUSD/test/IECoreUSD/data/pointInstancerWeirdPrototypes.usda new file mode 100644 index 0000000000..4845af62d6 --- /dev/null +++ b/contrib/IECoreUSD/test/IECoreUSD/data/pointInstancerWeirdPrototypes.usda @@ -0,0 +1,26 @@ +#usda 1.0 +( +) + +def PointInstancer "inst" ( + kind = "group" +) +{ + point3f[] positions = [(0, 0, -20), (0, 0, -16), (0, 0, -12), (0, 0, -8), (0, 0, -4), (0, 0, 0), (0, 0, 4), (0, 0, 8), (0, 0, 12), (0, 0, 16)] + int[] protoIndices = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + rel prototypes = [ , ] + + def Scope "Prototypes" ( + kind = "group" + ) + { + def Sphere "sphere" + { + double radius = 1 + } + } +} + +def Cube "cube" +{ +}