From 147645b146b3171a5563d6075e3243fae5aaf6d6 Mon Sep 17 00:00:00 2001 From: John Haddon Date: Fri, 8 Dec 2023 15:46:37 +0000 Subject: [PATCH] Switch : Add `connectedInputs` plug Fixes #1797. --- Changes.md | 1 + include/Gaffer/Switch.h | 4 ++ python/GafferImageTest/ImageSwitchTest.py | 3 +- python/GafferSceneTest/SceneSwitchTest.py | 3 +- python/GafferTest/NameSwitchTest.py | 40 ++++++++++++ python/GafferTest/SwitchTest.py | 44 +++++++++++++- python/GafferUI/NameSwitchUI.py | 6 ++ python/GafferUI/SwitchUI.py | 37 ++++++++++++ src/Gaffer/Switch.cpp | 74 +++++++++++++++++++++++ 9 files changed, 206 insertions(+), 6 deletions(-) diff --git a/Changes.md b/Changes.md index e1496e74559..5411be1c2fc 100644 --- a/Changes.md +++ b/Changes.md @@ -30,6 +30,7 @@ Improvements - NodeEditor : Improved image channel selectors : - Added "Custom" option, to allow strings to be entered manually. - Added right-click context menu. +- Switch : Added `connectedInputs` output plug. Fixes ----- diff --git a/include/Gaffer/Switch.h b/include/Gaffer/Switch.h index 50ba8622306..a9ab5c0d361 100644 --- a/include/Gaffer/Switch.h +++ b/include/Gaffer/Switch.h @@ -39,6 +39,7 @@ #include "Gaffer/ArrayPlug.h" #include "Gaffer/ComputeNode.h" #include "Gaffer/NumericPlug.h" +#include "Gaffer/TypedObjectPlug.h" namespace Gaffer { @@ -87,6 +88,9 @@ class GAFFER_API Switch : public ComputeNode BoolPlug *enabledPlug() override; const BoolPlug *enabledPlug() const override; + IntVectorDataPlug *connectedInputsPlug(); + const IntVectorDataPlug *connectedInputsPlug() const; + Plug *correspondingInput( const Plug *output ) override; const Plug *correspondingInput( const Plug *output ) const override; diff --git a/python/GafferImageTest/ImageSwitchTest.py b/python/GafferImageTest/ImageSwitchTest.py index f1aab03fd47..134ca25e7ea 100644 --- a/python/GafferImageTest/ImageSwitchTest.py +++ b/python/GafferImageTest/ImageSwitchTest.py @@ -71,8 +71,7 @@ def testAffects( self ) : for p in [ switch["in"][0], switch["in"][1] ] : for n in [ "format", "dataWindow", "metadata", "deep", "sampleOffsets", "channelNames", "channelData" ] : a = switch.affects( p[n] ) - self.assertEqual( len( a ), 1 ) - self.assertTrue( a[0].isSame( switch["out"][n] ) ) + self.assertEqual( a, [ switch["out"][n], switch["connectedInputs"] ] ) a = set( [ plug.relativeName( plug.node() ) for plug in switch.affects( switch["enabled"] ) ] ) self.assertEqual( diff --git a/python/GafferSceneTest/SceneSwitchTest.py b/python/GafferSceneTest/SceneSwitchTest.py index a44a7e33ebb..f1f59eec6c4 100644 --- a/python/GafferSceneTest/SceneSwitchTest.py +++ b/python/GafferSceneTest/SceneSwitchTest.py @@ -73,8 +73,7 @@ def testAffects( self ) : for p in [ switch["in"][0], switch["in"][1] ] : for n in p.keys() : a = switch.affects( p[n] ) - self.assertEqual( len( a ), 1 ) - self.assertTrue( a[0].isSame( switch["out"][n] ) ) + self.assertEqual( a, [ switch["out"][n], switch["connectedInputs"] ] ) a = set( switch.affects( switch["enabled"] ) ) self.assertEqual( a, set( switch["out"].children() ) ) diff --git a/python/GafferTest/NameSwitchTest.py b/python/GafferTest/NameSwitchTest.py index d244376c3f5..8c6fd058ce7 100644 --- a/python/GafferTest/NameSwitchTest.py +++ b/python/GafferTest/NameSwitchTest.py @@ -195,5 +195,45 @@ def testUnnamedRowsNeverMatch( self ) : del c["selector"] self.assertEqual( s["out"]["value"].getValue(), 1 ) + def testConnectedInputs( self ) : + + switch = Gaffer.NameSwitch() + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData() ) + + switch.setup( Gaffer.IntPlug() ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData() ) + + inputA = Gaffer.IntPlug() + switch["in"][0]["value"].setInput( inputA ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData( [ 0 ] ) ) + + switch["in"][1]["value"].setInput( inputA ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData( [ 0, 1 ] ) ) + + switch["in"][0]["value"].setInput( None ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData( [ 1 ] ) ) + + def testConnectedInputsWithPromotedInPlug( self ) : + + box = Gaffer.Box() + box["switch"] = Gaffer.NameSwitch() + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData() ) + + box["switch"].setup( Gaffer.IntPlug() ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData() ) + + Gaffer.PlugAlgo.promote( box["switch"]["in"] ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData() ) + + inputA = Gaffer.IntPlug() + box["in"][0]["value"].setInput( inputA ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData( [ 0 ] ) ) + + box["in"][1]["value"].setInput( inputA ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData( [ 0, 1 ] ) ) + + box["in"][0]["value"].setInput( None ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData( [ 1 ] ) ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferTest/SwitchTest.py b/python/GafferTest/SwitchTest.py index 9e14bdc7974..29932629205 100644 --- a/python/GafferTest/SwitchTest.py +++ b/python/GafferTest/SwitchTest.py @@ -141,14 +141,14 @@ def testAffects( self ) : # not affect the output (dependency is carried by # the connection instead). for plug in [ n["in"][0], n["in"][1] ] : - self.assertEqual( n.affects( plug ), [] ) + self.assertEqual( n.affects( plug ), [ n["connectedInputs"] ] ) # Now the index is computed, the dependencies # must be declared. a = GafferTest.AddNode() n["index"].setInput( a["sum"] ) for plug in [ n["in"][0], n["in"][1] ] : - self.assertEqual( n.affects( plug ), [ n["out"] ] ) + self.assertEqual( n.affects( plug ), [ n["out"], n["connectedInputs"] ] ) self.assertEqual( n.affects( n["out"] ), [] ) @@ -562,6 +562,46 @@ def testInternalConnectionWithTypeConversionAndCanceller( self ) : boolPlug.setValue( index ) self.assertTrue( switch["out"].getInput().isSame( switch["in"][index] ) ) + def testConnectedInputs( self ) : + + switch = Gaffer.Switch() + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData() ) + + switch.setup( Gaffer.IntPlug() ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData() ) + + inputA = Gaffer.IntPlug() + switch["in"][0].setInput( inputA ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData( [ 0 ] ) ) + + switch["in"][1].setInput( inputA ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData( [ 0, 1 ] ) ) + + switch["in"][0].setInput( None ) + self.assertEqual( switch["connectedInputs"].getValue(), IECore.IntVectorData( [ 1 ] ) ) + + def testConnectedInputsWithPromotedInPlug( self ) : + + box = Gaffer.Box() + box["switch"] = Gaffer.Switch() + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData() ) + + box["switch"].setup( Gaffer.IntPlug() ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData() ) + + Gaffer.PlugAlgo.promote( box["switch"]["in"] ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData() ) + + inputA = Gaffer.IntPlug() + box["in"][0].setInput( inputA ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData( [ 0 ] ) ) + + box["in"][1].setInput( inputA ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData( [ 0, 1 ] ) ) + + box["in"][0].setInput( None ) + self.assertEqual( box["switch"]["connectedInputs"].getValue(), IECore.IntVectorData( [ 1 ] ) ) + def setUp( self ) : GafferTest.TestCase.setUp( self ) diff --git a/python/GafferUI/NameSwitchUI.py b/python/GafferUI/NameSwitchUI.py index e47cd76f7b6..4a87f2621b0 100644 --- a/python/GafferUI/NameSwitchUI.py +++ b/python/GafferUI/NameSwitchUI.py @@ -152,6 +152,12 @@ ], + "connectedInputs" : [ + + "layout:index", -3, + + ], + } ) diff --git a/python/GafferUI/SwitchUI.py b/python/GafferUI/SwitchUI.py index 60ea3251ff9..12c71fc19e6 100644 --- a/python/GafferUI/SwitchUI.py +++ b/python/GafferUI/SwitchUI.py @@ -35,6 +35,9 @@ ########################################################################## import Gaffer +import GafferUI + +from GafferUI.PlugValueWidget import sole Gaffer.Metadata.registerNode( @@ -98,6 +101,40 @@ ], + "connectedInputs" : [ + + "description", + """ + The indices of the input array that have incoming connections. + + > Tip : This can be used to drive a Wedge or Collect node so that + > they operate over each input in turn. + """, + + "nodule:type", "", + "layout:section", "Advanced", + "plugValueWidget:type", "GafferUI.SwitchUI._ConnectedInputsPlugValueWidget", + + ], + } ) + +class _ConnectedInputsPlugValueWidget( GafferUI.PlugValueWidget ) : + + def __init__( self, plugs, **kw ) : + + self.__textWidget = GafferUI.TextWidget( editable = False ) + GafferUI.PlugValueWidget.__init__( self, self.__textWidget, plugs, **kw ) + + def _updateFromValues( self, values, exception ) : + + value = sole( values ) + self.__textWidget.setText( + ", ".join( [ str( x ) for x in value ] ) + if value is not None + else "---" + ) + + self.__textWidget.setErrored( exception is not None ) diff --git a/src/Gaffer/Switch.cpp b/src/Gaffer/Switch.cpp index f2d874e5c13..852f418d55d 100644 --- a/src/Gaffer/Switch.cpp +++ b/src/Gaffer/Switch.cpp @@ -52,6 +52,36 @@ using namespace Gaffer; namespace { +bool connectedIndividually( const ArrayPlug *array, size_t childIndex ) +{ + const Plug *child = array->getChild( childIndex ); + auto nameValuePlug = IECore::runTimeCast( child ); + if( nameValuePlug ) + { + // For NameSwitch, we only check connections to the `value` + // plug, because typically the `name` part isn't connected. + child = nameValuePlug->valuePlug(); + if( !child ) + { + return false; + } + } + + const Plug *source = child->source(); + if( source == child ) + { + // No input. + return false; + } + + // We do have an input connection, but it might just be + // that the entire input array was promoted. We don't consider + // an individual child to have a connection until it differs + // from the array's. + + return !array->source()->isAncestorOf( source ); +} + const IECore::InternedString g_inPlugsName( "in" ); const IECore::InternedString g_outPlugName( "out" ); @@ -70,6 +100,7 @@ Switch::Switch( const std::string &name) addChild( new IntPlug( "index", Gaffer::Plug::In, 0, 0 ) ); addChild( new BoolPlug( "enabled", Gaffer::Plug::In, true ) ); + addChild( new IntVectorDataPlug( "connectedInputs", Plug::Out ) ); childAddedSignal().connect( boost::bind( &Switch::childAdded, this, ::_2 ) ); plugSetSignal().connect( boost::bind( &Switch::plugSet, this, ::_1 ) ); @@ -169,6 +200,16 @@ const BoolPlug *Switch::enabledPlug() const return getChild( g_firstPlugIndex + 1 ); } +IntVectorDataPlug *Switch::connectedInputsPlug() +{ + return getChild( g_firstPlugIndex + 2 ); +} + +const IntVectorDataPlug *Switch::connectedInputsPlug() const +{ + return getChild( g_firstPlugIndex + 2 ); +} + void Switch::affects( const Plug *input, DependencyNode::AffectedPlugsContainer &outputs ) const { ComputeNode::affects( input, outputs ); @@ -204,6 +245,7 @@ void Switch::affects( const Plug *input, DependencyNode::AffectedPlugsContainer { outputs.push_back( output ); } + outputs.push_back( connectedInputsPlug() ); } } } @@ -275,6 +317,21 @@ void Switch::hash( const ValuePlug *output, const Context *context, IECore::Murm h = input->hash(); return; } + else if( output == connectedInputsPlug() ) + { + ComputeNode::hash( output, context, h ); + if( auto in = inPlugs() ) + { + for( int i = 0, s = in->children().size(); i < s; ++i ) + { + if( connectedIndividually( in, i ) ) + { + h.append( i ); + } + } + } + return; + } ComputeNode::hash( output, context, h ); } @@ -286,6 +343,23 @@ void Switch::compute( ValuePlug *output, const Context *context ) const output->setFrom( input ); return; } + else if( output == connectedInputsPlug() ) + { + IECore::IntVectorDataPtr resultData = new IECore::IntVectorData; + std::vector &result = resultData->writable(); + if( auto in = inPlugs() ) + { + for( int i = 0, s = in->children().size(); i < s; ++i ) + { + if( connectedIndividually( in, i ) ) + { + result.push_back( i ); + } + } + } + static_cast( output )->setValue( resultData ); + return; + } ComputeNode::compute( output, context ); }