From b60487ead8f54cfefb8f28fe29b0cb72f0ea2c23 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Fri, 7 Jun 2024 16:52:53 -0400 Subject: [PATCH 01/10] graphics : Clean up LightEditor ExportAreas --- resources/graphics.svg | 178 ++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 84 deletions(-) diff --git a/resources/graphics.svg b/resources/graphics.svg index a79f4fd1be5..40ec120567a 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -3050,13 +3050,103 @@ + + + + + + + + + + - - - - - - - - - - Date: Fri, 21 Jun 2024 13:00:32 -0400 Subject: [PATCH 02/10] LightEditor : Support light filter parameters --- Changes.md | 1 + python/GafferSceneUI/LightEditor.py | 40 ++++++-- python/GafferSceneUITest/LightEditorTest.py | 4 +- resources/graphics.py | 4 + resources/graphics.svg | 96 ++++++++++++++++++- .../LightEditorBinding.cpp | 32 +++++-- 6 files changed, 162 insertions(+), 15 deletions(-) diff --git a/Changes.md b/Changes.md index dc7889ba76d..ed34648440c 100644 --- a/Changes.md +++ b/Changes.md @@ -30,6 +30,7 @@ API - Loop : Added `nextIterationContext()` method. - AnnotationsGadget : Added `annotationText()` method. - ParallelAlgoTest : Added `UIThreadCallHandler.receive()` method. +- LightEditor : Added `registerShaderParameter()` method for registering parameters for shader attributes that are not the same as the `rendererKey`. Build ----- diff --git a/python/GafferSceneUI/LightEditor.py b/python/GafferSceneUI/LightEditor.py index b9d65cc46c6..9d39acca23c 100644 --- a/python/GafferSceneUI/LightEditor.py +++ b/python/GafferSceneUI/LightEditor.py @@ -83,7 +83,7 @@ def __init__( self, scriptNode, **kw ) : Gaffer.NodeAlgo.applyUserDefaults( self.__settingsNode ) self.__setFilter = _GafferSceneUI._HierarchyViewSetFilter() - self.__setFilter.setSetNames( [ "__lights" ] ) + self.__setFilter.setSetNames( [ "__lights", "__lightFilters" ] ) with column : @@ -137,20 +137,27 @@ def scene( self ) : return self.__plug - # Registers a parameter to be available for editing. `rendererKey` is a pattern - # that will be matched against `self.__settingsNode["attribute"]` to determine if - # the column should be shown. @classmethod - def registerParameter( cls, rendererKey, parameter, section = None, columnName = None ) : + def __parseParameter( cls, parameter ) : if isinstance( parameter, str ) : shader = "" param = parameter if "." in parameter : shader, dot, param = parameter.partition( "." ) - parameter = IECoreScene.ShaderNetwork.Parameter( shader, param ) + return IECoreScene.ShaderNetwork.Parameter( shader, param ) else : assert( isinstance( parameter, IECoreScene.ShaderNetwork.Parameter ) ) + return parameter + + # Registers a parameter to be available for editing. `rendererKey` is a pattern + # that will be matched against `self.__settingsNode["attribute"]` to determine if + # the column should be shown. + # \todo Deprecate in favor of method below. + @classmethod + def registerParameter( cls, rendererKey, parameter, section = None, columnName = None ) : + + parameter = cls.__parseParameter( parameter ) GafferSceneUI.LightEditor.registerColumn( rendererKey, @@ -162,6 +169,27 @@ def registerParameter( cls, rendererKey, parameter, section = None, columnName = section ) + # Registers a parameter to be available for editing. `rendererKey` is a pattern + # that will be matched against `self.__settingsNode["attribute"]` to determine if + # the column should be shown. `attribute` is the attribute holding the shader that + # will be edited. If it is `None`, the attribute will be the same as `rendererKey`. + @classmethod + def registerShaderParameter( cls, rendererKey, parameter, shaderAttribute = None, section = None, columnName = None ) : + + parameter = cls.__parseParameter( parameter ) + + shaderAttribute = shaderAttribute if shaderAttribute is not None else rendererKey + + GafferSceneUI.LightEditor.registerColumn( + rendererKey, + ".".join( x for x in [ parameter.shader, parameter.name ] if x ), + lambda scene, editScope : _GafferSceneUI._LightEditorInspectorColumn( + GafferSceneUI.Private.ParameterInspector( scene, editScope, shaderAttribute, parameter ), + columnName if columnName is not None else "" + ), + section + ) + @classmethod def registerAttribute( cls, rendererKey, attributeName, section = None ) : diff --git a/python/GafferSceneUITest/LightEditorTest.py b/python/GafferSceneUITest/LightEditorTest.py index 5ea7a50c325..c0f099fe33d 100644 --- a/python/GafferSceneUITest/LightEditorTest.py +++ b/python/GafferSceneUITest/LightEditorTest.py @@ -708,8 +708,8 @@ def testToggleContext( self ) : def testShaderParameterEditScope( self ) : - GafferSceneUI.LightEditor.registerParameter( "light", "add.a" ) - GafferSceneUI.LightEditor.registerParameter( "light", "exposure" ) + GafferSceneUI.LightEditor.registerShaderParameter( "light", "add.a" ) + GafferSceneUI.LightEditor.registerShaderParameter( "light", "exposure" ) script = Gaffer.ScriptNode() diff --git a/resources/graphics.py b/resources/graphics.py index b5b720018f2..30b475696e8 100644 --- a/resources/graphics.py +++ b/resources/graphics.py @@ -371,6 +371,10 @@ "setMemberHighlighted", "setMemberFaded", "setMemberFadedHighlighted", + "boxBlocker", + "sphereBlocker", + "planeBlocker", + "cylinderBlocker", ] }, diff --git a/resources/graphics.svg b/resources/graphics.svg index 40ec120567a..460015390ad 100644 --- a/resources/graphics.svg +++ b/resources/graphics.svg @@ -69,7 +69,7 @@ + + + + + + + + + + + + + + + + + + + + members() ) { - if( attribute.first != "light" && !boost::ends_with( attribute.first.c_str(), ":light" ) ) + std::vector tokens; + StringAlgo::tokenize( attribute.first, ':', tokens ); + if( + attribute.first != "light" && + tokens.back() != "light" && + attribute.first != "lightFilter" && + ( tokens.size() < 2 || tokens[1] != "lightFilter" ) + ) { continue; } @@ -129,15 +136,28 @@ class LocationNameColumn : public StandardPathColumn continue; } - const IECoreScene::Shader *lightShader = shaderNetwork->outputShader(); - const string metadataTarget = attribute.first.string() + ":" + lightShader->getName(); - ConstStringDataPtr lightType = Metadata::value( metadataTarget, "type" ); - if( !lightType ) + const IECoreScene::Shader *shader = shaderNetwork->outputShader(); + const string metadataTarget = attribute.first.string() + ":" + shader->getName(); + ConstStringDataPtr type = Metadata::value( metadataTarget, "type" ); + if( !type ) { continue; } - result.icon = new StringData( lightType->readable() + "Light.png" ); + if( type->readable() == "lightBlocker" ) + { + if( ConstStringDataPtr blockerTypeParameter = Metadata::value( metadataTarget, "typeParameter" ) ) + { + if( ConstStringDataPtr blockerType = shader->parametersData()->member( blockerTypeParameter->readable() ) ) + { + result.icon = new StringData( blockerType->readable() + "Blocker.png" ); + } + } + } + else + { + result.icon = new StringData( type->readable() + "Light.png" ); + } } /// \todo Add support for icons based on object type. We don't want to have From e6094853752e98294feb2cd21985b6851775b56a Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Tue, 11 Jun 2024 10:47:38 -0400 Subject: [PATCH 03/10] LightEditor : Add Arnold blocker parameters --- Changes.md | 1 + python/GafferArnoldUI/ArnoldShaderUI.py | 8 ++++++++ startup/GafferScene/arnoldLights.py | 3 +++ 3 files changed, 12 insertions(+) diff --git a/Changes.md b/Changes.md index ed34648440c..37807796251 100644 --- a/Changes.md +++ b/Changes.md @@ -12,6 +12,7 @@ Improvements - Annotations : - Added support for `{plug}` value substitutions in node annotations. - Added Ctrl + Enter keyboard shortcut to annotation dialogue. This applies the annotation and closes the dialogue. +- LightEditor : Added support for Arnold light blockers. Fixes ----- diff --git a/python/GafferArnoldUI/ArnoldShaderUI.py b/python/GafferArnoldUI/ArnoldShaderUI.py index 63ba243a2f0..a075f13f5d7 100644 --- a/python/GafferArnoldUI/ArnoldShaderUI.py +++ b/python/GafferArnoldUI/ArnoldShaderUI.py @@ -345,6 +345,14 @@ def __translateNodeMetadata( nodeEntry ) : "ai:light", paramName, page ) + if ( + nodeName == "light_blocker" and + __aiMetadataGetStr( nodeEntry, paramName, "gaffer.plugType" ) != "" + ) : + GafferSceneUI.LightEditor.registerShaderParameter( + "ai:light", paramName, "ai:lightFilter:filter", "Blocker" + ) + # Label from OSL "label" label = __aiMetadataGetStr( nodeEntry, paramName, "label" ) if label is None : diff --git a/startup/GafferScene/arnoldLights.py b/startup/GafferScene/arnoldLights.py index 2aa4386aeee..7d9de953509 100644 --- a/startup/GafferScene/arnoldLights.py +++ b/startup/GafferScene/arnoldLights.py @@ -102,3 +102,6 @@ # Most profiles generally shine down -y Gaffer.Metadata.registerValue( "ai:light:photometric_light", "visualiserOrientation", imath.M44f().rotate( imath.V3f( -0.5 * math.pi, 0 , 0 ) ) ) Gaffer.Metadata.registerValue( "ai:light:photometric_light", "type", "photometric" ) + +Gaffer.Metadata.registerValue( "ai:lightFilter:filter:light_blocker", "typeParameter", "geometry_type" ) +Gaffer.Metadata.registerValue( "ai:lightFilter:filter:light_blocker", "type", "lightBlocker" ) From 593424738626a9925068b266234fa8649dd82af2 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Tue, 11 Jun 2024 12:19:15 -0400 Subject: [PATCH 04/10] LightEditor : Add Arnold light filter parameters --- Changes.md | 2 +- python/GafferArnoldUI/ArnoldShaderUI.py | 40 +++++++++++++++++-------- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/Changes.md b/Changes.md index 37807796251..5445f76f395 100644 --- a/Changes.md +++ b/Changes.md @@ -12,7 +12,7 @@ Improvements - Annotations : - Added support for `{plug}` value substitutions in node annotations. - Added Ctrl + Enter keyboard shortcut to annotation dialogue. This applies the annotation and closes the dialogue. -- LightEditor : Added support for Arnold light blockers. +- LightEditor : Added support for Arnold light blockers and barndoor, gobo and decay light filters. Fixes ----- diff --git a/python/GafferArnoldUI/ArnoldShaderUI.py b/python/GafferArnoldUI/ArnoldShaderUI.py index a075f13f5d7..251ddcbde09 100644 --- a/python/GafferArnoldUI/ArnoldShaderUI.py +++ b/python/GafferArnoldUI/ArnoldShaderUI.py @@ -51,6 +51,15 @@ import GafferSceneUI import GafferArnold +# Arnold shaders to add to the light editor. +lightEditorShaders = { + # "shaderName" : ( "shaderAttributeName", "lightEditorSection" ) + "light_blocker" : ( "ai:lightFilter:filter", "Blocker" ), + "barndoor" : ( "ai:lightFilter:barndoor", "Barndoor" ), + "gobo" : ( "ai:lightFilter:gobo", "Gobo" ), + "light_decay" : ( "ai:lightFilter:light_decay", "Decay" ), +} + ########################################################################## # Utilities to make it easier to work with the Arnold API, which has a # fairly bare wrapping using ctypes. @@ -337,6 +346,17 @@ def __translateNodeMetadata( nodeEntry ) : parent = paramPath.rsplit( '.', 1 )[0] __metadata[parent]["layout:section:%s:collapsed" % page] = collapsed + # Label from OSL "label" + label = __aiMetadataGetStr( nodeEntry, paramName, "label" ) + if label is None : + # Label from Arnold naming convention + # Arnold uses snake_case rather than camelCase for naming, so translate this into + # nice looking names + label = " ".join( [ i.capitalize() for i in paramName.split( "_" ) ] ) + + __metadata[paramPath]["label"] = label + __metadata[paramPath]["noduleLayout:label"] = label + if ( arnold.AiNodeEntryGetType( nodeEntry ) == arnold.AI_NODE_LIGHT and __aiMetadataGetStr( nodeEntry, paramName, "gaffer.plugType" ) != "" @@ -346,24 +366,18 @@ def __translateNodeMetadata( nodeEntry ) : ) if ( - nodeName == "light_blocker" and + nodeName in lightEditorShaders and __aiMetadataGetStr( nodeEntry, paramName, "gaffer.plugType" ) != "" ) : + attributeName, sectionName = lightEditorShaders[nodeName] GafferSceneUI.LightEditor.registerShaderParameter( - "ai:light", paramName, "ai:lightFilter:filter", "Blocker" + "ai:light", + paramName, + attributeName, + sectionName, + f"{page} {label}" if page is not None and label is not None else paramName ) - # Label from OSL "label" - label = __aiMetadataGetStr( nodeEntry, paramName, "label" ) - if label is None : - # Label from Arnold naming convention - # Arnold uses snake_case rather than camelCase for naming, so translate this into - # nice looking names - label = " ".join( [ i.capitalize() for i in paramName.split( "_" ) ] ) - - __metadata[paramPath]["label"] = label - __metadata[paramPath]["noduleLayout:label"] = label - childComponents = { arnold.AI_TYPE_VECTOR2 : "xy", arnold.AI_TYPE_VECTOR : "xyz", From 85d93d31940fe72596ea072a6f8ae2ac4aa036b7 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Tue, 11 Jun 2024 16:03:57 -0400 Subject: [PATCH 05/10] LightEditor : Ignore Mute and Solo for blockers --- python/GafferSceneUI/LightEditor.py | 22 ++++++++-- python/GafferSceneUITest/LightEditorTest.py | 40 +++++++++++++++++-- .../LightEditorBinding.cpp | 19 +++++++++ 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/python/GafferSceneUI/LightEditor.py b/python/GafferSceneUI/LightEditor.py index 9d39acca23c..83492a684be 100644 --- a/python/GafferSceneUI/LightEditor.py +++ b/python/GafferSceneUI/LightEditor.py @@ -292,9 +292,9 @@ def __updateColumns( self ) : sectionColumns += [ c( self.__settingsNode["in"], self.__settingsNode["editScope"] ) for c in section.values() ] nameColumn = self.__pathListing.getColumns()[0] - muteColumn = self.__pathListing.getColumns()[1] - soloColumn = self.__pathListing.getColumns()[2] - self.__pathListing.setColumns( [ nameColumn, muteColumn, soloColumn ] + sectionColumns ) + self.__muteColumn = self.__pathListing.getColumns()[1] + self.__soloColumn = self.__pathListing.getColumns()[2] + self.__pathListing.setColumns( [ nameColumn, self.__muteColumn, self.__soloColumn ] + sectionColumns ) def __settingsPlugSet( self, plug ) : @@ -382,11 +382,27 @@ def __editSelectedCells( self, pathListing, quickBoolean = True ) : inspections = [] with Gaffer.Context( self.getContext() ) as context : + lightSetMembers = self.__settingsNode["in"].set( "__lights" ).value + for selection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : if not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) : continue for pathString in selection.paths() : path = GafferScene.ScenePlug.stringToPath( pathString ) + + if ( + ( column == self.__muteColumn or column == self.__soloColumn ) and + not ( lightSetMembers.match( path ) & ( + IECore.PathMatcher.Result.ExactMatch | IECore.PathMatcher.Result.DescendantMatch + ) ) + ) : + with GafferUI.PopupWindow() as self.__popup : + with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + GafferUI.Image( "warningSmall.png" ) + GafferUI.Label( "

