diff --git a/Changes b/Changes index b6120e17f7..615228f6b4 100644 --- a/Changes +++ b/Changes @@ -1,10 +1,16 @@ 10.6.x.x (relative to 10.6.0.2) ======== +Improvements +------------ + +- ShaderNetworkAlgo : Added support for RenderMan's spline convention in `expandSplines()` and `collapseSplines()`. + Fixes ----- - ConfigLoader : Fixed UnicodeDecodeErrors in non-UTF8 locales. +- USDScene : Fixed identifiers for exported RenderMan shaders, by omitting the `ri:` type prefix. 10.6.0.2 (relative to 10.6.0.1) ======== diff --git a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp index 9f2891ead9..3219f221bf 100644 --- a/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp +++ b/contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp @@ -309,15 +309,33 @@ pxr::UsdShadeConnectableAPI createShaderPrim( const IECoreScene::Shader *shader, { throw IECore::Exception( "Could not create shader at " + path.GetAsString() ); } + const std::string type = shader->getType(); + std::string typePrefix; - size_t typeColonPos = type.find( ":" ); - if( typeColonPos != std::string::npos ) + if( boost::starts_with( shader->getName(), "Pxr" ) || boost::starts_with( shader->getName(), "Lama" ) ) { - typePrefix = type.substr( 0, typeColonPos ) + ":"; - if( typePrefix == "ai:" ) + // Leave the type prefix empty. This should be the default, but we are currently only doing this + // for a small number of shaders that we can be completely confident require it, in order to + // preserve backwards compatibility. + } + else + { + size_t typeColonPos = type.find( ":" ); + if( typeColonPos != std::string::npos ) { - typePrefix = "arnold:"; + // According to our current understanding, this is almost completely wrong. Renderer's like + // PRMan won't accept shaders with type prefixes, and Arnold apparently requires all shaders + // to be prefixed with "arnold:", including OSL. This code prefixes OSL shaders with "osl:", + // which fails in all renderers we're aware of - we're keeping this behaviour for now for + // backwards compatibility reasons. + typePrefix = type.substr( 0, typeColonPos ) + ":"; + + // This is the one case that actually works + if( typePrefix == "ai:" ) + { + typePrefix = "arnold:"; + } } } usdShader.SetShaderId( pxr::TfToken( typePrefix + shader->getName() ) ); diff --git a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py index 8c08fcfe7d..3c0f6f8c27 100644 --- a/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py +++ b/contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py @@ -3055,6 +3055,13 @@ def testShaders( self ) : ) ) manualComponentNetwork.setOutput( IECoreScene.ShaderNetwork.Parameter( "dest", "" ) ) + + rmanSurface = IECoreScene.Shader( "PxrFoo", "osl:surface", surface.parameters ) + rmanNetwork = IECoreScene.ShaderNetwork() + rmanNetwork.addShader( "out", rmanSurface ) + rmanNetwork.setOutput( IECoreScene.ShaderNetwork.Parameter( "out", "" ) ) + + writerRoot = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write ) shaderLocation = writerRoot.createChild( "shaderLocation" ) shaderLocation.writeAttribute( "ai:surface", oneShaderNetwork, 0 ) @@ -3063,6 +3070,7 @@ def testShaders( self ) : shaderLocation.writeAttribute( "complex:surface", complexNetwork, 0 ) shaderLocation.writeAttribute( "componentConnection:surface", componentConnectionNetwork, 0 ) shaderLocation.writeAttribute( "manualComponent:surface", manualComponentNetwork, 0 ) + shaderLocation.writeAttribute( "ri:surface", rmanNetwork, 0 ) shaderLocation.writeAttribute( "volume", oneShaderNetwork, 0 ) # USD supports shaders without a prefix @@ -3121,11 +3129,26 @@ def testShaders( self ) : self.assertEqual( textureUsd.GetInput( "filename" ).Get(), "sometexture.tx" ) + + + riShaderSource = mat.GetOutput( "ri:surface" ).GetConnectedSource() + self.assertEqual( riShaderSource[1], "DEFAULT_OUTPUT" ) + riShaderUsd = pxr.UsdShade.Shader( riShaderSource[0].GetPrim() ) + self.assertEqual( riShaderUsd.GetShaderId(), "PxrFoo" ) + self.assertEqual( riShaderUsd.GetInput( "c" ).Get(), "42" ) + self.assertEqual( riShaderUsd.GetInput( "a" ).Get(), 42.0 ) + self.assertEqual( riShaderUsd.GetInput( "g" ).Get(), 5 ) + self.assertEqual( riShaderUsd.GetInput( "g_Knots" ).Get(), pxr.Vt.FloatArray( [0, 0, 10, 20, 20] ) ) + self.assertEqual( riShaderUsd.GetInput( "g_Colors" ).Get(), pxr.Vt.Vec3fArray( + [pxr.Gf.Vec3f( 1 ), pxr.Gf.Vec3f( 1 ), pxr.Gf.Vec3f( 2 ), pxr.Gf.Vec3f( 0 ), pxr.Gf.Vec3f( 0 )] + ) ) + self.assertEqual( riShaderUsd.GetInput( "g_Interpolation" ).Get(), 'linear' ) + # Read via SceneInterface, and check that we've round-tripped successfully. root = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Read ) - self.assertEqual( set( root.child( "shaderLocation" ).attributeNames() ), set( ['ai:disp_map', 'ai:surface', 'complex:surface', 'volume', 'componentConnection:surface', 'manualComponent:surface' ] ) ) + self.assertEqual( set( root.child( "shaderLocation" ).attributeNames() ), set( ['ai:disp_map', 'ai:surface', 'complex:surface', 'volume', 'componentConnection:surface', 'manualComponent:surface', "ri:surface" ] ) ) self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ai:surface", 0 ).outputShader().parameters, oneShaderNetwork.outputShader().parameters ) self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ai:surface", 0 ).outputShader(), oneShaderNetwork.outputShader() ) @@ -3150,6 +3173,13 @@ def testShaders( self ) : self.assertEqual( root.child( "shaderLocation" ).readAttribute( "componentConnection:surface", 0 ), componentConnectionNetwork ) self.assertEqual( root.child( "shaderLocation" ).readAttribute( "manualComponent:surface", 0 ), manualComponentNetwork ) + rmanSurfaceWithoutTypePrefix = IECoreScene.Shader( "PxrFoo", "surface", surface.parameters ) + rmanNetworkWithoutTypePrefix = IECoreScene.ShaderNetwork() + rmanNetworkWithoutTypePrefix.addShader( "out", rmanSurfaceWithoutTypePrefix ) + rmanNetworkWithoutTypePrefix.setOutput( IECoreScene.ShaderNetwork.Parameter( "out", "" ) ) + + self.assertEqual( root.child( "shaderLocation" ).readAttribute( "ri:surface", 0 ), rmanNetworkWithoutTypePrefix ) + def testManyShaders( self ) : # Write shaders diff --git a/include/IECoreScene/ShaderNetworkAlgo.h b/include/IECoreScene/ShaderNetworkAlgo.h index e7e7ef445c..0e8998ad5a 100644 --- a/include/IECoreScene/ShaderNetworkAlgo.h +++ b/include/IECoreScene/ShaderNetworkAlgo.h @@ -148,10 +148,12 @@ IECORESCENE_API void collapseSplines( ShaderNetwork *network, std::string target IECORESCENE_API void expandSplines( ShaderNetwork *network, std::string targetPrefix = "" ); -/// \deprecated: Use collapseSplines on the whole network, which can handle input connections +/// \deprecated: Use collapseSplines on the whole network, which can handle input connections, and supports +/// different spline conventions for different renderers' shader libraries IECORESCENE_API IECore::ConstCompoundDataPtr collapseSplineParameters( const IECore::ConstCompoundDataPtr& parametersData ); -/// \deprecated: Use expandSplines on the whole network, which can handle input connections +/// \deprecated: Use expandSplines on the whole network, which can handle input connections, and supports +/// different spline conventions for different renderers' shader libraries IECORESCENE_API IECore::ConstCompoundDataPtr expandSplineParameters( const IECore::ConstCompoundDataPtr& parametersData ); diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index 3676ba3635..bab0cecf0e 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -50,6 +50,7 @@ #include "boost/regex.hpp" #include +#include #include #include @@ -811,8 +812,36 @@ std::pair< size_t, size_t > getEndPointDuplication( const T &basis ) return std::make_pair( 0, 0 ); } +std::tuple< const std::string*, const std::string*, const std::string*, const std::string*, const std::string* > +lookupSplinePlugSuffixes( const std::string &shaderName ) +{ + // We seem to be able to identify shaders that should use the PRMan convention by whether they start + // with one of the PRMan prefixes. + // NOTE : This will fail if a shader is loaded from an explicit path, rather than being found in the + // search path, because the shader name will include the full file path. We consider this an + // acceptable failure, because shaders should be found in the search paths. + if( boost::starts_with( shaderName, "Pxr" ) || boost::starts_with( shaderName, "Lama" ) ) + { + // The convention used by the PRMan shader library. + static const std::string positions( "_Knots" ); + static const std::string floatValues( "_Floats" ); + static const std::string colorValues( "_Colors" ); + static const std::string basis( "_Interpolation" ); + static const std::string count( "" ); + return { &positions, &floatValues, &colorValues, &basis, &count }; + } + else + { + // The convention used by the OSL shaders that we ship with Gaffer. + static const std::string positions( "Positions" ); + static const std::string values( "Values" ); + static const std::string basis( "Basis" ); + return { &positions, &values, &values, &basis, nullptr }; + } +} + template -void expandSpline( const InternedString &name, const Spline &spline, CompoundDataMap &newParameters ) +void expandSpline( const InternedString &name, const Spline &spline, CompoundDataMap &newParameters, const std::string &shaderName ) { const char *basis = "catmull-rom"; if( spline.basis == Spline::Basis::bezier() ) @@ -866,9 +895,23 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat } } - newParameters[ name.string() + "Positions" ] = positionsData; - newParameters[ name.string() + "Values" ] = valuesData; - newParameters[ name.string() + "Basis" ] = new StringData( basis ); + auto [ positionsSuffix, floatValuesSuffix, colorValuesSuffix, basisSuffix, countSuffix ] = lookupSplinePlugSuffixes( shaderName ); + + newParameters[ name.string() + *positionsSuffix ] = positionsData; + if constexpr( std::is_same_v< typename Spline::YType, float > ) + { + newParameters[ name.string() + *floatValuesSuffix ] = valuesData; + } + else + { + newParameters[ name.string() + *colorValuesSuffix ] = valuesData; + } + newParameters[ name.string() + *basisSuffix ] = new StringData( basis ); + + if( countSuffix ) + { + newParameters[ name.string() + *countSuffix ] = new IntData( positionsData->readable().size() ); + } } template @@ -929,6 +972,122 @@ IECore::DataPtr loadSpline( return resultData; } +void ensureParametersCopy( + const IECore::CompoundDataMap ¶meters, + IECore::CompoundDataPtr ¶metersDataCopy, CompoundDataMap *¶metersCopy +) +{ + if( !parametersDataCopy ) + { + parametersDataCopy = new CompoundData(); + parametersCopy = ¶metersDataCopy->writable(); + *parametersCopy = parameters; + } +} + +IECore::ConstCompoundDataPtr collapseSplineParametersInternal( const IECore::ConstCompoundDataPtr ¶metersData, const std::string &shaderName ) +{ + + auto [ positionsSuffix, floatValuesSuffix, colorValuesSuffix, basisSuffix, countSuffix ] = lookupSplinePlugSuffixes( shaderName ); + + const CompoundDataMap ¶meters( parametersData->readable() ); + CompoundDataPtr newParametersData; + CompoundDataMap *newParameters = nullptr; + + for( const auto &maybeBasis : parameters ) + { + if( !boost::ends_with( maybeBasis.first.string(), *basisSuffix ) ) + { + continue; + } + const StringData *basis = runTimeCast( maybeBasis.second.get() ); + if( !basis ) + { + continue; + } + + + std::string prefix = maybeBasis.first.string().substr( 0, maybeBasis.first.string().size() - basisSuffix->size() ); + const IECore::InternedString positionsName = prefix + *positionsSuffix; + const FloatVectorData *floatPositions = parametersData->member( positionsName ); + if( !floatPositions ) + { + continue; + } + + IECore::InternedString countName; + const IntData *countData = nullptr; + + if( countSuffix ) + { + countName = prefix + *countSuffix; + countData = parametersData->member( countName ); + + if( !countData ) + { + IECore::msg( + Msg::Error, "ShaderNetworkAlgo", + "Using spline format that expects count parameter, but no int count parameter found matching \"" + countName.string() + "\"" + ); + } + else + { + if( (int)floatPositions->readable().size() != countData->readable() ) + { + IECore::msg( + Msg::Error, "ShaderNetworkAlgo", + "Spline count \"" + countName.string() + "\" does not match length of data: " + std::to_string( countData->readable() ) + " != " + std::to_string( floatPositions->readable().size() ) + "\"" + ); + } + } + } + + IECore::InternedString valuesName = prefix + *floatValuesSuffix; + IECore::DataPtr foundSpline; + if( const FloatVectorData *floatValues = parametersData->member( valuesName ) ) + { + foundSpline = loadSpline( basis, floatPositions, floatValues ); + } + else + { + valuesName = prefix + *colorValuesSuffix; + if( const Color3fVectorData *color3Values = parametersData->member( valuesName ) ) + { + foundSpline = loadSpline( basis, floatPositions, color3Values ); + } + else if( const Color4fVectorData *color4Values = parametersData->member( valuesName ) ) + { + foundSpline = loadSpline( basis, floatPositions, color4Values ); + } + + } + + if( foundSpline ) + { + ensureParametersCopy( parameters, newParametersData, newParameters ); + newParameters->erase( maybeBasis.first ); + newParameters->erase( positionsName ); + newParameters->erase( valuesName ); + if( countData ) + { + newParameters->erase( countName ); + } + + (*newParameters)[prefix] = foundSpline; + + } + } + + if( newParametersData ) + { + return newParametersData; + } + else + { + return parametersData; + } +} + const std::string g_oslShader( "osl:shader" ); const std::string g_colorToArrayAdapter( "Utility/__ColorToArray" ); @@ -1003,19 +1162,6 @@ std::pair< InternedString, int > createSplineInputAdapter( return std::make_pair( adapterHandle, getEndPointDuplication( splineData->readable().basis ).first ); } -void ensureParametersCopy( - const IECore::CompoundDataMap ¶meters, - IECore::CompoundDataPtr ¶metersDataCopy, CompoundDataMap *¶metersCopy -) -{ - if( !parametersDataCopy ) - { - parametersDataCopy = new CompoundData(); - parametersCopy = ¶metersDataCopy->writable(); - *parametersCopy = parameters; - } -} - } // namespace void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string targetPrefix ) @@ -1040,12 +1186,12 @@ void ShaderNetworkAlgo::collapseSplines( ShaderNetwork *network, std::string tar } // For nodes which aren't spline adapters, we just need to deal with any parameters that are splines - ConstCompoundDataPtr collapsed = collapseSplineParameters( shader->parametersData() ); + ConstCompoundDataPtr collapsed = collapseSplineParametersInternal( shader->parametersData(), shader->getName() ); if( collapsed != shader->parametersData() ) { - // \todo - this const_cast is ugly, although safe because if the return from collapseSplineParameters + // \todo - this const_cast is ugly, although safe because if the return from collapseSplineParameterInternals // doesn't match the input, it is freshly allocated. Once collapseSplineParameters is fully - // deprecated, and no longer visible publicly, an internal version of collapseSplineParameters could + // deprecated, and no longer visible publicly, collapseSplineParametersInternal could // just return a non-const new parameter data, or nullptr if no changes are needed. network->setShader( name, std::move( new Shader( shader->getName(), shader->getType(), const_cast< CompoundData *>( collapsed.get() ) ) ) ); } @@ -1167,13 +1313,13 @@ void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targe { ensureParametersCopy( origParameters, newParametersData, newParameters ); newParameters->erase( name ); - expandSpline( name, colorSpline->readable(), *newParameters ); + expandSpline( name, colorSpline->readable(), *newParameters, s.second->getName() ); } else if( const SplineffData *floatSpline = runTimeCast( value.get() ) ) { ensureParametersCopy( origParameters, newParametersData, newParameters ); newParameters->erase( name ); - expandSpline( name, floatSpline->readable(), *newParameters ); + expandSpline( name, floatSpline->readable(), *newParameters, s.second->getName() ); } } @@ -1291,76 +1437,7 @@ void ShaderNetworkAlgo::expandSplines( ShaderNetwork *network, std::string targe IECore::ConstCompoundDataPtr ShaderNetworkAlgo::collapseSplineParameters( const IECore::ConstCompoundDataPtr ¶metersData ) { - const CompoundDataMap ¶meters( parametersData->readable() ); - CompoundDataPtr newParametersData; - CompoundDataMap *newParameters = nullptr; - - for( const auto &maybeBasis : parameters ) - { - if( !boost::ends_with( maybeBasis.first.string(), "Basis" ) ) - { - continue; - } - const StringData *basis = runTimeCast( maybeBasis.second.get() ); - if( !basis ) - { - continue; - } - - - std::string prefix = maybeBasis.first.string().substr( 0, maybeBasis.first.string().size() - 5 ); - IECore::InternedString positionsName = prefix + "Positions"; - const auto positionsIter = parameters.find( positionsName ); - const FloatVectorData *floatPositions = nullptr; - - if( positionsIter != parameters.end() ) - { - floatPositions = runTimeCast( positionsIter->second.get() ); - } - - if( !floatPositions ) - { - continue; - } - - IECore::InternedString valuesName = prefix + "Values"; - const auto valuesIter = parameters.find( valuesName ); - - IECore::DataPtr foundSpline; - if( valuesIter != parameters.end() ) - { - if( const FloatVectorData *floatValues = runTimeCast( valuesIter->second.get() ) ) - { - foundSpline = loadSpline( basis, floatPositions, floatValues ); - } - else if( const Color3fVectorData *color3Values = runTimeCast( valuesIter->second.get() ) ) - { - foundSpline = loadSpline( basis, floatPositions, color3Values ); - } - else if( const Color4fVectorData *color4Values = runTimeCast( valuesIter->second.get() ) ) - { - foundSpline = loadSpline( basis, floatPositions, color4Values ); - } - } - - if( foundSpline ) - { - ensureParametersCopy( parameters, newParametersData, newParameters ); - (*newParameters)[prefix] = foundSpline; - newParameters->erase( maybeBasis.first ); - newParameters->erase( positionsName ); - newParameters->erase( valuesName ); - } - } - - if( newParametersData ) - { - return newParametersData; - } - else - { - return parametersData; - } + return collapseSplineParametersInternal( parametersData, "" ); } IECore::ConstCompoundDataPtr ShaderNetworkAlgo::expandSplineParameters( const IECore::ConstCompoundDataPtr ¶metersData ) @@ -1376,13 +1453,13 @@ IECore::ConstCompoundDataPtr ShaderNetworkAlgo::expandSplineParameters( const IE { ensureParametersCopy( parameters, newParametersData, newParameters ); newParameters->erase( i.first ); - expandSpline( i.first, colorSpline->readable(), *newParameters ); + expandSpline( i.first, colorSpline->readable(), *newParameters, "" ); } else if( const SplineffData *floatSpline = runTimeCast( i.second.get() ) ) { ensureParametersCopy( parameters, newParametersData, newParameters ); newParameters->erase( i.first ); - expandSpline( i.first, floatSpline->readable(), *newParameters ); + expandSpline( i.first, floatSpline->readable(), *newParameters, "" ); } } diff --git a/test/IECoreScene/ShaderNetworkAlgoTest.py b/test/IECoreScene/ShaderNetworkAlgoTest.py index 0ae5268a0f..ab05dc5418 100644 --- a/test/IECoreScene/ShaderNetworkAlgoTest.py +++ b/test/IECoreScene/ShaderNetworkAlgoTest.py @@ -406,7 +406,10 @@ def testSplineConversion( self ): ( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) ) ) shaderNetworkOrig = IECoreScene.ShaderNetwork( - shaders = { "test" : IECoreScene.Shader( "test", "osl:shader", parms ) }, + shaders = { + "test" : IECoreScene.Shader( "test", "osl:shader", parms ), + "riTest" : IECoreScene.Shader( "PxrTest", "osl:shader", parms ) + }, output = "test" ) shaderNetwork = shaderNetworkOrig.copy() @@ -429,6 +432,21 @@ def testSplineConversion( self ): ]: self.assertEqual( len( parms[name].value.keys() ) + extra, len( parmsExpanded[name + "Positions"] ) ) + riParmsExpanded = shaderNetwork.getShader( "riTest" ).parameters + + # Test a few things in the PRMan convention, and that we've got the extra "count" plugs + self.assertEqual( set( riParmsExpanded.keys() ), set( [ i + suffix for suffix in [ "", "_Interpolation", "_Knots" ] for i in parms.keys() ] + [ 'testffbSpline_Floats', 'testffbezier_Floats', 'testfColor3fcatmullRom_Colors', 'testfColor3flinear_Colors', 'testffconstant_Floats'] ) ) + self.assertEqual( riParmsExpanded["testffbSpline_Interpolation"], IECore.StringData( "bspline" ) ) + self.assertEqual( riParmsExpanded["testffbSpline_Knots"], IECore.FloatVectorData( [ 0, 10, 20, 21 ] ) ) + self.assertEqual( riParmsExpanded["testffbSpline_Floats"], IECore.FloatVectorData( [ 1, 2, 0, 2 ] ) ) + self.assertEqual( riParmsExpanded["testffbSpline"], IECore.IntData( 4 ) ) + self.assertEqual( riParmsExpanded["testfColor3fcatmullRom_Knots"], IECore.FloatVectorData( [ 0, 10, 20, 30, 40, 50 ] ) ) + self.assertEqual( riParmsExpanded["testfColor3fcatmullRom_Colors"], IECore.Color3fVectorData( [ imath.Color3f( i ) for i in [ 1, 2, 0, 5, 2, 6 ] ] ) ) + self.assertEqual( riParmsExpanded["testfColor3fcatmullRom"], IECore.IntData( 6 ) ) + self.assertEqual( riParmsExpanded["testfColor3flinear_Colors"], IECore.Color3fVectorData( [ imath.Color3f( i ) for i in [ 1, 1, 2, 0, 0 ] ] ) ) + self.assertEqual( riParmsExpanded["testfColor3flinear"], IECore.IntData( 5 ) ) + + IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetwork ) self.assertEqual( shaderNetwork, shaderNetworkOrig ) @@ -490,6 +508,30 @@ def testSplineConversion( self ): IECoreScene.ShaderNetworkAlgo.collapseSplines( shaderNetworkInvalid ) self.assertEqual( shaderNetworkInvalid, shaderNetworkInvalidOrig ) + riParmsExpandedBadCounts = riParmsExpanded.copy() + del riParmsExpandedBadCounts["testffbSpline"] + riParmsExpandedBadCounts["testffbezier"] = IECore.IntData( 10 ) + + riShaderNetworkBadCounts = IECoreScene.ShaderNetwork( + shaders = { "test" : IECoreScene.Shader( "PxrTest", "osl:shader", riParmsExpandedBadCounts ) }, + output = "test" + ) + messageHandler = IECore.CapturingMessageHandler() + with messageHandler: + IECoreScene.ShaderNetworkAlgo.collapseSplines( riShaderNetworkBadCounts ) + + self.assertEqual( + set( [ i.message for i in messageHandler.messages ] ), + set( [ + 'Using spline format that expects count parameter, but no int count parameter found matching "testffbSpline"', + 'Spline count "testffbezier" does not match length of data: 10 != 5"' + ] ) + ) + + # IF we re-expand things, the counts have just been recomputed, and everything is back the way it was before + IECoreScene.ShaderNetworkAlgo.expandSplines( riShaderNetworkBadCounts ) + self.assertEqual( riShaderNetworkBadCounts.getShader( "test" ).parameters, riParmsExpanded ) + def testSplineInputs( self ): fC3fcatmullRom = IECore.SplinefColor3fData( IECore.SplinefColor3f(