Repository: GafferHQ/gaffer
Pull request #6327: _PointInstancerAdaptor : Control using options instead of attributes
Author: login: danieldresser-ie
Created: 2025-03-21T00:57:29Z | Merged: 2025-03-27T12:37:42Z

Behaviour changes as discussed this morning:

* The attribute "gafferUSD:pointInstancerAdaptor:enabled" has been replaced with the option "gafferUSD:pointInstancerAdaptor:enabled"
* The attribute "gafferUSD:pointInstancerAdaptor:attributes" has been replaced with the option "gafferUSD:pointInstancerAdaptor:attributes"
* The dictionary plug "defaultEnabledPerRenderer" has been replaced with a string plug "defaultEnabledForRenderers" holding a string separated list. A consequence of this is that it can no longer default to enabled for renderers that haven't been specified ( Should I add a special case to default enabled when `renderer` is left as empty string? That would remove the need for some of the test changes.

In addition to the behaviour changes, there's a bit of cleanup. This morning you I think you mentioned two improvements, but said they would probably be unnecessary once we did the behaviour changes. One of them was using the typed output plug of Collect rather than the untyped "enabledValues", in order to avoid needing an expression to transfer the value. We had hoped to avoid this entirely by getting rid of the whole FilterResults -> Collect thing, but in addition to checking attributes, that was also being used with PrimitiveVariableQuery to avoid processing locations that don't have points at them. I'm pretty sure you requested this for the case where a parent location is in the "usd:pointInstancers" set, but isn't actually an instancer, and we don't want to remove the children. I'm assuming we should continue doing this, so I have done the tweak to Collect to avoid using an expression.

I feel like you might have mentioned another improvement that you thought was going to become unnecessary, but I've forgotten what it was, so I'm not sure if I've done it.

I haven't written a Changes entry, since it sounded like you had a plan for how to communicate this stuff.


Diff:
diff --git a/python/GafferUSD/_PointInstancerAdaptor.py b/python/GafferUSD/_PointInstancerAdaptor.py
index c6aa4035ae8..381ed81afda 100644
--- a/python/GafferUSD/_PointInstancerAdaptor.py
+++ b/python/GafferUSD/_PointInstancerAdaptor.py
@@ -52,31 +52,22 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["renderer"] = Gaffer.StringPlug()
 
 		# By default, we are enabled for any renderer other than OpenGL
-		self["defaultEnabledPerRenderer"] = Gaffer.AtomicCompoundDataPlug( defaultValue = { "OpenGL" : False } )
-
-		# First, a simple thing that is a bit messy to do currently: we need all the locations in
-		# the set usd:pointInstancers where the attribute gafferUSD:pointInstancerAdaptor:enabled isn't
-		# set to false. Since we don't have an AttributeFilter, we have to use a FilterResults
-		# to get all the paths, and a Collect to remove the ones where the attribute value is
-		# set to 0.
+		enabledForRenderers = set( GafferScene.Private.IECoreScenePreview.Renderer.types() )
+		enabledForRenderers = enabledForRenderers - set( [ "OpenGL", "Capturing" ] )
+		self["enabledRenderers"] = Gaffer.StringVectorDataPlug( defaultValue = IECore.StringVectorData( enabledForRenderers ) )
+
+		# First, a simple thing that is a bit messy to do: we don't want to process locations that
+		# aren't points. The most obvious way this could come up is if a parent transform is put in
+		# the 'usd:pointInstancers' set - we don't want to hide everything underneath that location.
+		# In order to apply only to actual points primitives, we use a PrimitiveVariableQuery to check
+		# each location for whether it is an object with a P primvar.
 		self["usdPointInstancerSet"] = GafferScene.SetFilter()
 		self["usdPointInstancerSet"]["setExpression"].setValue( 'usd:pointInstancers' )
 
-
 		self["pathInSetFilterResult"] = GafferScene.FilterResults()
 		self["pathInSetFilterResult"]["scene"].setInput( self["in"] )
 		self["pathInSetFilterResult"]["filter"].setInput( self["usdPointInstancerSet"]["out"] )
 
