Skip to content

IECoreUSD : Load prototype paths inside a PointInstancer as relative #1451

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -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
-----

Expand Down
30 changes: 29 additions & 1 deletion contrib/IECoreUSD/src/IECoreUSD/PointInstancerAlgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 );
Expand Down
36 changes: 36 additions & 0 deletions contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import shutil
import tempfile
import imath
import subprocess
import sys
import threading
import time

Expand Down Expand Up @@ -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 ) :

Expand Down
Original file line number Diff line number Diff line change
@@ -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 = [ </inst/Prototypes/sphere>, </cube> ]

def Scope "Prototypes" (
kind = "group"
)
{
def Sphere "sphere"
{
double radius = 1
}
}
}

def Cube "cube"
{
}
Loading