Skip to content

Commit

Permalink
Merge branch '1.5_maintenance'
Browse files Browse the repository at this point in the history
  • Loading branch information
johnhaddon committed Jan 23, 2025
2 parents 0863f5b + 8b7c846 commit 40ce482
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 56 deletions.
12 changes: 12 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ Breaking Changes
1.5.x.x (relative to 1.5.3.0)
=======

Improvements
------------

- AttributeEditor : Added "Select Affected Objects" menu item to the "Linked Lights" and Arnold "Shadow Group" columns.

Fixes
-----

- AttributeEditor : Fixed display of fallback value for `linkedLights` attribute.
- AttributeEditor, LightEditor, RenderPassEditor :
- Fixed bugs which prevented edits being made in "Source" scope when there was a downstream edit in an EditScope (#6172).
- Fixed warning messages when attempting to disable a non-existent edit.
- Fixed warning message which referred to "None" rather than the "Source" scope.

API
---

- RenderPassEditor : Added optional `index` argument to `registerOption()` and `registerColumn()`. This can be used to specify the column's position in the UI.
- Metadata : Added `targetsWithMetadata()` function, returning all the string targets which match a pattern and have a specific metadata key.

1.5.3.0 (relative to 1.5.2.0)
=======

Expand Down
4 changes: 4 additions & 0 deletions include/Gaffer/Metadata.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,10 @@ class GAFFER_API Metadata
/// Utilities
/// =========

/// Returns the names of all matching string targets with the specified
/// metadata key.
static std::vector<IECore::InternedString> targetsWithMetadata( const IECore::StringAlgo::MatchPattern &targetPattern, IECore::InternedString key );

/// Lists all node descendants of "root" with the specified metadata key.
/// If instanceOnly is true the search is restricted to instance metadata.
static std::vector<Node*> nodesWithMetadata( GraphComponent *root, IECore::InternedString key, bool instanceOnly = false );
Expand Down
30 changes: 24 additions & 6 deletions python/GafferSceneUI/RenderPassEditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def __init__( self, scriptNode, **kw ) :
__columnRegistry = collections.OrderedDict()

@classmethod
def registerOption( cls, groupKey, optionName, section = "Main", columnName = None ) :
def registerOption( cls, groupKey, optionName, section = "Main", columnName = None, index = None ) :

optionLabel = Gaffer.Metadata.value( "option:" + optionName, "label" )
if not columnName :
Expand All @@ -173,20 +173,21 @@ def registerOption( cls, groupKey, optionName, section = "Main", columnName = No
columnName,
toolTip
),
section
section,
index
)

# Registers a column in the Render Pass Editor.
# `inspectorFunction` is a callable object of the form
# `inspectorFunction( scene, editScope )` returning a
# `GafferSceneUI.Private.InspectorColumn` object.
@classmethod
def registerColumn( cls, groupKey, columnKey, inspectorFunction, section = "Main" ) :
def registerColumn( cls, groupKey, columnKey, inspectorFunction, section = "Main", index = None ) :

sections = cls.__columnRegistry.setdefault( groupKey, collections.OrderedDict() )
section = sections.setdefault( section, collections.OrderedDict() )

section[columnKey] = inspectorFunction
section[columnKey] = ( inspectorFunction, index )

@classmethod
def deregisterColumn( cls, groupKey, columnKey, section = "Main" ) :
Expand Down Expand Up @@ -287,9 +288,26 @@ def __updateColumns( self ) :
for groupKey, sections in self.__columnRegistry.items() :
if IECore.StringAlgo.match( tabGroup, groupKey ) :
section = sections.get( currentSection or None, {} )
sectionColumns += [ c( self.settings()["in"], self.settings()["editScope"] ) for c in section.values() ]
sectionColumns += [ ( c( self.settings()["in"], self.settings()["editScope"] ), index ) for ( c, index ) in section.values() ]

self.__pathListing.setColumns( [ self.__renderPassNameColumn, self.__renderPassActiveColumn ] + sectionColumns )
self.__pathListing.setColumns( [ self.__renderPassNameColumn, self.__renderPassActiveColumn ] + self.__orderedColumns( sectionColumns ) )