-		self["expandInstancersAttribQuery"] = GafferScene.AttributeQuery()
-		self["expandInstancersAttribQuery"]["scene"].setInput( self["in"] )
-		self["expandInstancersAttribQuery"].setup( Gaffer.BoolPlug( "value", direction = Gaffer.Plug.Direction.Out, defaultValue = False, ) )
-		self["expandInstancersAttribQuery"]["location"].setValue( "${collect:value}" )
-		self["expandInstancersAttribQuery"]["attribute"].setValue( 'gafferUSD:pointInstancerAdaptor:enabled' )
-		self["expandInstancersAttribQuery"]["inherit"].setValue( True )
-
-		self["defaultEnabledExpression"] = Gaffer.Expression()
-		self["defaultEnabledExpression"].setExpression( 'parent["expandInstancersAttribQuery"]["default"] = parent["defaultEnabledPerRenderer"].get( parent["renderer"], IECore.BoolData( True ) )', "python" )
-
 		self["primitiveVariableQuery"] = GafferScene.PrimitiveVariableQuery()
 		self["primitiveVariableQuery"]["scene"].setInput( self["in"] )
 		self["primitiveVariableQuery"]["location"].setValue( "${collect:value}" )
@@ -84,19 +75,12 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["primitiveVariableQuery"]["queries"][0]["name"].setValue( 'P' )
 
 		self["collectValidPaths"] = Gaffer.Collect()
-		self["collectValidPaths"].addInput( Gaffer.StringPlug( "StringPlug", defaultValue = '', ) )
+		self["collectValidPaths"].addInput( Gaffer.StringPlug( "paths", defaultValue = '${collect:value}', ) )
 		self["collectValidPaths"]["contextValues"].setInput( self["pathInSetFilterResult"]["outStrings"] )
-
-		self["validPathExpression"] = Gaffer.Expression()
-		self["validPathExpression"].setExpression( "parent.collectValidPaths.enabled = parent.expandInstancersAttribQuery.value && parent.primitiveVariableQuery.out.out0.exists", "OSL" )
+		self["collectValidPaths"]["enabled"].setInput( self["primitiveVariableQuery"]["out"][0]["exists"] )
 
 		self["validPaths"] = GafferScene.PathFilter()
-
-		# We were mostly able to pull this off without Python expressions, but unfortunately, we do need a
-		# Python expression just to connect these two plugs ( enabledValues acts like a StringVectorDataPlug, but
-		# is typed as an ObjectPlug to allow the type of data returned to change in the future ).
-		self["unfortunateExpressionToConnectToObjectPlug"] = Gaffer.Expression()
-		self["unfortunateExpressionToConnectToObjectPlug"].setExpression( 'parent["validPaths"]["paths"] = parent["collectValidPaths"]["enabledValues"]', 'python' )
+		self["validPaths"]["paths"].setInput( self["collectValidPaths"]["out"]["paths"] )
 
 		# Set an invisible attribute on the descendants of the instancers, which shouldn't be visible
 		# ( USD halts draw traversal when it finds PointInstancer )
@@ -110,12 +94,9 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["invisAttribute"]["attributes"]["visibility"]["value"].setValue( False )
 		self["invisAttribute"]["attributes"]["visibility"]["enabled"].setValue( True )
 
-		self["attrAttributesQuery"] = GafferScene.AttributeQuery()
-		self["attrAttributesQuery"]["scene"].setInput( self["in"] )
-		self["attrAttributesQuery"].setup( Gaffer.StringPlug( "value", direction = Gaffer.Plug.Direction.Out, defaultValue = '', ) )
-		self["attrAttributesQuery"]["location"].setValue( '${scene:path}' )
-		self["attrAttributesQuery"]["inherit"].setValue( True )
-		self["attrAttributesQuery"]["attribute"].setValue( 'gafferUSD:pointInstancerAdaptor:attributes' )
+		self["attrOptionQuery"] = GafferScene.OptionQuery()
+		self["attrOptionQuery"]["scene"].setInput( self["in"] )
+		self["attrOptionQuery"].addQuery( Gaffer.StringPlug( "value" ), "gafferUSD:pointInstancerAdaptor:attributes" )
 
 		self["instancer"] = GafferScene.Instancer()
 		self["instancer"]["in"].setInput( self["invisAttribute"]["out"] )
