diff --git a/Changes.md b/Changes.md index 5df564c9e67..7d7fb965227 100644 --- a/Changes.md +++ b/Changes.md @@ -1,5 +1,11 @@ 0.61.x.x (relative to 0.61.11.0) -========= +======== + +Improvements +------------ + +- StringPlug : Added support for input connections from StringVectorDataPlugs. The string value is formed by joining the string array using spaces. +- StringVectorDataPlug : Added support for input connections from StringPlugs. The array value is formed by splitting the string on spaces. Improvements ------------ diff --git a/python/GafferTest/StringPlugTest.py b/python/GafferTest/StringPlugTest.py index 34f1157efa4..ff2a1a7ebf8 100644 --- a/python/GafferTest/StringPlugTest.py +++ b/python/GafferTest/StringPlugTest.py @@ -377,5 +377,31 @@ def testHashUsesValue( self ) : for i in range( 10, 20 ) : self.assertEqual( hashes[i], hashes[10] ) + def testStringVectorDataInput( self ) : + + node = Gaffer.Node() + node["user"]["string"] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + node["user"]["stringVector"] = Gaffer.StringVectorDataPlug( defaultValue = IECore.StringVectorData(), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + + self.assertTrue( node["user"]["string"].acceptsInput( node["user"]["stringVector"] ) ) + node["user"]["string"].setInput( node["user"]["stringVector"] ) + + hashes = set() + for input, output in [ + ( [], "", ), + ( [ "test" ], "test" ), + ( [ "a", "b", "c" ], "a b c" ), + ( [ "dog", "cat" ], "dog cat" ), + ( [ "a", "b", "", "c" ], "a b c" ), + ] : + + node["user"]["stringVector"].setValue( IECore.StringVectorData( input ) ) + + h = node["user"]["string"].hash() + self.assertNotIn( h, hashes ) + hashes.add( h ) + + self.assertEqual( node["user"]["string"].getValue(), output ) + if __name__ == "__main__": unittest.main() diff --git a/python/GafferTest/TypedObjectPlugTest.py b/python/GafferTest/TypedObjectPlugTest.py index 923845c8878..24703b5969e 100644 --- a/python/GafferTest/TypedObjectPlugTest.py +++ b/python/GafferTest/TypedObjectPlugTest.py @@ -335,5 +335,31 @@ def testRepr( self ) : plug2 = eval( repr( plug ) ) self.assertEqual( plug2.defaultValue(), plug.defaultValue() ) + def testStringVectorDataPlugWithStringInput( self ) : + + node = Gaffer.Node() + node["user"]["string"] = Gaffer.StringPlug( flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + node["user"]["stringVector"] = Gaffer.StringVectorDataPlug( defaultValue = IECore.StringVectorData(), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic ) + + self.assertTrue( node["user"]["stringVector"].acceptsInput( node["user"]["string"] ) ) + node["user"]["stringVector"].setInput( node["user"]["string"] ) + + hashes = set() + for input, output in [ + ( "", [] ), + ( "test", [ "test" ] ), + ( "a b c", [ "a", "b", "c" ] ), + ( "dog cat", [ "dog", "cat" ] ), + ( "a b c", [ "a", "b", "", "c" ] ) + ] : + + node["user"]["string"].setValue( input ) + + h = node["user"]["stringVector"].hash() + self.assertNotIn( h, hashes ) + hashes.add( h ) + + self.assertEqual( node["user"]["stringVector"].getValue(), IECore.StringVectorData( output ) ) + if __name__ == "__main__": unittest.main() diff --git a/src/Gaffer/StringPlug.cpp b/src/Gaffer/StringPlug.cpp index e60da638c74..457c0764bbd 100644 --- a/src/Gaffer/StringPlug.cpp +++ b/src/Gaffer/StringPlug.cpp @@ -39,6 +39,9 @@ #include "Gaffer/Context.h" #include "Gaffer/Process.h" +#include "Gaffer/TypedObjectPlug.h" + +#include "boost/algorithm/string/join.hpp" using namespace IECore; using namespace Gaffer; @@ -73,7 +76,7 @@ bool StringPlug::acceptsInput( const Plug *input ) const } if( input ) { - return input->isInstanceOf( staticTypeId() ); + return input->isInstanceOf( staticTypeId() ) || input->isInstanceOf( StringVectorDataPlug::staticTypeId() ); } return true; } @@ -109,10 +112,14 @@ std::string StringPlug::getValue( const IECore::MurmurHash *precomputedHash ) co void StringPlug::setFrom( const ValuePlug *other ) { - const StringPlug *tOther = IECore::runTimeCast( other ); - if( tOther ) + if( auto stringPlug = IECore::runTimeCast( other ) ) + { + setValue( stringPlug->getValue() ); + } + else if( auto stringVectorPlug = IECore::runTimeCast( other ) ) { - setValue( tOther->getValue() ); + ConstStringVectorDataPtr data = stringVectorPlug->getValue(); + setValue( boost::algorithm::join( data->readable(), " " ) ); } else { diff --git a/src/Gaffer/TypedObjectPlug.cpp b/src/Gaffer/TypedObjectPlug.cpp index 6ce75569e3a..70a0feb2e1d 100644 --- a/src/Gaffer/TypedObjectPlug.cpp +++ b/src/Gaffer/TypedObjectPlug.cpp @@ -37,8 +37,12 @@ #include "Gaffer/TypedObjectPlug.h" +#include "Gaffer/StringPlug.h" #include "Gaffer/TypedObjectPlug.inl" +#include "boost/algorithm/string/classification.hpp" +#include "boost/algorithm/string/split.hpp" + namespace Gaffer { @@ -98,6 +102,49 @@ void CompoundObjectPlug::setFrom( const ValuePlug *other ) } } +// Specialise StringVectorDataPlug to accept connections from StringPlug + +template<> +bool StringVectorDataPlug::acceptsInput( const Plug *input ) const +{ + if( !ValuePlug::acceptsInput( input ) ) + { + return false; + } + + if( input ) + { + return + input->isInstanceOf( staticTypeId() ) || + input->isInstanceOf( StringPlug::staticTypeId() ) + ; + } + return true; +} + +template<> +void StringVectorDataPlug::setFrom( const ValuePlug *other ) +{ + if( auto stringVectorPlug = IECore::runTimeCast( other ) ) + { + setValue( stringVectorPlug->getValue() ); + } + else if( auto stringPlug = IECore::runTimeCast( other ) ) + { + IECore::StringVectorDataPtr value = new IECore::StringVectorData; + std::string s = stringPlug->getValue(); + if( !s.empty() ) + { + boost::split( value->writable(), s, boost::is_any_of( " " ) ); + } + setValue( value ); + } + else + { + throw IECore::Exception( "Unsupported plug type" ); + } +} + // explicit instantiation template class TypedObjectPlug; template class TypedObjectPlug;