@staticmethod
def __orderedColumns( columnsAndIndices ) :

for i, ( column, index ) in enumerate( columnsAndIndices ) :
if index is not None :
# Negative indices are remapped to their absolute position in the column list.
columnsAndIndices[i] = ( column, index if index >= 0 else len( columnsAndIndices ) + index )

# As column indices may be sparse, we fill in the gaps with any unspecified indices before sorting.
availableIndices = iter( sorted( set( range( len( columnsAndIndices ) ) ) - { x[1] for x in columnsAndIndices } ) )
orderedColumns = sorted(
[ ( column, index if index is not None else next( availableIndices ) ) for column, index in columnsAndIndices ],
key = lambda x: x[1]
)

return [ x[0] for x in orderedColumns ]

@GafferUI.LazyMethod( deferUntilPlaybackStops = True )
def __setPathListingPath( self ) :
Expand Down
109 changes: 109 additions & 0 deletions python/GafferSceneUITest/RenderPassEditorTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,112 @@ def testFn( name ) :
self.assertEqual( [ str( c ) for c in path.children() ], [ "/A/A_A", "/A/A_B" ] )
path.setFromString( "/B" )
self.assertEqual( [ str( c ) for c in path.children() ], [ "/B/B_C", "/B/B_D" ] )

def testRegisterOption( self ) :

for columnName in [ "A", "B", "C", "D", "E" ] :
GafferSceneUI.RenderPassEditor.registerOption( "*", columnName, "Test" )
self.addCleanup( GafferSceneUI.RenderPassEditor.deregisterColumn, "*", columnName, "Test" )

script = Gaffer.ScriptNode()

editor = GafferSceneUI.RenderPassEditor( script )
editor.settings()["section"].setValue( "Test" )

GafferSceneUI.RenderPassEditor._RenderPassEditor__updateColumns.flush( editor )

pathListing = editor._RenderPassEditor__pathListing

columnNames = [ c.headerData().value for c in pathListing.getColumns() ]
for columnName in [ "A", "B", "C", "D", "E" ] :
self.assertIn( columnName, columnNames )

GafferSceneUI.RenderPassEditor.deregisterColumn( "*", "B", "Test" )

editor._RenderPassEditor__updateColumns()
GafferSceneUI.RenderPassEditor._RenderPassEditor__updateColumns.flush( editor )

columnNames = [ c.headerData().value for c in pathListing.getColumns() ]
self.assertNotIn( "B", columnNames )
self.assertIn( "A", columnNames )
self.assertIn( "C", columnNames )
self.assertIn( "D", columnNames )
self.assertIn( "E", columnNames )

def testColumnOrder( self ) :

script = Gaffer.ScriptNode()
editor = GafferSceneUI.RenderPassEditor( script )
editor.settings()["section"].setValue( "Test" )

def assertColumnOrder( columns, order ) :

for columnName, index in columns :
GafferSceneUI.RenderPassEditor.registerOption( "*", columnName, "Test", index = index )
self.addCleanup( GafferSceneUI.RenderPassEditor.deregisterColumn, "*", columnName, "Test" )

editor._RenderPassEditor__updateColumns()
GafferSceneUI.RenderPassEditor._RenderPassEditor__updateColumns.flush( editor )
columnNames = [ c.headerData().value for c in editor._RenderPassEditor__pathListing.getColumns() if isinstance( c, GafferSceneUI.Private.InspectorColumn ) ]
self.assertEqual( columnNames, order )

for columnName, _ in columns :
GafferSceneUI.RenderPassEditor.deregisterColumn( "*", columnName, "Test" )

assertColumnOrder(
[ ( "A", None ), ( "B", None ), ( "C", None ), ( "D", None ), ( "E", None ) ],
[ "A", "B", "C", "D", "E" ]
)

assertColumnOrder(
[ ( "E", None ), ( "D", None ), ( "C", None ), ( "B", None ), ( "A", None ) ],
[ "E", "D", "C", "B", "A" ]
)