@@ -126,7 +107,7 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["instancer"]["orientation"].setValue( 'orientation' )
 		self["instancer"]["scale"].setValue( 'scale' )
 		self["instancer"]["inactiveIds"].setValue( 'inactiveIds invisibleIds' )
-		self["instancer"]["attributes"].setInput( self["attrAttributesQuery"]["value"] )
+		self["instancer"]["attributes"].setInput( self["attrOptionQuery"]["out"][0]["value"] )
 		self["instancer"]["attributePrefix"].setValue( 'user:' )
 
 		# Do some rewiring to turn this Instancer into a recursive instancer by plumbing the output
@@ -148,6 +129,16 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 
 		self["instancer"]["encapsulate"].setInput( self["encapsulateSwitch"]["out"]["value"] )
 
+		self["enabledOptionQuery"] = GafferScene.OptionQuery()
+		self["enabledOptionQuery"]["scene"].setInput( self["in"] )
+		self["enabledOptionQuery"].addQuery( Gaffer.BoolPlug( "value" ), "gafferUSD:pointInstancerAdaptor:enabled" )
+
+		self["defaultEnabledExpression"] = Gaffer.Expression()
+		self["defaultEnabledExpression"].setExpression( 'parent["enabledOptionQuery"]["queries"]["query0"]["value"] = parent["renderer"] in parent["enabledRenderers"]' )
+
+		self["invisAttribute"]["enabled"].setInput( self["enabledOptionQuery"]["out"][0]["value"] )
+		self["instancer"]["enabled"].setInput( self["enabledOptionQuery"]["out"][0]["value"] )
+
 		self["out"].setInput( self["instancer"]["out"] )
 
 IECore.registerRunTimeTyped( _PointInstancerAdaptor, typeName = "GafferUSD::_PointInstancerAdaptor" )
diff --git a/python/GafferUSDTest/PromotePointInstancesTest.py b/python/GafferUSDTest/PromotePointInstancesTest.py
index 1909f12c255..2f13c12f35a 100644
--- a/python/GafferUSDTest/PromotePointInstancesTest.py
+++ b/python/GafferUSDTest/PromotePointInstancesTest.py
@@ -82,6 +82,8 @@ def translationMatrix( x ):
 
 		referenceAdaptor = GafferUSD._PointInstancerAdaptor()
 		referenceAdaptor["in"].setInput( simpleParent["out"] )
+		referenceAdaptor["renderer"].setValue( "test" )
+		referenceAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "test" ] ) )
 
 		# Do a simple promote, which both tests the basic functionality, and serves as a reference scene for our
 		# expected output when we're testing trickier cases
@@ -96,6 +98,8 @@ def translationMatrix( x ):
 
 		simplePromoteAdaptor = GafferUSD._PointInstancerAdaptor()
 		simplePromoteAdaptor["in"].setInput( simplePromotePointInstances["out"] )
+		simplePromoteAdaptor["renderer"].setValue( "test" )
+		simplePromoteAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "test" ] ) )
 
 
 		self.assertEqual( simplePromotePointInstances["out"].childNames( "/" ), IECore.InternedStringVectorData( [ "object", "promotedInstances" ] ) )
@@ -137,6 +141,8 @@ def translationMatrix( x ):
 
 		pointInstancerAdaptor = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptor["in"].setInput( promoteInstances["out"] )
+		pointInstancerAdaptor["renderer"].setValue( "test" )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "test" ] ) )
 
 		# Reordering points should not affect the expanded output
 		self.assertScenesEqual( pointInstancerAdaptor["out"], referenceAdaptor["out"] )
diff --git a/python/GafferUSDTest/_PointInstancerAdaptorTest.py b/python/GafferUSDTest/_PointInstancerAdaptorTest.py
index c3eb3da3ad6..c8b080dd19b 100644
--- a/python/GafferUSDTest/_PointInstancerAdaptorTest.py
+++ b/python/GafferUSDTest/_PointInstancerAdaptorTest.py
@@ -63,9 +63,8 @@ def testBasic( self ):
 		pointInstancerSet["filter"].setInput( cubeFilter["out"] )
 		pointInstancerSet["name"].setValue( 'usd:pointInstancers' )
 