The " + column.headerData().value + " column can only be toggled for lights." ) + self.__popup.popup() + return + context["scene:path"] = path inspection = column.inspector().inspect() diff --git a/python/GafferSceneUITest/LightEditorTest.py b/python/GafferSceneUITest/LightEditorTest.py index c0f099fe33d..42790df67dd 100644 --- a/python/GafferSceneUITest/LightEditorTest.py +++ b/python/GafferSceneUITest/LightEditorTest.py @@ -649,6 +649,9 @@ def testLightMuteAttribute( toggleCount, toggleLocation, newStates ) : editor = GafferSceneUI.LightEditor( script ) editor._LightEditor__settingsNode["editScope"].setInput( script["editScope"]["out"] ) + editor._LightEditor__updateColumns() + GafferSceneUI.LightEditor._LightEditor__updateColumns.flush( editor ) + widget = editor._LightEditor__pathListing self.setLightEditorMuteSelection( widget, togglePaths ) @@ -696,6 +699,9 @@ def testToggleContext( self ) : self.assertEqual( attr["gl:visualiser:scale"].value, 5.0 ) editor = GafferSceneUI.LightEditor( script ) + editor._LightEditor__updateColumns() + GafferSceneUI.LightEditor._LightEditor__updateColumns.flush( editor ) + widget = editor._LightEditor__pathListing editor.setNodeSet( Gaffer.StandardSet( [ script["custAttr"] ] ) ) self.setLightEditorMuteSelection( widget, ["/group/light"] ) @@ -731,12 +737,11 @@ def testShaderParameterEditScope( self ) : self.assertEqual( attributes["light"].shaders()["add"].parameters["a"].value, imath.Color3f( 0.0 ) ) self.assertEqual( attributes["light"].shaders()["__shader"].parameters["exposure"].value, 0.0 ) - with GafferUI.Window() as window : - editor = GafferSceneUI.LightEditor( script ) + editor = GafferSceneUI.LightEditor( script ) editor._LightEditor__settingsNode["editScope"].setInput( script["editScope"]["out"] ) - window.setVisible( True ) - self.waitForIdle( 1000 ) + editor._LightEditor__updateColumns() + GafferSceneUI.LightEditor._LightEditor__updateColumns.flush( editor ) editor.setNodeSet( Gaffer.StandardSet( [ script["editScope"] ] ) ) @@ -863,5 +868,32 @@ def testDeregisterColumn( self ) : self.assertNotIn( "Y", columnNames ) self.assertNotIn( "Z", columnNames ) + def testLightBlockerSoloDisabled( self ) : + + script = Gaffer.ScriptNode() + + script["blocker"] = GafferScene.Cube() + script["blocker"]["sets"].setValue( "__lightFilters" ) + + editor = GafferSceneUI.LightEditor( script ) + editor._LightEditor__updateColumns() + GafferSceneUI.LightEditor._LightEditor__updateColumns.flush( editor ) + + editor.setNodeSet( Gaffer.StandardSet( [ script["blocker"] ] ) ) + + widget = editor._LightEditor__pathListing + + columns = widget.getColumns() + for i, c in zip( range( 0, len( columns ) ), columns ) : + if isinstance( c, _GafferSceneUI._LightEditorSetMembershipColumn ) : + selection = [ IECore.PathMatcher() for i in range( 0, len( columns ) ) ] + selection[i].addPath( "/cube" ) + widget.setSelection( selection ) + + editor._LightEditor__editSelectedCells( widget ) + + self.assertTrue( script["blocker"]["out"].set( "soloLights" ).value.isEmpty() ) + + if __name__ == "__main__" : unittest.main() diff --git a/src/GafferSceneUIModule/LightEditorBinding.cpp b/src/GafferSceneUIModule/LightEditorBinding.cpp index ea5260f9fa0..ef7aaa17d90 100644 --- a/src/GafferSceneUIModule/LightEditorBinding.cpp +++ b/src/GafferSceneUIModule/LightEditorBinding.cpp @@ -79,6 +79,15 @@ namespace { ConstStringDataPtr g_emptyLocation = new StringData( "emptyLocation.png" ); +const InternedString g_lightSetName( "__lights" ); + +bool isLight( const ScenePath *scenePath, const Canceller *canceller ) +{ + ScenePlug::SetScope scope( scenePath->getContext(), &g_lightSetName ); + scope.setCanceller( canceller ); + ConstPathMatcherDataPtr lightsData = scenePath->getScene()->setPlug()->getValue(); + return lightsData->readable().match( scenePath->names() ) & PathMatcher::ExactMatch; +} class LocationNameColumn : public StandardPathColumn { @@ -289,6 +298,11 @@ class MuteColumn : public InspectorColumn return result; } + if( !isLight( scenePath, canceller ) ) + { + return CellData(); + } + if( auto value = runTimeCast( result.value ) ) { result.icon = value->readable() ? m_muteIconData : m_unMuteIconData; @@ -409,6 +423,11 @@ class SetMembershipColumn : public InspectorColumn return result; } + if( !isLight( scenePath, canceller ) ) + { + return CellData(); + } + std::string toolTip; if( auto toolTipData = runTimeCast( result.toolTip ) ) { From dc9cea9ef85c41b54625770b57b66ce3c4b91cd0 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Mon, 17 Jun 2024 15:40:28 -0400 Subject: [PATCH 06/10] EditScopeAlgo : Improve processor names --- src/GafferScene/EditScopeAlgo.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/GafferScene/EditScopeAlgo.cpp b/src/GafferScene/EditScopeAlgo.cpp index 4c8dcc237cd..b73b8a26631 100644 --- a/src/GafferScene/EditScopeAlgo.cpp +++ b/src/GafferScene/EditScopeAlgo.cpp @@ -398,8 +398,23 @@ const boost::container::flat_map g_rendererAttributePrefixes = { { "cycles", "Cycles" } }; +/// \todo Create a registration method for populating overrides. +using ProcessorOverrideMap = std::unordered_map; +const ProcessorOverrideMap g_processorNameOverrides = { + { "ai:lightFilter:filter", "ArnoldLightBlockerEdits" }, + { "ai:lightFilter:barndoor", "ArnoldBarndoorEdits" }, + { "ai:lightFilter:light_decay", "ArnoldLightDecayEdits" }, + { "ai:lightFilter:gobo", "ArnoldGoboEdits" } +}; + string parameterProcessorName( const std::string &attribute ) { + ProcessorOverrideMap::const_iterator override = g_processorNameOverrides.find( attribute ); + if( override != g_processorNameOverrides.end() ) + { + return override->second; + } + string rendererPrefix; vector parts; From a79ea5ea08412068cab077f40b9efd8ebc214817 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Mon, 17 Jun 2024 15:57:19 -0400 Subject: [PATCH 07/10] EditScopeAlgo : `*FilterEdits` in Location Edits --- python/GafferSceneUI/EditScopeUI.py | 2 +- src/GafferScene/EditScopeAlgo.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/python/GafferSceneUI/EditScopeUI.py b/python/GafferSceneUI/EditScopeUI.py index d409c330090..43414384320 100644 --- a/python/GafferSceneUI/EditScopeUI.py +++ b/python/GafferSceneUI/EditScopeUI.py @@ -219,7 +219,7 @@ def _summary( processor, linkCreator ) : summaries[0] = summaries[0][0].upper() + summaries[0][1:] return " and ".join( summaries ) -GafferUI.EditScopeUI.ProcessorWidget.registerProcessorWidget( "AttributeEdits TransformEdits *LightEdits *SurfaceEdits", __LocationEditsWidget ) +GafferUI.EditScopeUI.ProcessorWidget.registerProcessorWidget( "AttributeEdits TransformEdits *LightEdits *SurfaceEdits *FilterEdits", __LocationEditsWidget ) class __PruningEditsWidget( _SceneProcessorWidget ) : diff --git a/src/GafferScene/EditScopeAlgo.cpp b/src/GafferScene/EditScopeAlgo.cpp index b73b8a26631..59cdcb7cc86 100644 --- a/src/GafferScene/EditScopeAlgo.cpp +++ b/src/GafferScene/EditScopeAlgo.cpp @@ -401,10 +401,10 @@ const boost::container::flat_map g_rendererAttributePrefixes = { /// \todo Create a registration method for populating overrides. using ProcessorOverrideMap = std::unordered_map; const ProcessorOverrideMap g_processorNameOverrides = { - { "ai:lightFilter:filter", "ArnoldLightBlockerEdits" }, - { "ai:lightFilter:barndoor", "ArnoldBarndoorEdits" }, - { "ai:lightFilter:light_decay", "ArnoldLightDecayEdits" }, - { "ai:lightFilter:gobo", "ArnoldGoboEdits" } + { "ai:lightFilter:filter", "ArnoldLightBlockerFilterEdits" }, + { "ai:lightFilter:barndoor", "ArnoldBarndoorFilterEdits" }, + { "ai:lightFilter:light_decay", "ArnoldLightDecayFilterEdits" }, + { "ai:lightFilter:gobo", "ArnoldGoboFilterEdits" } }; string parameterProcessorName( const std::string &attribute ) From 4c24e46dfbff7e7f7d301c83435382e4d42fd838 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Fri, 21 Jun 2024 11:13:29 -0400 Subject: [PATCH 08/10] AttributeInspector : Add `LightFilter` as source --- .../AttributeInspectorTest.py | 29 +++++++++++++++++++ src/GafferScene/EditScopeAlgo.cpp | 3 +- src/GafferSceneUI/AttributeInspector.cpp | 6 ++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/python/GafferSceneUITest/AttributeInspectorTest.py b/python/GafferSceneUITest/AttributeInspectorTest.py index d230430d71d..5b8c587e836 100644 --- a/python/GafferSceneUITest/AttributeInspectorTest.py +++ b/python/GafferSceneUITest/AttributeInspectorTest.py @@ -736,5 +736,34 @@ def testDontEditParentOfInspectedLocation( self ) : self.assertEqual( row["name"].getValue(), "/parent/child" ) + def testLightFilter( self ) : + + lightFilter = GafferSceneTest.TestLightFilter() + + editScope = Gaffer.EditScope() + editScope.setup( lightFilter["out"] ) + editScope["in"].setInput( lightFilter["out"] ) + + self.__assertExpectedResult( + self.__inspect( editScope["out"], "/lightFilter", "filteredLights" ), + source = lightFilter["filteredLights"], + sourceType = GafferSceneUI.Private.Inspector.Result.SourceType.Other, + editable = True, + edit = lightFilter["filteredLights"] + ) + + inspection = self.__inspect( editScope["out"], "/lightFilter", "filteredLights", editScope ) + edit = inspection.acquireEdit() + edit["enabled"].setValue( True ) + + self.__assertExpectedResult( + self.__inspect( editScope["out"], "/lightFilter", "filteredLights", editScope ), + source = edit, + sourceType = GafferSceneUI.Private.Inspector.Result.SourceType.EditScope, + editable = True, + edit = edit + ) + + if __name__ == "__main__" : unittest.main() diff --git a/src/GafferScene/EditScopeAlgo.cpp b/src/GafferScene/EditScopeAlgo.cpp index 59cdcb7cc86..03a669d10f5 100644 --- a/src/GafferScene/EditScopeAlgo.cpp +++ b/src/GafferScene/EditScopeAlgo.cpp @@ -94,7 +94,8 @@ CreatableRegistry g_attributeRegistry { { "gl:visualiser:frustum", new IECore::StringData( "whenSelected" ) }, { "gl:light:frustumScale", new IECore::FloatData( 1.0f ) }, { "gl:light:drawingMode", new IECore::StringData( "texture" ) }, - { "light:mute", new IECore::BoolData( false ) } + { "light:mute", new IECore::BoolData( false ) }, + { "filteredLights", new StringData( "" ) } }; /// Entry keys for `g_optionRegistry` should not include the "option:" prefix. diff --git a/src/GafferSceneUI/AttributeInspector.cpp b/src/GafferSceneUI/AttributeInspector.cpp index 828e756e63c..a55524601bc 100644 --- a/src/GafferSceneUI/AttributeInspector.cpp +++ b/src/GafferSceneUI/AttributeInspector.cpp @@ -41,6 +41,7 @@ #include "GafferScene/Camera.h" #include "GafferScene/EditScopeAlgo.h" #include "GafferScene/Light.h" +#include "GafferScene/LightFilter.h" #include "GafferScene/SceneAlgo.h" #include "GafferScene/SceneNode.h" @@ -260,6 +261,11 @@ Gaffer::ValuePlugPtr AttributeInspector::source( const GafferScene::SceneAlgo::H return attributePlug( light->visualiserAttributesPlug(), m_attribute ); } + else if( auto lightFilter = runTimeCast( sceneNode ) ) + { + return lightFilter->filteredLightsPlug(); + } + else if( auto camera = runTimeCast( sceneNode ) ) { return attributePlug( camera->visualiserAttributesPlug(), m_attribute ); From 66ebcde2f2880dc60307ac76f062c12e3261b33e Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Fri, 21 Jun 2024 11:15:23 -0400 Subject: [PATCH 09/10] LightEditor : Add `filteredLights` attribute --- python/GafferArnoldUI/ArnoldShaderUI.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/GafferArnoldUI/ArnoldShaderUI.py b/python/GafferArnoldUI/ArnoldShaderUI.py index 251ddcbde09..2efd0196230 100644 --- a/python/GafferArnoldUI/ArnoldShaderUI.py +++ b/python/GafferArnoldUI/ArnoldShaderUI.py @@ -478,6 +478,9 @@ def addActivator( activator ) : GafferSceneUI.LightEditor.registerParameter( "ai:light", "width", "Shape" ) GafferSceneUI.LightEditor.registerParameter( "ai:light", "height", "Shape" ) +# Manually add the `filteredLights` parameter for `light_blocker` +GafferSceneUI.LightEditor.registerAttribute( "ai:light", "filteredLights", "Blocker" ) + ########################################################################## # Gaffer Metadata queries. These are implemented using the preconstructed # registry above. From b22623dd4691da3284cc9a2f218ac1dfac3f11be Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Fri, 21 Jun 2024 12:56:25 -0400 Subject: [PATCH 10/10] LightEditor : Blockers `Select Affected Objects` --- python/GafferSceneUI/LightEditor.py | 54 ++++++++++++++++++++++++++++ startup/GafferSceneUI/lightFilter.py | 40 +++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 startup/GafferSceneUI/lightFilter.py diff --git a/python/GafferSceneUI/LightEditor.py b/python/GafferSceneUI/LightEditor.py index 83492a684be..bf5ca53edad 100644 --- a/python/GafferSceneUI/LightEditor.py +++ b/python/GafferSceneUI/LightEditor.py @@ -604,6 +604,50 @@ def __removeAttributes( self, pathListing ) : tweak["enabled"].setValue( True ) tweak["mode"].setValue( Gaffer.TweakPlug.Mode.Remove ) + def __selectedSetExpressions( self, pathListing ) : + + # A dictionary of the form : + # { light1 : set( setExpression1, setExpression2 ), light2 : set( setExpression1 ), ... } + result = {} + + lightPath = pathListing.getPath().copy() + for columnSelection, column in zip( pathListing.getSelection(), pathListing.getColumns() ) : + if ( + not columnSelection.isEmpty() and ( + not isinstance( column, _GafferSceneUI._LightEditorInspectorColumn ) or + not ( + Gaffer.Metadata.value( "attribute:" + column.inspector().name(), "ui:scene:acceptsSetName" ) or + Gaffer.Metadata.value( "attribute:" + column.inspector().name(), "ui:scene:acceptsSetNames" ) or + Gaffer.Metadata.value( "attribute:" + column.inspector().name(), "ui:scene:acceptsSetExpression" ) + ) + ) + ) : + # We only return set expressions if all selected paths are in + # columns that accept set names or set expressions. + return {} + + for path in columnSelection.paths() : + lightPath.setFromString( path ) + cellValue = column.cellData( lightPath ).value + if cellValue is not None : + result.setdefault( path, set() ).add( cellValue ) + else : + # We only return set expressions if all selected paths are render passes. + return {} + + return result + + def __selectAffected( self, pathListing ) : + + result = IECore.PathMatcher() + + with Gaffer.Context( self.getContext() ) as context : + for light, setExpressions in self.__selectedSetExpressions( pathListing ).items() : + for setExpression in setExpressions : + result.addPaths( GafferScene.SetAlgo.evaluateSetExpression( setExpression, self.__settingsNode["in"] ) ) + + GafferSceneUI.ContextAlgo.setSelectedPaths( self.getContext(), result ) + def __buttonPress( self, pathListing, event ) : if event.button != event.Buttons.Right or event.modifiers != event.Modifiers.None_ : @@ -709,6 +753,16 @@ def __buttonPress( self, pathListing, event ) : "shortCut" : "Backspace, Delete", } ) + if len( self.__selectedSetExpressions( pathListing ) ) > 0 : + menuDefinition.append( + "SelectAffectedObjectsDivider", { "divider" : True } + ) + menuDefinition.append( + "Select Affected Objects", + { + "command" : functools.partial( self.__selectAffected, pathListing ), + } + ) self.__contextMenu = GafferUI.Menu( menuDefinition ) self.__contextMenu.popup( pathListing ) diff --git a/startup/GafferSceneUI/lightFilter.py b/startup/GafferSceneUI/lightFilter.py new file mode 100644 index 00000000000..b83b32687b4 --- /dev/null +++ b/startup/GafferSceneUI/lightFilter.py @@ -0,0 +1,40 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferSceneUI + +Gaffer.Metadata.registerValue( "attribute:filteredLights", "ui:scene:acceptsSetExpression", True )