assertColumnOrder(
[ ( "A", 0 ), ( "B", 1 ), ( "C", 2 ), ( "D", 3 ), ( "E", 4 ) ],
[ "A", "B", "C", "D", "E" ]
)

assertColumnOrder(
[ ( "A", 4 ), ( "B", 3 ), ( "C", 2 ), ( "D", 1 ), ( "E", 0 ) ],
[ "E", "D", "C", "B", "A" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", None ), ( "C", None ), ( "D", 0 ), ( "E", 2 ) ],
[ "D", "A", "E", "B", "C" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", None ), ( "C", 0 ), ( "D", -1 ), ( "E", None ) ],
[ "C", "A", "B", "E", "D" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", None ), ( "C", -1 ), ( "D", -2 ), ( "E", None ) ],
[ "A", "B", "E", "D", "C" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", -1 ), ( "C", None ), ( "D", -1 ), ( "E", None ) ],
[ "A", "C", "E", "B", "D" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", 0 ), ( "C", 0 ), ( "D", None ), ( "E", 2 ) ],
[ "B", "C", "A", "E", "D" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", 0 ), ( "C", 0 ), ( "D", 0 ), ( "E", 2 ) ],
[ "B", "C", "D", "A", "E" ]
)

assertColumnOrder(
[ ( "A", None ), ( "B", 0 ), ( "C", 0 ), ( "D", 0 ), ( "E", 1 ) ],
[ "B", "C", "D", "E", "A" ]
)

if __name__ == "__main__" :
unittest.main()
17 changes: 17 additions & 0 deletions python/GafferTest/MetadataTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1356,6 +1356,23 @@ def changed( node, key, reason ) :
Gaffer.Metadata.registerValue( node, "test", 2 )
self.assertEqual( Gaffer.Metadata.value( node, "test" ), 1 )

def testTargetsWithMetadata( self ) :

for target, key in [
[ "target1", "k1" ],
[ "target1", "k2" ],
[ "target2", "k2" ],
[ "target2", "k3" ],
[ "target3", "k4" ],
[ "targetA", "k1" ],
] :
Gaffer.Metadata.registerValue( target, key, "test" )
self.addCleanup( Gaffer.Metadata.deregisterValue, target, key )

self.assertEqual( Gaffer.Metadata.targetsWithMetadata( "target[1-3]", "k2" ), [ "target1", "target2" ] )
self.assertEqual( Gaffer.Metadata.targetsWithMetadata( "target*", "k1" ), [ "target1", "targetA" ] )
self.assertEqual( Gaffer.Metadata.targetsWithMetadata( "*", "k3" ), [ "target2" ] )

def tearDown( self ) :

GafferTest.TestCase.tearDown( self )
Expand Down
62 changes: 53 additions & 9 deletions src/Gaffer/Metadata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,17 @@ using Values = multi_index::multi_index_container<
>
>;

using MetadataMap = std::map<IECore::InternedString, Values>;
using NamedValues = std::pair<InternedString, Values>;

using MetadataMap = multi_index::multi_index_container<
NamedValues,
multi_index::indexed_by<
multi_index::ordered_unique<
multi_index::member<NamedValues, InternedString, &NamedValues::first>
>,
multi_index::sequenced<>
>
>;

MetadataMap &metadataMap()
{
Expand Down Expand Up @@ -481,12 +491,23 @@ void Metadata::registerValue( IECore::InternedString target, IECore::InternedStr

void Metadata::registerValue( IECore::InternedString target, IECore::InternedString key, ValueFunction value )
{
NamedValue namedValue( key, value );
auto &m = metadataMap()[target];
auto i = m.insert( namedValue );
if( !i.second )
auto &targetMap = metadataMap();

auto targetIt = targetMap.find( target );
if( targetIt == targetMap.end() )
{
targetIt = targetMap.insert( NamedValues( target, Values() ) ).first;
}

// Cast is safe because we don't use `second` as a key in the `multi_index_container`,
// so we can modify it without affecting indexing.
Values &values = const_cast<Values &>( targetIt->second );

const NamedValue namedValue( key, value );
auto keyIt = values.insert( namedValue );
if( !keyIt.second )
{
m.replace( i.first, namedValue );
values.replace( keyIt.first, namedValue );
}

valueChangedSignal()( target, key );
Expand All @@ -501,13 +522,17 @@ void Metadata::deregisterValue( IECore::InternedString target, IECore::InternedS
return;
}

auto vIt = mIt->second.find( key );
if( vIt == mIt->second.end() )
// Cast is safe because we don't use `second` as a key in the `multi_index_container`,
// so we can modify it without affecting indexing.
Values &values = const_cast<Values &>( mIt->second );

auto vIt = values.find( key );
if( vIt == values.end() )
{
return;
}

mIt->second.erase( vIt );
values.erase( vIt );
valueChangedSignal()( target, key );
}

Expand Down Expand Up @@ -544,6 +569,25 @@ IECore::ConstDataPtr Metadata::valueInternal( IECore::InternedString target, IEC
return nullptr;
}

std::vector<IECore::InternedString> Metadata::targetsWithMetadata( const IECore::StringAlgo::MatchPattern &targetPattern, IECore::InternedString key )
{
vector<InternedString> result;
const auto &orderedIndex = metadataMap().get<1>();
for( const auto &[target, values] : orderedIndex )
{
if( !StringAlgo::match( target.c_str(), targetPattern ) )
{
continue;
}
if( values.find( key ) != values.end() )
{
result.push_back( target );
}
}

return result;
}

void Metadata::registerValue( IECore::TypeId typeId, IECore::InternedString key, IECore::ConstDataPtr value )
{
registerValue( typeId, key, [value]( const GraphComponent * ){ return value; } );
Expand Down
18 changes: 18 additions & 0 deletions src/GafferModule/MetadataBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,16 @@ list registeredGraphComponentValuesDeprecated( const GraphComponent *target, boo
return keysToList( keys );
}

list targetsWithMetadataWrapper( const IECore::StringAlgo::MatchPattern &targetPattern, IECore::InternedString key )
{
std::vector<InternedString> targets;
{
IECorePython::ScopedGILRelease gilRelease;
targets = Metadata::targetsWithMetadata( targetPattern, key );
}
return keysToList( targets );
}

list plugsWithMetadata( GraphComponent *root, const std::string &key, bool instanceOnly )
{
std::vector<Plug*> plugs = Metadata::plugsWithMetadata( root, key, instanceOnly );
Expand Down Expand Up @@ -452,6 +462,14 @@ void GafferModule::bindMetadata()
.def( "plugValueChangedSignal", (Metadata::PlugValueChangedSignal &(*)( Gaffer::Node * ) )&Metadata::plugValueChangedSignal, return_value_policy<reference_existing_object>() )
.staticmethod( "plugValueChangedSignal" )

.def( "targetsWithMetadata", &targetsWithMetadataWrapper,
(
boost::python::arg( "targetPattern" ),
boost::python::arg( "key" )
)
)
.staticmethod( "targetsWithMetadata" )

.def( "plugsWithMetadata", &plugsWithMetadata,
(
boost::python::arg( "root" ),
Expand Down
1 change: 1 addition & 0 deletions src/GafferScene/StandardAttributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ StandardAttributes::StandardAttributes( const std::string &name )

// light linking

/// \todo The default value is wrong - it should be "defaultLights".
attributes->addChild( new Gaffer::NameValuePlug( "linkedLights", new IECore::StringData( "" ), false, "linkedLights" ) );

// light filter linking
Expand Down
1 change: 1 addition & 0 deletions startup/GafferScene/arnoldAttributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
contribute to illumination by default.
""",
)
Gaffer.Metadata.registerValue( "attribute:ai:visibility:shadow_group", "ui:scene:acceptsSetExpression", True )

Gaffer.Metadata.registerValue( "attribute:ai:visibility:diffuse_reflect", "label", "Diffuse Reflection" )
Gaffer.Metadata.registerValue( "attribute:ai:visibility:diffuse_reflect", "defaultValue", IECore.BoolData( True ) )
Expand Down
Loading

0 comments on commit 40ce482

Please sign in to comment.