-		customAttributes = GafferScene.CustomAttributes()
-		customAttributes["in"].setInput( pointInstancerSet["out"] )
-		customAttributes["filter"].setInput( cubeFilter["out"] )
+		customOptions = GafferScene.CustomOptions()
+		customOptions["in"].setInput( pointInstancerSet["out"] )
 
 		sphere = GafferScene.Sphere()
 
@@ -74,12 +73,14 @@ def testBasic( self ):
 		prototypes["name"].setValue( 'prototypes' )
 
 		parent = GafferScene.Parent()
-		parent["in"].setInput( customAttributes["out"] )
+		parent["in"].setInput( customOptions["out"] )
 		parent["parent"].setValue( '/cube' )
 		parent["children"][0].setInput( prototypes["out"] )
 
 		pointInstancerAdaptor = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptor["in"].setInput( parent["out"] )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( ["test", "Arnold"] ) )
+		pointInstancerAdaptor["renderer"].setValue( "test" )
 
 		self.assertEqual(
 			pointInstancerAdaptor["out"].childNames( "/cube" ),
@@ -120,19 +121,19 @@ def testBasic( self ):
 		self.assertEqual( type( pointInstancerAdaptor["out"].object( "/cube/instances" ) ), GafferScene.Capsule )
 
 		# This parameter controls the default for whether locations get processed, based on the chosen renderer
-		pointInstancerAdaptor["defaultEnabledPerRenderer"].setValue( { "Arnold" : False } )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "test" ] ) )
 		self.assertScenesEqual( pointInstancerAdaptor["out"], pointInstancerAdaptor["in"] )
 
 		# The attribute can override
-		customAttributes["attributes"].addChild( Gaffer.NameValuePlug( "gafferUSD:pointInstancerAdaptor:enabled", Gaffer.BoolPlug( "value", defaultValue = True ), True, "member1" ) )
+		customOptions["options"].addChild( Gaffer.NameValuePlug( "gafferUSD:pointInstancerAdaptor:enabled", Gaffer.BoolPlug( "value", defaultValue = True ), True, "member1" ) )
 
 		self.assertEqual( type( pointInstancerAdaptor["out"].object( "/cube/instances" ) ), GafferScene.Capsule )
 
-		pointInstancerAdaptor["defaultEnabledPerRenderer"].setValue( { "Arnold" : True } )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( ["test", "Arnold" ] ) )
 
 		self.assertEqual( type( pointInstancerAdaptor["out"].object( "/cube/instances" ) ), GafferScene.Capsule )
 
-		customAttributes["attributes"]["member1"]["value"].setValue( False )
+		customOptions["options"]["member1"]["value"].setValue( False )
 
 		self.assertScenesEqual( pointInstancerAdaptor["out"], pointInstancerAdaptor["in"] )
 
@@ -145,6 +146,8 @@ def testRecursiveUSD( self ):
 
 		pointInstancerAdaptor = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptor["in"].setInput( sceneReader["out"] )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "test" ] ) )
+		pointInstancerAdaptor["renderer"].setValue( "test" )
 
 		self.assertSceneValid( pointInstancerAdaptor["out"] )
 		self.assertEqual(
@@ -197,6 +200,7 @@ def testRecursiveUSD( self ):
 
 		pointInstancerAdaptorEncapsulating = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptorEncapsulating["in"].setInput( sceneReader["out"] )
+		pointInstancerAdaptorEncapsulating["enabledRenderers"].setValue( IECore.StringVectorData( [ "Arnold" ] ) )
 		pointInstancerAdaptorEncapsulating["renderer"].setValue( "Arnold" )
 
 		self.assertEqual( type( pointInstancerAdaptorEncapsulating["out"].object( "/inst/instances" ) ), GafferScene.Capsule )
@@ -212,6 +216,7 @@ def testRelativePrototypes( self ):
 		pointInstancerAdaptor = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptor["in"].setInput( sceneReader["out"] )
 		pointInstancerAdaptor["renderer"].setValue( "Arnold" )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "Arnold" ] ) )
 
 		postGroup = GafferScene.Group()
 		postGroup["transform"]["rotate"]["x"].setValue( 30 )
@@ -224,6 +229,7 @@ def testRelativePrototypes( self ):
 		pointInstancerAdaptorAfterGroup = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptorAfterGroup["in"].setInput( preGroup["out"] )
 		pointInstancerAdaptorAfterGroup["renderer"].setValue( "Arnold" )
+		pointInstancerAdaptorAfterGroup["enabledRenderers"].setValue( IECore.StringVectorData( [ "Arnold" ] ) )
 
 		self.assertScenesRenderSame( postGroup["out"], pointInstancerAdaptorAfterGroup["out"], expandProcedurals = True, ignoreLinks = True )
 
@@ -249,6 +255,7 @@ def testRelativePrototypes( self ):
 		pointInstancerAdaptorAfterMerge = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptorAfterMerge["in"].setInput( preMerge["out"] )
 		pointInstancerAdaptorAfterMerge["renderer"].setValue( "Arnold" )
+		pointInstancerAdaptorAfterMerge["enabledRenderers"].setValue( IECore.StringVectorData( [ "Arnold" ] )  )
 
 		self.assertScenesRenderSame( pointInstancerAdaptorAfterMerge["out"], refMerge["out"], expandProcedurals = True, ignoreLinks = True )
 
@@ -281,6 +288,8 @@ def testNullObjectInInstancerSet( self ):
 
 		pointInstancerAdaptor = GafferUSD._PointInstancerAdaptor()
 		pointInstancerAdaptor["in"].setInput( pointInstancerSet["out"] )
+		pointInstancerAdaptor["renderer"].setValue( "test Arnold" )
+		pointInstancerAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( [ "test" ] ) )
 
 		# We're not making the children invisible
 		self.assertEqual( pointInstancerAdaptor["out"].attributes( "/group/group" ), IECore.CompoundObject() )
diff --git a/python/GafferUSDUI/_PointInstancerAdaptorUI.py b/python/GafferUSDUI/_PointInstancerAdaptorUI.py
index a8e0677208b..5a8250345fc 100644
--- a/python/GafferUSDUI/_PointInstancerAdaptorUI.py
+++ b/python/GafferUSDUI/_PointInstancerAdaptorUI.py
@@ -59,14 +59,14 @@
 
 		],
 
-		"defaultEnabledPerRenderer" : [
+		"enabledRenderers" : [
 
 			"description",
 			"""
-			Controls whether the adaptor is hidden by default. Contains a dict, with a boolean value for
-			each renderer. Defaults true for any renderers not specified in the dict. Slightly weird
-			interface, but is basically internal. It should only ever be edited by the expansion menu
-			set up in startup/GafferSceneUI/usdPointInstancerAdaptor.py
+			If a renderer is listed in this space separated list, the adaptor will be enabled by default
+			for that renderer ( it can still be overridden by the option gafferUSD:pointInstancerAdaptor:enabled ).
+			This should plug only ever be edited by the expansion menu set up in
+			startup/GafferSceneUI/usdPointInstancerAdaptor.py
 			""",
 
 		],
diff --git a/startup/GafferSceneUI/usdPointInstancerAdaptor.py b/startup/GafferSceneUI/usdPointInstancerAdaptor.py
index d65336b7543..ce2655e76f3 100644
--- a/startup/GafferSceneUI/usdPointInstancerAdaptor.py
+++ b/startup/GafferSceneUI/usdPointInstancerAdaptor.py
@@ -49,13 +49,15 @@ def __expandUSDPointInstancersMenu( menuDefinition, plugValueWidget ) :
 		return
 
 	renderer = renderAdaptor["renderer"].getValue()
-	currentDict = renderAdaptor["defaultEnabledPerRenderer"].getValue()
-	current = currentDict.get( renderer, IECore.BoolData( True ) ).value
+	currentList = list( renderAdaptor["enabledRenderers"].getValue() )
+	current = renderer in currentList
 
 	def callBackFunc( unused ):
-		modified = currentDict.copy()
-		modified[renderer] = IECore.BoolData( not current )
-		renderAdaptor["defaultEnabledPerRenderer"].setValue( modified )
+		if current:
+			modifiedList = [ i for i in currentList if i != renderer ]
+		else:
+			modifiedList = currentList + [ renderer ]
+		renderAdaptor["enabledRenderers"].setValue( IECore.StringVectorData( modifiedList ) )
 
 	menuDefinition.append( "/AttributesDivider", { "divider" : True } )
 	menuDefinition.append(


Reviews:
user: login: johnhaddon
body: Thanks Daniel - LGTM apart from the two small comments inline.

It's unfortunate the even after this, transform and attribute dirtiness translates to additional object dirtiness, but I'm not sure there's much we can do about that? Or did we have an idea for cases where we could statically determine that encapsulation was off?

> I'm pretty sure you requested this for the case where a parent location is in the "usd:pointInstancers" set, but isn't actually an instancer, and we don't want to remove the children. I'm assuming we should continue doing this, so I have done the tweak to Collect to avoid using an expression.

Yep, that was to avoid breaking scripts where folks have applied an Instancer manually. Keeping that is good.

> I feel like you might have mentioned another improvement that you thought was going to become unnecessary, but I've forgotten what it was

I only recall the two you've done, so if I had another idea, it has escaped us both.

> One extra thing I noticed during testing: it looks like this adaptor is getting applied before the arnoldViewerSettings adaptor ... which means that if you have the default setting of Shading = Flat, that doesn't actually override the shaders, because everything gets encapsulated before the shader override, so the shader override doesn't see the shaders. 

Good catch - maybe we should open a separate PR to move the render settings before the adaptors then? Might be good to check with @murraystevenson on that - he probably has the best overview of any likely downsides. I remember us being pleasantly surprised recently when we found the diagnostic shading modes were applied _after_ the adaptors, and I wonder if there are any similar considerations with the render settings.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-24T13:09:14Z
user: login: danieldresser-ie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T22:17:36Z
user: login: danieldresser-ie
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-24T22:23:08Z

Review comments:
diff_hunk: @@ -52,51 +52,35 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["renderer"] = Gaffer.StringPlug()
 
 		# By default, we are enabled for any renderer other than OpenGL
-		self["defaultEnabledPerRenderer"] = Gaffer.AtomicCompoundDataPlug( defaultValue = { "OpenGL" : False } )
-
-		# First, a simple thing that is a bit messy to do currently: we need all the locations in
-		# the set usd:pointInstancers where the attribute gafferUSD:pointInstancerAdaptor:enabled isn't
-		# set to false. Since we don't have an AttributeFilter, we have to use a FilterResults
-		# to get all the paths, and a Collect to remove the ones where the attribute value is
-		# set to 0.
+		enabledForRenderers = set( GafferScene.Private.IECoreScenePreview.Renderer.types() )
+		enabledForRenderers = enabledForRenderers - set( [ "OpenGL", "Capturing" ] )
+		self["defaultEnabledForRenderers"] = Gaffer.StringPlug( defaultValue = " ".join( enabledForRenderers ) )
path: python/GafferUSD/_PointInstancerAdaptor.py
user: login: johnhaddon
body: I appreciate that you're trying to communicate that this-is-the-default-value-for-the-option-query, but `defaultEnabledForRenderers` sounds kindof awkward. Perhaps just `enabledRenderers`, and then we document that it can be overridden by the option?
created_at: 2025-03-24T12:49:07Z
author_association: MEMBER
diff_hunk: @@ -52,51 +52,35 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["renderer"] = Gaffer.StringPlug()
 
 		# By default, we are enabled for any renderer other than OpenGL
-		self["defaultEnabledPerRenderer"] = Gaffer.AtomicCompoundDataPlug( defaultValue = { "OpenGL" : False } )
-
-		# First, a simple thing that is a bit messy to do currently: we need all the locations in
-		# the set usd:pointInstancers where the attribute gafferUSD:pointInstancerAdaptor:enabled isn't
-		# set to false. Since we don't have an AttributeFilter, we have to use a FilterResults
-		# to get all the paths, and a Collect to remove the ones where the attribute value is
-		# set to 0.
+		enabledForRenderers = set( GafferScene.Private.IECoreScenePreview.Renderer.types() )
+		enabledForRenderers = enabledForRenderers - set( [ "OpenGL", "Capturing" ] )
+		self["defaultEnabledForRenderers"] = Gaffer.StringPlug( defaultValue = " ".join( enabledForRenderers ) )
path: python/GafferUSD/_PointInstancerAdaptor.py
user: login: johnhaddon
body: "3Delight Cloud" causes a bit of a problem for this join-with-spaces approach. Perhaps we can `renderer.replace( " ", "?" )` for now? We'd need to update the menu to do the same. Any better ideas?

Longer term, RenderMan requiring all the scene options before creation makes me think we might end up adjusting our Renderer API to match, in which case we could make a `dl:cloud` option and drop the separate "3Delight Cloud" renderer. Maybe that makes us feel better about the hack above?
created_at: 2025-03-24T12:53:27Z
author_association: MEMBER
diff_hunk: @@ -52,51 +52,35 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["renderer"] = Gaffer.StringPlug()
 
 		# By default, we are enabled for any renderer other than OpenGL
-		self["defaultEnabledPerRenderer"] = Gaffer.AtomicCompoundDataPlug( defaultValue = { "OpenGL" : False } )
-
-		# First, a simple thing that is a bit messy to do currently: we need all the locations in
-		# the set usd:pointInstancers where the attribute gafferUSD:pointInstancerAdaptor:enabled isn't
-		# set to false. Since we don't have an AttributeFilter, we have to use a FilterResults
-		# to get all the paths, and a Collect to remove the ones where the attribute value is
-		# set to 0.
+		enabledForRenderers = set( GafferScene.Private.IECoreScenePreview.Renderer.types() )
+		enabledForRenderers = enabledForRenderers - set( [ "OpenGL", "Capturing" ] )
+		self["defaultEnabledForRenderers"] = Gaffer.StringPlug( defaultValue = " ".join( enabledForRenderers ) )
path: python/GafferUSD/_PointInstancerAdaptor.py
user: login: danieldresser-ie
body: Renamed.
created_at: 2025-03-24T22:17:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -52,51 +52,35 @@ def __init__( self, name = "_PointInstancerAdaptor" ) :
 		self["renderer"] = Gaffer.StringPlug()
 
 		# By default, we are enabled for any renderer other than OpenGL
-		self["defaultEnabledPerRenderer"] = Gaffer.AtomicCompoundDataPlug( defaultValue = { "OpenGL" : False } )
-
-		# First, a simple thing that is a bit messy to do currently: we need all the locations in
-		# the set usd:pointInstancers where the attribute gafferUSD:pointInstancerAdaptor:enabled isn't
-		# set to false. Since we don't have an AttributeFilter, we have to use a FilterResults
-		# to get all the paths, and a Collect to remove the ones where the attribute value is
-		# set to 0.
+		enabledForRenderers = set( GafferScene.Private.IECoreScenePreview.Renderer.types() )
+		enabledForRenderers = enabledForRenderers - set( [ "OpenGL", "Capturing" ] )
+		self["defaultEnabledForRenderers"] = Gaffer.StringPlug( defaultValue = " ".join( enabledForRenderers ) )
path: python/GafferUSD/_PointInstancerAdaptor.py
user: login: danieldresser-ie
body: When I skimmed this this morning, I was thinking of using a different separator rather than spaces, I'd forgotten that the PatternMatch node is hardcoded to spaces, so we'd have to do the replacement on the renderer names. It ended up feeling more obvious to me to use a StringVectorData to hold the list of names, rather than tokenizing - this removes any concern with the separator matching something. Hopefully that feels reasonable? Maybe it's going backwards, since the original dict approach was too much data structure? The list feels pretty clear to me. It does mean we're back to using an expression, but I can't really picture any scenario where the expression doesn't get cached, and we end up frequently evaluating Python.
created_at: 2025-03-24T22:23:08Z
author_association: CONTRIBUTOR

PR comments:
user: login: danieldresser-ie
created_at: 2025-03-21T01:10:27Z
body: One extra thing I noticed during testing: it looks like this adaptor is getting applied before the arnoldViewerSettings adaptor ... which means that if you have the default setting of Shading = Flat, that doesn't actually override the shaders, because everything gets encapsulated before the shader override, so the shader override doesn't see the shaders. That feels pretty counterintuitive.
author_association: CONTRIBUTOR
user: login: danieldresser-ie
created_at: 2025-03-24T22:26:01Z
body: I've replied to your inline comments.

Other issues discussed:
* attribute dirties propagating to object in Instancer: not addressed as part of this PR. Doesn't seem that urgent to me, because usually when we care about performance, we are encapsulating, which means we can't avoid these dirties.
* reordering render settings / instancer adaptor: I'll wait for comment from @murraystevenson 
author_association: CONTRIBUTOR